diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/emoteCloner/index.tsx index 1b26e2f05..c60085015 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/emoteCloner/index.tsx @@ -24,12 +24,18 @@ import { Margins } from "@utils/margins"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import definePlugin from "@utils/types"; import { findByCodeLazy, findStoreLazy } from "@webpack"; -import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; +import { Constants, ContextMenuApi, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Promisable } from "type-fest"; const StickersStore = findStoreLazy("StickersStore"); const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); +interface OnboardingContextMenuProps { + option: { + emoji: { id: string; name: string; animated: boolean; } | { id: null; }; + }; +} + interface Sticker { t: "Sticker"; description: string; @@ -176,7 +182,6 @@ const getFontSize = (s: string) => { const sizes = [20, 20, 18, 18, 16, 14, 12]; return sizes[s.length] ?? 4; }; - const nameValidator = /^\w+$/i; function CloneModal({ data }: { data: Sticker | Emoji; }) { @@ -272,7 +277,11 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) { ); } -function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable>) { +type Discriminate< + U extends { "t": string; }, + K extends U["t"] +> = U extends { "t": K; } ? U : never; +function buildMenuItem(type: T, fetchData: () => Promisable, "t">>) { return ( Promisable openModalLazy(async () => { const res = await fetchData(); - const data = { t: type, ...res } as Sticker | Emoji; + const data = { t: type, ...res } as any as Sticker | Emoji; const url = getUrl(data); return modalProps => ( @@ -309,7 +318,7 @@ function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable ({ id: favoriteableId, name, - isAnimated: isGifUrl(itemHref ?? itemSrc) + isAnimated: isEmojiAnimated(itemHref ?? itemSrc) })); case "sticker": const sticker = props.message.stickerItems.find(s => s.id === favoriteableId); @@ -354,20 +363,73 @@ const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { t children.push(buildMenuItem("Emoji", () => ({ id, name, - isAnimated: firstChild && isGifUrl(firstChild.src) + isAnimated: firstChild && isEmojiAnimated(firstChild.src) }))); } else if (type === "sticker" && !props.target.className?.includes("lottieCanvas")) { children.push(buildMenuItem("Sticker", () => fetchSticker(id))); } }; +let emojiUrlRegex: RegExp; +// Patches user statuses with a custom emoji +const imageContextMenuPatch: NavContextMenuPatchCallback = (children, { target, src }: { + target?: HTMLImageElement; + src?: string; +}) => { + const [, id] = src?.match(emojiUrlRegex) ?? []; + + if (!id) return; + + const name = target?.alt || "EmojiName"; + children.push(buildMenuItem("Emoji", () => ({ + id, + name, + isAnimated: isEmojiAnimated(src!) + }))); + return; +}; export default definePlugin({ name: "EmoteCloner", description: "Allows you to clone Emotes & Stickers to your own server (right click them)", tags: ["StickerCloner"], - authors: [Devs.Ven, Devs.Nuckyz], + authors: [Devs.Ven, Devs.Nuckyz, Devs.sadan], + + patches: [ + { + find: "emoji.animated||", + replacement: { + match: /(?=onClick:)/, + replace: "onContextMenu:$self.OnboardingContextMenu.bind(null, arguments[0])," + } + } + ], + + start() { + const { CDN_HOST } = window.GLOBAL_ENV; + emojiUrlRegex = new RegExp(`^${location.protocol}//${CDN_HOST}/emojis/(\\d+)\\.\\w+.+$`); + }, + contextMenus: { "message": messageContextMenuPatch, - "expression-picker": expressionPickerPatch - } + "expression-picker": expressionPickerPatch, + "image-context": imageContextMenuPatch + }, + + OnboardingContextMenu({ option: { emoji } }: OnboardingContextMenuProps, ev: React.MouseEvent) { + // covers no emoji and unicode emojis + if (emoji?.id == null) return; + + console.log(emoji); + ContextMenuApi.openContextMenuLazy(ev, async () => { + return () => ( FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} + > + {buildMenuItem("Emoji", () => ({ + ...emoji, + isAnimated: emoji.animated + }))} + ); + }); + }, });