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 {
|
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");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue