Fix Emote Cloner and improve ReverseImageSearch (#489)
This commit is contained in:
		
							parent
							
								
									0fb3901a18
								
							
						
					
					
						commit
						253183a16a
					
				
					 4 changed files with 107 additions and 103 deletions
				
			
		|  | @ -90,7 +90,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba | |||
|  */ | ||||
| export function findGroupChildrenByChildId(id: string, children: Array<React.ReactElement>, itemsArray?: Array<React.ReactElement>): Array<React.ReactElement> | null { | ||||
|     for (const child of children) { | ||||
|         if (child === null) continue; | ||||
|         if (child == null) continue; | ||||
| 
 | ||||
|         if (child.props?.id === id) return itemsArray ?? null; | ||||
| 
 | ||||
|  |  | |||
|  | @ -37,7 +37,7 @@ function listener(exports: any, id: number) { | |||
|                 all: true, | ||||
|                 noWarn: true, | ||||
|                 find: "navId:", | ||||
|                 replacement: { | ||||
|                 replacement: [{ | ||||
|                     /** Regex explanation | ||||
|                      * Use of https://blog.stevenlevithan.com/archives/mimic-atomic-groups to mimick atomic groups: (?=(...))\1
 | ||||
|                      * Match ${id} and look behind it for the first match of `<variable name>=`: ${id}(?=(\i)=.+?) | ||||
|  | @ -45,7 +45,7 @@ function listener(exports: any, id: number) { | |||
|                      */ | ||||
|                     match: RegExp(`(?=(${id}(?<=(\\i)=.+?).+?\\2\\.${key},{))\\1`, "g"), | ||||
|                     replace: "$&contextMenuApiArguments:arguments," | ||||
|                 } | ||||
|                 }] | ||||
|             }); | ||||
| 
 | ||||
|             removeListener(listener); | ||||
|  |  | |||
|  | @ -16,12 +16,12 @@ | |||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| 
 | ||||
| import { migratePluginSettings, Settings } from "@api/settings"; | ||||
| import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; | ||||
| import { migratePluginSettings } from "@api/settings"; | ||||
| import { CheckedTextInput } from "@components/CheckedTextInput"; | ||||
| import { Devs } from "@utils/constants"; | ||||
| import Logger from "@utils/Logger"; | ||||
| import { Margins } from "@utils/margins"; | ||||
| import { makeLazy } from "@utils/misc"; | ||||
| import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal"; | ||||
| import definePlugin from "@utils/types"; | ||||
| import { findByCodeLazy, findByPropsLazy } from "@webpack"; | ||||
|  | @ -176,72 +176,74 @@ function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: str | |||
|     ); | ||||
| } | ||||
| 
 | ||||
