diff --git a/src/api/ContextMenu.ts b/src/api/ContextMenu.ts index 64671177b..9a8d7b66f 100644 --- a/src/api/ContextMenu.ts +++ b/src/api/ContextMenu.ts @@ -90,7 +90,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba */ export function findGroupChildrenByChildId(id: string, children: Array, itemsArray?: Array): Array | null { for (const child of children) { - if (child === null) continue; + if (child == null) continue; if (child.props?.id === id) return itemsArray ?? null; diff --git a/src/plugins/apiContextMenu.ts b/src/plugins/apiContextMenu.ts index 77afead84..69dfde4a4 100644 --- a/src/plugins/apiContextMenu.ts +++ b/src/plugins/apiContextMenu.ts @@ -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 `=`: ${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); diff --git a/src/plugins/emoteCloner.tsx b/src/plugins/emoteCloner.tsx index e252fe5fb..7db5efddf 100644 --- a/src/plugins/emoteCloner.tsx +++ b/src/plugins/emoteCloner.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ -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(( + + openModal(modalProps => ( + + + + Clone {name} + + + + + + )) + } + > + + )); + } +}; + 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: /(?.).onHeightUpdate.{0,200}(.)=(.)=.\.url;.+?\(null!=\3\?\3:\2[^)]+/, - replace: "$&,$.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 - openModal(modalProps => ( - - - - Clone {name} - - - - - - )) + patches: [ + { + find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL", + replacement: { + match: /(?<=favoriteableType:\i,)(?<=(\i)\.getAttribute\("data-type"\).+?)/, + replace: (_, target) => `emoteClonerDataAlt:${target}.alt,` } - > - ; + } + ], + + start() { + addContextMenuPatch("message", messageContextMenuPatch); }, + + stop() { + removeContextMenuPatch("message", messageContextMenuPatch); + } }); diff --git a/src/plugins/reverseImageSearch.tsx b/src/plugins/reverseImageSearch.tsx index 4d9f040b1..6335fbd9f 100644 --- a/src/plugins/reverseImageSearch.tsx +++ b/src/plugins/reverseImageSearch.tsx @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +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(( 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))} /> - ); + )); + } +}; + +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); } });