Fix Emote Cloner and improve ReverseImageSearch (#489)

This commit is contained in:
Nuckyz 2023-03-08 04:01:24 -03:00 committed by GitHub
parent 0fb3901a18
commit 253183a16a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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 { export function findGroupChildrenByChildId(id: string, children: Array<React.ReactElement>, itemsArray?: Array<React.ReactElement>): Array<React.ReactElement> | null {
for (const child of children) { for (const child of children) {
if (child === null) continue; if (child == null) continue;
if (child.props?.id === id) return itemsArray ?? null; if (child.props?.id === id) return itemsArray ?? null;

@ -37,7 +37,7 @@ function listener(exports: any, id: number) {
all: true, all: true,
noWarn: true, noWarn: true,
find: "navId:", find: "navId:",
replacement: { replacement: [{
/** Regex explanation /** Regex explanation
* Use of https://blog.stevenlevithan.com/archives/mimic-atomic-groups to mimick atomic groups: (?=(...))\1 * 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)=.+?) * 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"), match: RegExp(`(?=(${id}(?<=(\\i)=.+?).+?\\2\\.${key},{))\\1`, "g"),
replace: "$&contextMenuApiArguments:arguments," replace: "$&contextMenuApiArguments:arguments,"
} }]
}); });
removeListener(listener); removeListener(listener);

@ -16,12 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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 { CheckedTextInput } from "@components/CheckedTextInput";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import Logger from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { makeLazy } from "@utils/misc";
import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; 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"); migratePluginSettings("EmoteCloner", "EmoteYoink");
export default definePlugin({ export default definePlugin({
name: "EmoteCloner", name: "EmoteCloner",
description: "Adds a Clone context menu item to emotes to clone them your own server", description: "Adds a Clone context menu item to emotes to clone them your own server",
authors: [Devs.Ven], authors: [Devs.Ven, Devs.Nuckyz],
dependencies: ["MenuItemDeobfuscatorAPI"], dependencies: ["MenuItemDeobfuscatorAPI", "ContextMenuAPI"],
patches: [{ patches: [
// Literally copy pasted from ReverseImageSearch lol {
find: "open-native-link", find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL",
replacement: { replacement: {
match: /id:"open-native-link".{0,200}\(\{href:(.{0,3}),.{0,200}\},"open-native-link"\)/, match: /(?<=favoriteableType:\i,)(?<=(\i)\.getAttribute\("data-type"\).+?)/,
replace: "$&,$self.makeMenu(arguments[2])" replace: (_, target) => `emoteClonerDataAlt:${target}.alt,`
},
},
// 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>
))
} }
> }
</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/>. * 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 { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Menu } from "@webpack/common"; import { Menu } from "@webpack/common";
@ -29,39 +30,21 @@ const Engines = {
ImgOps: "https://imgops.com/start?url=" ImgOps: "https://imgops.com/start?url="
}; };
export default definePlugin({ function search(src: string, engine: string) {
name: "ReverseImageSearch", open(engine + encodeURIComponent(src), "_blank");
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"
}
]
}],
makeMenu(src: string, target: HTMLElement) { const imageContextMenuPatch: NavContextMenuPatchCallback = (children, args) => {
if (target && !(target instanceof HTMLImageElement) && target.attributes["data-role"]?.value !== "img") if (!args?.[0]) return;
return null; 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 <Menu.MenuItem
label="Search Image" label="Search Image"
key="search-image" key="search-image"
@ -74,7 +57,7 @@ export default definePlugin({
key={key} key={key}
id={key} id={key}
label={engine} label={engine}
action={() => this.search(src, Engines[engine])} action={() => search(src, Engines[engine])}
/> />
); );
})} })}
@ -82,14 +65,33 @@ export default definePlugin({
key="search-image-all" key="search-image-all"
id="search-image-all" id="search-image-all"
label="All" label="All"
action={() => Object.values(Engines).forEach(e => this.search(src, e))} action={() => Object.values(Engines).forEach(e => search(src, e))}
/> />
</Menu.MenuItem> </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 stop() {
search(src: string, engine: string) { removeContextMenuPatch("message", imageContextMenuPatch);
open(engine + encodeURIComponent(src), "_blank");
} }
}); });