diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 92b0e8234..7927f405d 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -26,7 +26,7 @@ import definePlugin, { OptionType } from "@utils/types"; import type { Emoji, Message, RenderModalProps, Sticker } from "@vencord/discord-types"; import { StickerFormatType } from "@vencord/discord-types/enums"; import { findByCodeLazy, findByPropsLazy, proxyLazyWebpack } from "@webpack"; -import { ChannelStore, ConfirmModal, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, lodash, openModal, Parser, PermissionsBits, PermissionStore, StickersStore, UploadHandler, UserSettingsActionCreators, UserSettingsProtoStore, UserStore } from "@webpack/common"; +import { ChannelStore, ConfirmModal, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, openModal, PermissionsBits, PermissionStore, StickersStore, UploadHandler, UserSettingsActionCreators, UserSettingsProtoStore, UserStore } from "@webpack/common"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; import type { ReactElement, ReactNode } from "react"; @@ -40,6 +40,8 @@ function searchProtoClassField(localName: string, protoClass: any) { return fieldGetter?.(); } +const logger = new Logger("FakeNitro"); + const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators); const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass)); const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators)); @@ -328,20 +330,11 @@ export default definePlugin({ }, { find: '["strong","em","u","text","inlineCode","s","spoiler"]', - replacement: [ - { - // Call our function to decide whether the emoji link should be kept or not - predicate: () => settings.store.transformEmojis, - match: /1!==(\i)\.length\|\|1!==\i\.length/, - replace: (m, content) => `${m}||$self.shouldKeepEmojiLink(${content}[0])` - }, - { - // Patch the rendered message content to add fake nitro emojis or remove sticker links - predicate: () => settings.store.transformEmojis || settings.store.transformStickers, - match: /(?=return{hasSpoilerEmbeds:\i,hasBailedAst:\i,content:(\i))/, - replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);` - } - ] + predicate: () => settings.store.transformEmojis || settings.store.transformStickers, + replacement: { + match: /\(\{ast:(\i)(?=,inline:\i)/, + replace: "({ast:$self.transformAst($1)" + } }, { find: "}renderStickersAccessories(", @@ -520,107 +513,58 @@ export default definePlugin({ if (!Array.isArray(child.props.children)) child.props.children = [child.props.children]; }, - patchFakeNitroEmojisOrRemoveStickersLinks(content: Array, inline: boolean) { - // If content has more than one child or it's a single ReactElement like a header, list or span - if ((content.length > 1 || typeof content[0]?.type === "string") && !settings.store.transformCompoundSentence) return content; + transformAst(ast: any[]) { + try { + if (!settings.store.transformCompoundSentence && ast.length > 1) { + return ast; + } + // Filter out sticker links. These are transformed into actual stickers later, so we don't want duplicate links + if (settings.store.transformStickers) { + ast = ast.filter(node => { + const { type, target } = node; - let nextIndex = content.length; + if (type !== "link") return true; + if (fakeNitroStickerRegex.test(target)) return false; + + const gifStickerLinkMatch = target.match(fakeNitroGifStickerRegex); + const isGifStickerLink = gifStickerLinkMatch && StickersStore.getStickerById(gifStickerLinkMatch[1]); + + return !isGifStickerLink; + }); + } - const transformLinkChild = (child: ReactElement) => { if (settings.store.transformEmojis) { - const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex); - if (fakeNitroMatch) { + ast = ast.map(node => { + const { type, target } = node; + + if (type !== "link") return node; + + const fakeNitroMatch = target.match(fakeNitroEmojiRegex); + if (!fakeNitroMatch) return node; + let url: URL | null = null; try { - url = new URL(child.props.href); + url = new URL(target); } catch { } - const emojiName = EmojiStore.getCustomEmojiById(fakeNitroMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroEmoji"; - const isAnimated = fakeNitroMatch[2] === "gif" || url?.searchParams.get("animated") === "true"; + const emojiId = fakeNitroMatch[1]; + const emojiName = EmojiStore.getCustomEmojiById(emojiId)?.name ?? url?.searchParams.get("name") ?? "FakeNitroEmoji"; + const animated = fakeNitroMatch[2] === "gif" || url?.searchParams.get("animated") === "true"; - return Parser.defaultRules.customEmoji.react({ - jumboable: !inline && content.length === 1 && typeof content[0].type !== "string", - animated: isAnimated, - emojiId: fakeNitroMatch[1], - name: emojiName, + return { + animated, + emojiId, + name: `:${emojiName}:`, + type: "customEmoji", fake: true - }, void 0, { key: String(nextIndex++) }); - } + }; + }); } - - if (settings.store.transformStickers) { - if (fakeNitroStickerRegex.test(child.props.href)) return null; - - const gifMatch = child.props.href.match(fakeNitroGifStickerRegex); - if (gifMatch) { - // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickersStore contains the id of the fake sticker - if (StickersStore.getStickerById(gifMatch[1])) return null; - } - } - - return child; - }; - - const transformChild = (child: ReactElement) => { - if (child?.props?.trusted != null) return transformLinkChild(child); - if (child?.props?.children != null) { - if (!Array.isArray(child.props.children)) { - child.props.children = modifyChild(child.props.children); - return child; - } - - child.props.children = modifyChildren(child.props.children); - if (child.props.children.length === 0) return null; - return child; - } - - return child; - }; - - const modifyChild = (child: ReactElement) => { - const newChild = transformChild(child); - - if (newChild?.type === "ul" || newChild?.type === "ol") { - this.ensureChildrenIsArray(newChild); - if (newChild.props.children.length === 0) return null; - - let listHasAnItem = false; - for (const [index, child] of newChild.props.children.entries()) { - if (child == null) { - delete newChild.props.children[index]; - continue; - } - - this.ensureChildrenIsArray(child); - if (child.props.children.length > 0) listHasAnItem = true; - else delete newChild.props.children[index]; - } - - if (!listHasAnItem) return null; - - newChild.props.children = this.clearEmptyArrayItems(newChild.props.children); - } - - return newChild; - }; - - const modifyChildren = (children: Array>) => { - for (const [index, child] of children.entries()) children[index] = modifyChild(child); - - children = this.clearEmptyArrayItems(children); - - return children; - }; - - try { - const newContent = modifyChildren(lodash.cloneDeep(content)); - this.trimContent(newContent); - - return newContent; - } catch (err) { - new Logger("FakeNitro").error(err); - return content; + } catch (e) { + logger.error("Error transforming AST:", e); } + + return ast; }, patchFakeNitroStickers(stickers: Array, message: Message) { @@ -722,10 +666,6 @@ export default definePlugin({ }); }, - shouldKeepEmojiLink(link: any) { - return link.target && fakeNitroEmojiRegex.test(link.target); - }, - addFakeNotice(type: FakeNoticeType, node: Array, fake: boolean) { if (!fake) return node; diff --git a/src/plugins/imageLink/index.ts b/src/plugins/imageLink/index.ts index 0d01cb1d3..c3edeb23a 100644 --- a/src/plugins/imageLink/index.ts +++ b/src/plugins/imageLink/index.ts @@ -15,7 +15,8 @@ export default definePlugin({ patches: [ { - find: "unknownUserMentionPlaceholder:", + // small util file + find: "={linkCount:0,onlyLinks:!1};function ", replacement: { // SimpleEmbedTypes.has(embed.type) && isEmbedInline(embed) match: /\i\.has\(\i\.type\)&&\(0,\i\.\i\)\(\i\)/,