| const messageContextMenuPatch: NavContextMenuPatchCallback = (children, args) => { | ||||
|     if (!args?.[0]) return; | ||||
|     const { favoriteableId, emoteClonerDataAlt, itemHref, itemSrc, favoriteableType } = args[0]; | ||||
| 
 | ||||
|     if (!emoteClonerDataAlt || favoriteableType !== "emoji") return; | ||||
| 
 | ||||
|     const name = emoteClonerDataAlt.match(/:(.*)(?:~\d+)?:/)?.[1]; | ||||
|     if (!name || !favoriteableId) return; | ||||
| 
 | ||||
|     const src = itemHref ?? itemSrc; | ||||
|     const isAnimated = new URL(src).pathname.endsWith(".gif"); | ||||
| 
 | ||||
|     const group = findGroupChildrenByChildId("save-image", children); | ||||
|     if (group && !group.some(child => child?.props?.id === "emote-cloner")) { | ||||
|         group.push(( | ||||
|             <Menu.MenuItem | ||||
|                 id="emote-cloner" | ||||
|                 key="emote-cloner" | ||||
|                 label="Clone" | ||||
|                 action={() => | ||||
|                     openModal(modalProps => ( | ||||
|                         <ModalRoot {...modalProps}> | ||||
|                             <ModalHeader> | ||||
|                                 <img | ||||
|                                     role="presentation" | ||||
|                                     aria-hidden | ||||
|                                     src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${favoriteableId}.${isAnimated ? "gif" : "png"}`} | ||||
|                                     alt="" | ||||
|                                     height={24} | ||||
|                                     width={24} | ||||
|                                     style={{ marginRight: "0.5em" }} | ||||
|                                 /> | ||||
|                                 <Forms.FormText>Clone {name}</Forms.FormText> | ||||
|                             </ModalHeader> | ||||
|                             <ModalContent> | ||||
|                                 <CloneModal id={favoriteableId} name={name} isAnimated={isAnimated} /> | ||||
|                             </ModalContent> | ||||
|                         </ModalRoot> | ||||
|                     )) | ||||
|                 } | ||||
|             > | ||||
|             </Menu.MenuItem> | ||||
|         )); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| migratePluginSettings("EmoteCloner", "EmoteYoink"); | ||||
| export default definePlugin({ | ||||
|     name: "EmoteCloner", | ||||
|     description: "Adds a Clone context menu item to emotes to clone them your own server", | ||||
|     authors: [Devs.Ven], | ||||
|     dependencies: ["MenuItemDeobfuscatorAPI"], | ||||
|     authors: [Devs.Ven, Devs.Nuckyz], | ||||
|     dependencies: ["MenuItemDeobfuscatorAPI", "ContextMenuAPI"], | ||||
| 
 | ||||
|     patches: [{ | ||||
|         // Literally copy pasted from ReverseImageSearch lol
 | ||||
|         find: "open-native-link", | ||||
|         replacement: { | ||||
|             match: /id:"open-native-link".{0,200}\(\{href:(.{0,3}),.{0,200}\},"open-native-link"\)/, | ||||
|             replace: "$&,$self.makeMenu(arguments[2])" | ||||
|         }, | ||||
| 
 | ||||
|     }, | ||||
|     // Also copy pasted from Reverse Image Search
 | ||||
|     { | ||||
|         // pass the target to the open link menu so we can grab its data
 | ||||
|         find: "REMOVE_ALL_REACTIONS_CONFIRM_BODY,", | ||||
|         predicate: makeLazy(() => !Settings.plugins.ReverseImageSearch.enabled), | ||||
|         noWarn: true, | ||||
|         replacement: { | ||||
|             match: /(?<props>.).onHeightUpdate.{0,200}(.)=(.)=.\.url;.+?\(null!=\3\?\3:\2[^)]+/, | ||||
|             replace: "$&,$<props>.target" | ||||
|         } | ||||
|     }], | ||||
| 
 | ||||
|     makeMenu(htmlElement: HTMLImageElement) { | ||||
|         if (htmlElement?.dataset.type !== "emoji") | ||||
|             return null; | ||||
| 
 | ||||
|         const { id } = htmlElement.dataset; | ||||
|         const name = htmlElement.alt.match(/:(.*)(?:~\d+)?:/)?.[1]; | ||||
| 
 | ||||
|         if (!name || !id) | ||||
|             return null; | ||||
| 
 | ||||
|         const isAnimated = new URL(htmlElement.src).pathname.endsWith(".gif"); | ||||
| 
 | ||||
|         return <Menu.MenuItem | ||||
|             id="emote-cloner" | ||||
|             key="emote-cloner" | ||||
|             label="Clone" | ||||
|             action={() => | ||||
|                 openModal(modalProps => ( | ||||
|                     <ModalRoot {...modalProps}> | ||||
|                         <ModalHeader> | ||||
|                             <img | ||||
|                                 role="presentation" | ||||
|                                 aria-hidden | ||||
|                                 src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${id}.${isAnimated ? "gif" : "png"}`} | ||||
|                                 alt="" | ||||
|                                 height={24} | ||||
|                                 width={24} | ||||
|                                 style={{ marginRight: "0.5em" }} | ||||
|                             /> | ||||
|                             <Forms.FormText>Clone {name}</Forms.FormText> | ||||
|                         </ModalHeader> | ||||
|                         <ModalContent> | ||||
|                             <CloneModal id={id} name={name} isAnimated={isAnimated} /> | ||||
|                         </ModalContent> | ||||
|                     </ModalRoot> | ||||
|                 )) | ||||
|     patches: [ | ||||
|         { | ||||
|             find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL", | ||||
|             replacement: { | ||||
|                 match: /(?<=favoriteableType:\i,)(?<=(\i)\.getAttribute\("data-type"\).+?)/, | ||||
|                 replace: (_, target) => `emoteClonerDataAlt:${target}.alt,` | ||||
|             } | ||||
|         > | ||||
|         </Menu.MenuItem>; | ||||
|         } | ||||
|     ], | ||||
| 
 | ||||
|     start() { | ||||
|         addContextMenuPatch("message", messageContextMenuPatch); | ||||
|     }, | ||||
| 
 | ||||
|     stop() { | ||||
|         removeContextMenuPatch("message", messageContextMenuPatch); | ||||
|     } | ||||
| }); | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| 
 | ||||
| import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; | ||||
| import { Devs } from "@utils/constants"; | ||||
| import definePlugin from "@utils/types"; | ||||
| import { Menu } from "@webpack/common"; | ||||
|  | @ -29,39 +30,21 @@ const Engines = { | |||
|     ImgOps: "https://imgops.com/start?url=" | ||||
| }; | ||||
| 
 | ||||
| export default definePlugin({ | ||||
|     name: "ReverseImageSearch", | ||||
|     description: "Adds ImageSearch to image context menus", | ||||
|     authors: [Devs.Ven], | ||||
|     dependencies: ["MenuItemDeobfuscatorAPI"], | ||||
|     patches: [{ | ||||
|         find: "open-native-link", | ||||
|         replacement: { | ||||
|             match: /id:"open-native-link".{0,200}\(\{href:(.{0,3}),.{0,200}\},"open-native-link"\)/, | ||||
|             replace: (m, src) => | ||||
|                 `${m},Vencord.Plugins.plugins.ReverseImageSearch.makeMenu(${src}, arguments[2])` | ||||
|         } | ||||
|     }, { | ||||
|         // pass the target to the open link menu so we can check if it's an image
 | ||||
|         find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL", | ||||
|         replacement: [ | ||||
|             { | ||||
|                 match: /ariaLabel:\i\.Z\.Messages\.MESSAGE_ACTIONS_MENU_LABEL/, | ||||
|                 replace: "$&,_vencordTarget:arguments[0].target" | ||||
|             }, | ||||
|             { | ||||
|                 // var f = props.itemHref, .... MakeNativeMenu(null != f ? f : blah)
 | ||||
|                 match: /(\i)=\i\.itemHref,.+?\(null!=\1\?\1:.{1,10}(?=\))/, | ||||
|                 replace: "$&,arguments[0]._vencordTarget" | ||||
|             } | ||||
|         ] | ||||
|     }], | ||||
| function search(src: string, engine: string) { | ||||
|     open(engine + encodeURIComponent(src), "_blank"); | ||||
| } | ||||
| 
 | ||||
|     makeMenu(src: string, target: HTMLElement) { | ||||
|         if (target && !(target instanceof HTMLImageElement) && target.attributes["data-role"]?.value !== "img") | ||||
|             return null; | ||||
| const imageContextMenuPatch: NavContextMenuPatchCallback = (children, args) => { | ||||
|     if (!args?.[0]) return; | ||||
|     const { reverseImageSearchType, itemHref, itemSrc } = args[0]; | ||||
| 
 | ||||
|         return ( | ||||
|     if (!reverseImageSearchType || reverseImageSearchType !== "img") return; | ||||
| 
 | ||||
|     const src = itemHref ?? itemSrc; | ||||
| 
 | ||||
|     const group = findGroupChildrenByChildId("save-image", children); | ||||
|     if (group && !group.some(child => child?.props?.id === "search-image")) { | ||||
|         group.push(( | ||||
|             <Menu.MenuItem | ||||
|                 label="Search Image" | ||||
|                 key="search-image" | ||||
|  | @ -74,7 +57,7 @@ export default definePlugin({ | |||
|                             key={key} | ||||
|                             id={key} | ||||
|                             label={engine} | ||||
|                             action={() => this.search(src, Engines[engine])} | ||||
|                             action={() => search(src, Engines[engine])} | ||||
|                         /> | ||||
|                     ); | ||||
|                 })} | ||||
|  | @ -82,14 +65,33 @@ export default definePlugin({ | |||
|                     key="search-image-all" | ||||
|                     id="search-image-all" | ||||
|                     label="All" | ||||
|                     action={() => Object.values(Engines).forEach(e => this.search(src, e))} | ||||
|                     action={() => Object.values(Engines).forEach(e => search(src, e))} | ||||
|                 /> | ||||
|             </Menu.MenuItem> | ||||
|         ); | ||||
|         )); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| export default definePlugin({ | ||||
|     name: "ReverseImageSearch", | ||||
|     description: "Adds ImageSearch to image context menus", | ||||
|     authors: [Devs.Ven, Devs.Nuckyz], | ||||
|     dependencies: ["MenuItemDeobfuscatorAPI", "ContextMenuAPI"], | ||||
|     patches: [ | ||||
|         { | ||||
|             find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL", | ||||
|             replacement: { | ||||
|                 match: /(?<=favoriteableType:\i,)(?<=(\i)\.getAttribute\("data-type"\).+?)/, | ||||
|                 replace: (_, target) => `reverseImageSearchType:${target}.getAttribute("data-role"),` | ||||
|             } | ||||
|         } | ||||
|     ], | ||||
| 
 | ||||
|     start() { | ||||
|         addContextMenuPatch("message", imageContextMenuPatch); | ||||
|     }, | ||||
| 
 | ||||
|     // openUrl is a mangled export, so just match it in the module and pass it
 | ||||
|     search(src: string, engine: string) { | ||||
|         open(engine + encodeURIComponent(src), "_blank"); | ||||
|     stop() { | ||||
|         removeContextMenuPatch("message", imageContextMenuPatch); | ||||
|     } | ||||
| }); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue