From 6bf6583e7d2aaa73cf575504760876f4fa985ee2 Mon Sep 17 00:00:00 2001 From: V Date: Tue, 23 May 2023 03:02:48 +0200 Subject: FakeNitro: Fix unavailable emotes; Discord Stickers are now free (#1184) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- src/plugins/fakeNitro.ts | 726 ++++++++++++++++++++++++++++++++++++++++++++++ src/plugins/fakeNitro.tsx | 713 --------------------------------------------- 2 files changed, 726 insertions(+), 713 deletions(-) create mode 100644 src/plugins/fakeNitro.ts delete mode 100644 src/plugins/fakeNitro.tsx diff --git a/src/plugins/fakeNitro.ts b/src/plugins/fakeNitro.ts new file mode 100644 index 0000000..0d69538 --- /dev/null +++ b/src/plugins/fakeNitro.ts @@ -0,0 +1,726 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; +import { definePluginSettings, Settings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies"; +import { getCurrentGuild } from "@utils/discord"; +import { proxyLazy } from "@utils/lazy"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; +import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common"; +import type { Message } from "discord-types/general"; +import type { ReactNode } from "react"; + +const DRAFT_TYPE = 0; +const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR"); +const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore"); +const PreloadedUserSettingsProtoHandler = findLazy(m => m.ProtoClass?.typeName === "discord_protos.discord_users.v1.PreloadedUserSettings"); +const ReaderFactory = findByPropsLazy("readerFactory"); +const StickerStore = findStoreLazy("StickersStore") as { + getPremiumPacks(): StickerPack[]; + getAllGuildStickers(): Map; + getStickerById(id: string): Sticker | undefined; +}; + +function searchProtoClass(localName: string, parentProtoClass: any) { + if (!parentProtoClass) return; + + const field = parentProtoClass.fields.find(field => field.localName === localName); + if (!field) return; + + const getter: any = Object.values(field).find(value => typeof value === "function"); + return getter?.(); +} + +const AppearanceSettingsProto = proxyLazy(() => searchProtoClass("appearance", PreloadedUserSettingsProtoHandler.ProtoClass)); +const ClientThemeSettingsProto = proxyLazy(() => searchProtoClass("clientThemeSettings", AppearanceSettingsProto)); + +const USE_EXTERNAL_EMOJIS = 1n << 18n; +const USE_EXTERNAL_STICKERS = 1n << 37n; + +const enum EmojiIntentions { + REACTION = 0, + STATUS = 1, + COMMUNITY_CONTENT = 2, + CHAT = 3, + GUILD_STICKER_RELATED_EMOJI = 4, + GUILD_ROLE_BENEFIT_EMOJI = 5, + COMMUNITY_CONTENT_ONLY = 6, + SOUNDBOARD = 7 +} + +const enum StickerType { + PNG = 1, + APNG = 2, + LOTTIE = 3, + // don't think you can even have gif stickers but the docs have it + GIF = 4 +} + +interface BaseSticker { + available: boolean; + description: string; + format_type: number; + id: string; + name: string; + tags: string; + type: number; +} +interface GuildSticker extends BaseSticker { + guild_id: string; +} +interface DiscordSticker extends BaseSticker { + pack_id: string; +} +type Sticker = GuildSticker | DiscordSticker; + +interface StickerPack { + id: string; + name: string; + sku_id: string; + description: string; + cover_sticker_id: string; + banner_asset_id: string; + stickers: Sticker[]; +} + +const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/; +const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./; +const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/; + +const settings = definePluginSettings({ + enableEmojiBypass: { + description: "Allow sending fake emojis", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + }, + emojiSize: { + description: "Size of the emojis when sending", + type: OptionType.SLIDER, + default: 48, + markers: [32, 48, 64, 128, 160, 256, 512] + }, + transformEmojis: { + description: "Whether to transform fake emojis into real ones", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + }, + enableStickerBypass: { + description: "Allow sending fake stickers", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + }, + stickerSize: { + description: "Size of the stickers when sending", + type: OptionType.SLIDER, + default: 160, + markers: [32, 64, 128, 160, 256, 512] + }, + transformStickers: { + description: "Whether to transform fake stickers into real ones", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + }, + transformCompoundSentence: { + description: "Whether to transform fake stickers and emojis in compound sentences (sentences with more content than just the fake emoji or sticker link)", + type: OptionType.BOOLEAN, + default: false + }, + enableStreamQualityBypass: { + description: "Allow streaming in nitro quality", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + } +}); + +export default definePlugin({ + name: "FakeNitro", + authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.obscurity, Devs.captain, Devs.Nuckyz, Devs.AutumnVN], + description: "Allows you to stream in nitro quality, send fake emojis/stickers and use client themes.", + dependencies: ["MessageEventsAPI"], + + settings, + + patches: [ + { + find: ".PREMIUM_LOCKED;", + predicate: () => settings.store.enableEmojiBypass, + replacement: [ + { + match: /(?<=(\i)=\i\.intention)/, + replace: (_, intention) => `,fakeNitroIntention=${intention}` + }, + { + match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g, + replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0' + }, + { + match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, + replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))` + }, + { + match: /if\(!\i\.available/, + replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))` + } + ] + }, + { + find: "canUseAnimatedEmojis:function", + predicate: () => settings.store.enableEmojiBypass, + replacement: { + match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g, + replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` + } + }, + { + find: "canUseStickersEverywhere:function", + predicate: () => settings.store.enableStickerBypass, + replacement: { + match: /canUseStickersEverywhere:function\(\i\){/, + replace: "$&return true;" + }, + }, + { + find: "\"SENDABLE\"", + predicate: () => settings.store.enableStickerBypass, + replacement: { + match: /(\w+)\.available\?/, + replace: "true?" + } + }, + { + find: "canStreamHighQuality:function", + predicate: () => settings.store.enableStreamQualityBypass, + replacement: [ + "canUseHighVideoUploadQuality", + "canStreamHighQuality", + "canStreamMidQuality" + ].map(func => { + return { + match: new RegExp(`${func}:function\\(\\i\\){`), + replace: "$&return true;" + }; + }) + }, + { + find: "STREAM_FPS_OPTION.format", + predicate: () => settings.store.enableStreamQualityBypass, + replacement: { + match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g, + replace: "" + } + }, + { + find: "canUseClientThemes:function", + replacement: { + match: /canUseClientThemes:function\(\i\){/, + replace: "$&return true;" + } + }, + { + find: '.displayName="UserSettingsProtoStore"', + replacement: [ + { + match: /CONNECTION_OPEN:function\((\i)\){/, + replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);` + }, + { + match: /=(\i)\.local;/, + replace: (m, props) => `${m}${props}.local||$self.handleProtoChange(${props}.settings.proto);` + } + ] + }, + { + find: "updateTheme:function", + replacement: { + match: /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)(\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\))/, + replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});` + } + }, + { + find: '["strong","em","u","text","inlineCode","s","spoiler"]', + replacement: [ + { + predicate: () => settings.store.transformEmojis, + match: /1!==(\i)\.length\|\|1!==\i\.length/, + replace: (m, content) => `${m}||$self.shouldKeepEmojiLink(${content}[0])` + }, + { + predicate: () => settings.store.transformEmojis || settings.store.transformStickers, + match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/, + replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);` + } + ] + }, + { + find: "renderEmbeds=function", + replacement: [ + { + predicate: () => settings.store.transformEmojis || settings.store.transformStickers, + match: /(renderEmbeds=function\((\i)\){)(.+?embeds\.map\(\(function\((\i)\){)/, + replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;` + }, + { + predicate: () => settings.store.transformStickers, + match: /renderStickersAccessories=function\((\i)\){var (\i)=\(0,\i\.\i\)\(\i\),/, + replace: (m, message, stickers) => `${m}${stickers}=$self.patchFakeNitroStickers(${stickers},${message}),` + }, + { + predicate: () => settings.store.transformStickers, + match: /renderAttachments=function\(\i\){var (\i)=\i.attachments.+?;/, + replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});` + } + ] + }, + { + find: ".STICKER_IN_MESSAGE_HOVER,", + predicate: () => settings.store.transformStickers, + replacement: [ + { + match: /var (\i)=\i\.renderableSticker,.{0,50}closePopout.+?channel:\i,closePopout:\i,/, + replace: (m, renderableSticker) => `${m}renderableSticker:${renderableSticker},` + }, + { + match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/, + replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice("STICKER",${reactNode},!!${props}.renderableSticker?.fake)` + } + ] + }, + { + find: ".Messages.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION", + predicate: () => settings.store.transformEmojis, + replacement: { + match: /((\i)=\i\.node,\i=\i\.emojiSourceDiscoverableGuild)(.+?return )(.{0,450}Messages\.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION.+?}\))/, + replace: (_, rest1, node, rest2, reactNode) => `${rest1},fakeNitroNode=${node}${rest2}$self.addFakeNotice("EMOJI",${reactNode},fakeNitroNode.fake)` + } + } + ], + + get guildId() { + return getCurrentGuild()?.id; + }, + + get canUseEmotes() { + return (UserStore.getCurrentUser().premiumType ?? 0) > 0; + }, + + get canUseStickers() { + return (UserStore.getCurrentUser().premiumType ?? 0) > 1; + }, + + handleProtoChange(proto: any, user: any) { + if (proto == null || typeof proto === "string" || !UserSettingsProtoStore || (!proto.appearance && !AppearanceSettingsProto)) return; + + const premiumType: number = user?.premium_type ?? UserStore?.getCurrentUser()?.premiumType ?? 0; + + if (premiumType !== 2) { + proto.appearance ??= AppearanceSettingsProto.create(); + + if (UserSettingsProtoStore.settings.appearance?.theme != null) { + proto.appearance.theme = UserSettingsProtoStore.settings.appearance.theme; + } + + if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null && ClientThemeSettingsProto) { + const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({ + backgroundGradientPresetId: { + value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value + } + }); + + proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto; + proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId; + } + } + }, + + handleGradientThemeSelect(backgroundGradientPresetId: number | undefined, theme: number, original: () => void) { + const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0; + if (premiumType === 2 || backgroundGradientPresetId == null) return original(); + + if (!AppearanceSettingsProto || !ClientThemeSettingsProto || !ReaderFactory) return; + + const currentAppearanceProto = PreloadedUserSettingsProtoHandler.getCurrentValue().appearance; + + const newAppearanceProto = currentAppearanceProto != null + ? AppearanceSettingsProto.fromBinary(AppearanceSettingsProto.toBinary(currentAppearanceProto), ReaderFactory) + : AppearanceSettingsProto.create(); + + newAppearanceProto.theme = theme; + + const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({ + backgroundGradientPresetId: { + value: backgroundGradientPresetId + } + }); + + newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummyProto; + newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId; + + const proto = PreloadedUserSettingsProtoHandler.ProtoClass.create(); + proto.appearance = newAppearanceProto; + + FluxDispatcher.dispatch({ + type: "USER_SETTINGS_PROTO_UPDATE", + local: true, + partial: true, + settings: { + type: 1, + proto + } + }); + }, + + patchFakeNitroEmojisOrRemoveStickersLinks(content: Array, inline: boolean) { + if (content.length > 1 && !settings.store.transformCompoundSentence) return content; + + const newContent: Array = []; + + let nextIndex = content.length; + + for (const element of content) { + if (element.props?.trusted == null) { + newContent.push(element); + continue; + } + + if (settings.store.transformEmojis) { + const fakeNitroMatch = element.props.href.match(fakeNitroEmojiRegex); + if (fakeNitroMatch) { + let url: URL | null = null; + try { + url = new URL(element.props.href); + } catch { } + + const emojiName = EmojiStore.getCustomEmojiById(fakeNitroMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroEmoji"; + + newContent.push(Parser.defaultRules.customEmoji.react({ + jumboable: !inline && content.length === 1, + animated: fakeNitroMatch[2] === "gif", + emojiId: fakeNitroMatch[1], + name: emojiName, + fake: true + }, void 0, { key: String(nextIndex++) })); + + continue; + } + } + + if (settings.store.transformStickers) { + if (fakeNitroStickerRegex.test(element.props.href)) continue; + + const gifMatch = element.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 StickerStore contains the id of the fake sticker + if (StickerStore.getStickerById(gifMatch[1])) continue; + } + } + + newContent.push(element); + } + + const firstContent = newContent[0]; + if (typeof firstContent === "string") newContent[0] = firstContent.trimStart(); + + return newContent; + }, + + patchFakeNitroStickers(stickers: Array, message: Message) { + const itemsToMaybePush: Array = []; + + const contentItems = message.content.split(/\s/); + if (contentItems.length === 1 && !settings.store.transformCompoundSentence) itemsToMaybePush.push(contentItems[0]); + else itemsToMaybePush.push(...contentItems); + + itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url)); + + for (const item of itemsToMaybePush) { + const imgMatch = item.match(fakeNitroStickerRegex); + if (imgMatch) { + let url: URL | null = null; + try { + url = new URL(item); + } catch { } + + const stickerName = StickerStore.getStickerById(imgMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroSticker"; + stickers.push({ + format_type: 1, + id: imgMatch[1], + name: stickerName, + fake: true + }); + + continue; + } + + const gifMatch = item.match(fakeNitroGifStickerRegex); + if (gifMatch) { + if (!StickerStore.getStickerById(gifMatch[1])) continue; + + const stickerName = StickerStore.getStickerById(gifMatch[1])?.name ?? "FakeNitroSticker"; + stickers.push({ + format_type: 2, + id: gifMatch[1], + name: stickerName, + fake: true + }); + } + } + + return stickers; + }, + + shouldIgnoreEmbed(embed: Message["embeds"][number], message: Message) { + if (message.content.split(/\s/).length > 1 && !settings.store.transformCompoundSentence) return false; + + switch (embed.type) { + case "image": { + if (settings.store.transformEmojis) { + if (fakeNitroEmojiRegex.test(embed.url!)) return true; + } + + if (settings.store.transformStickers) { + if (fakeNitroStickerRegex.test(embed.url!)) return true; + + const gifMatch = embed.url!.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 StickerStore contains the id of the fake sticker + if (StickerStore.getStickerById(gifMatch[1])) return true; + } + } + + break; + } + } + + return false; + }, + + filterAttachments(attachments: Message["attachments"]) { + return attachments.filter(attachment => { + if (attachment.content_type !== "image/gif") return true; + + const match = attachment.url.match(fakeNitroGifStickerRegex); + if (match) { + // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker + if (StickerStore.getStickerById(match[1])) return false; + } + + return true; + }); + }, + + shouldKeepEmojiLink(link: any) { + return link.target && fakeNitroEmojiRegex.test(link.target); + }, + + addFakeNotice(type: "STICKER" | "EMOJI", node: Array, fake: boolean) { + if (!fake) return node; + + node = Array.isArray(node) ? node : [node]; + + switch (type) { + case "STICKER": { + node.push(" This is a FakeNitro sticker and renders like a real sticker only for you. Appears as a link to non-plugin users."); + + return node; + } + case "EMOJI": { + node.push(" This is a FakeNitro emoji and renders like a real emoji only for you. Appears as a link to non-plugin users."); + + return node; + } + } + }, + + hasPermissionToUseExternalEmojis(channelId: string): boolean { + const channel = ChannelStore.getChannel(channelId); + + if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true; + + return PermissionStore.can(USE_EXTERNAL_EMOJIS, channel); + }, + + hasPermissionToUseExternalStickers(channelId: string) { + const channel = ChannelStore.getChannel(channelId); + + if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true; + + return PermissionStore.can(USE_EXTERNAL_STICKERS, channel); + }, + + getStickerLink(stickerId: string) { + return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`; + }, + + async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { + const [{ parseURL }, { + GIFEncoder, + quantize, + applyPalette + }] = await Promise.all([importApngJs(), getGifEncoder()]); + + const { frames, width, height } = await parseURL(stickerLink); + + const gif = new GIFEncoder(); + const resolution = Settings.plugins.FakeNitro.stickerSize; + + const canvas = document.createElement("canvas"); + canvas.width = resolution; + canvas.height = resolution; + + const ctx = canvas.getContext("2d", { + willReadFrequently: true + })!; + + const scale = resolution / Math.max(width, height); + ctx.scale(scale, scale); + + let previousFrameData: ImageData; + + for (const frame of frames) { + const { left, top, width, height, img, delay, blendOp, disposeOp } = frame; + + previousFrameData = ctx.getImageData(left, top, width, height); + + if (blendOp === ApngBlendOp.SOURCE) { + ctx.clearRect(left, top, width, height); + } + + ctx.drawImage(img, left, top, width, height); + + const { data } = ctx.getImageData(0, 0, resolution, resolution); + + const palette = quantize(data, 256); + const index = applyPalette(data, palette); + + gif.writeFrame(index, resolution, resolution, { + transparent: true, + palette, + delay + }); + + if (disposeOp === ApngDisposeOp.BACKGROUND) { + ctx.clearRect(left, top, width, height); + } else if (disposeOp === ApngDisposeOp.PREVIOUS) { + ctx.putImageData(previousFrameData, left, top); + } + } + + gif.finish(); + + const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" }); + promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE); + }, + + start() { + const s = settings.store; + + if (!s.enableEmojiBypass && !s.enableStickerBypass) { + return; + } + + function getWordBoundary(origStr: string, offset: number) { + return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " "; + } + + this.preSend = addPreSendListener((channelId, messageObj, extra) => { + const { guildId } = this; + + stickerBypass: { + if (!s.enableStickerBypass) + break stickerBypass; + + const sticker = StickerStore.getStickerById(extra.stickers?.[0]!); + if (!sticker) + break stickerBypass; + + // Discord Stickers are now free yayyy!! :D + if ("pack_id" in sticker) + break stickerBypass; + + const canUseStickers = this.canUseStickers && this.hasPermissionToUseExternalStickers(channelId); + if (sticker.available !== false && (canUseStickers || sticker.guild_id === guildId)) + break stickerBypass; + + const link = this.getStickerLink(sticker.id); + if (sticker.format_type === StickerType.APNG) { + this.sendAnimatedSticker(link, sticker.id, channelId); + return { cancel: true }; + } else { + extra.stickers!.length = 0; + messageObj.content += ` ${link}&name=${encodeURIComponent(sticker.name)}`; + } + } + + if (s.enableEmojiBypass) { + const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId); + + for (const emoji of messageObj.validNonShortcutEmojis) { + if (!emoji.require_colons) continue; + if (emoji.available !== false && canUseEmotes) continue; + if (emoji.guildId === guildId && !emoji.animated) continue; + + const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`; + const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({ + size: Settings.plugins.FakeNitro.emojiSize, + name: encodeURIComponent(emoji.name) + })); + messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { + return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`; + }); + } + } + + return { cancel: false }; + }); + + this.preEdit = addPreEditListener((channelId, __, messageObj) => { + if (!s.enableEmojiBypass) return; + + const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId); + + const { guildId } = this; + + messageObj.content = messageObj.content.replace(/(?/ig, (emojiStr, emojiId, offset, origStr) => { + const emoji = EmojiStore.getCustomEmojiById(emojiId); + if (emoji == null) return emojiStr; + if (!emoji.require_colons) return emojiStr; + if (emoji.available !== false && canUseEmotes) return emojiStr; + if (emoji.guildId === guildId && !emoji.animated) return emojiStr; + + const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({ + size: Settings.plugins.FakeNitro.emojiSize, + name: encodeURIComponent(emoji.name) + })); + return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + emojiStr.length)}`; + }); + }); + }, + + stop() { + removePreSendListener(this.preSend); + removePreEditListener(this.preEdit); + } +}); diff --git a/src/plugins/fakeNitro.tsx b/src/plugins/fakeNitro.tsx deleted file mode 100644 index 88b2ae2..0000000 --- a/src/plugins/fakeNitro.tsx +++ /dev/null @@ -1,713 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; -import { definePluginSettings, Settings } from "@api/Settings"; -import { Devs } from "@utils/constants"; -import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies"; -import { getCurrentGuild } from "@utils/discord"; -import { proxyLazy } from "@utils/lazy"; -import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; -import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common"; -import type { Message } from "discord-types/general"; -import type { ReactNode } from "react"; - -const DRAFT_TYPE = 0; -const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR"); -const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore"); -const PreloadedUserSettingsProtoHandler = findLazy(m => m.ProtoClass?.typeName === "discord_protos.discord_users.v1.PreloadedUserSettings"); -const ReaderFactory = findByPropsLazy("readerFactory"); -const StickerStore = findStoreLazy("StickersStore") as { - getPremiumPacks(): StickerPack[]; - getAllGuildStickers(): Map; - getStickerById(id: string): Sticker | undefined; -}; - -function searchProtoClass(localName: string, parentProtoClass: any) { - if (!parentProtoClass) return; - - const field = parentProtoClass.fields.find(field => field.localName === localName); - if (!field) return; - - const getter: any = Object.values(field).find(value => typeof value === "function"); - return getter?.(); -} - -const AppearanceSettingsProto = proxyLazy(() => searchProtoClass("appearance", PreloadedUserSettingsProtoHandler.ProtoClass)); -const ClientThemeSettingsProto = proxyLazy(() => searchProtoClass("clientThemeSettings", AppearanceSettingsProto)); - -const USE_EXTERNAL_EMOJIS = 1n << 18n; -const USE_EXTERNAL_STICKERS = 1n << 37n; - -enum EmojiIntentions { - REACTION = 0, - STATUS = 1, - COMMUNITY_CONTENT = 2, - CHAT = 3, - GUILD_STICKER_RELATED_EMOJI = 4, - GUILD_ROLE_BENEFIT_EMOJI = 5, - COMMUNITY_CONTENT_ONLY = 6, - SOUNDBOARD = 7 -} - -interface BaseSticker { - available: boolean; - description: string; - format_type: number; - id: string; - name: string; - tags: string; - type: number; -} -interface GuildSticker extends BaseSticker { - guild_id: string; -} -interface DiscordSticker extends BaseSticker { - pack_id: string; -} -type Sticker = GuildSticker | DiscordSticker; - -interface StickerPack { - id: string; - name: string; - sku_id: string; - description: string; - cover_sticker_id: string; - banner_asset_id: string; - stickers: Sticker[]; -} - -const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/; -const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./; -const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/; - -const settings = definePluginSettings({ - enableEmojiBypass: { - description: "Allow sending fake emojis", - type: OptionType.BOOLEAN, - default: true, - restartNeeded: true - }, - emojiSize: { - description: "Size of the emojis when sending", - type: OptionType.SLIDER, - default: 48, - markers: [32, 48, 64, 128, 160, 256, 512] - }, - transformEmojis: { - description: "Whether to transform fake emojis into real ones", - type: OptionType.BOOLEAN, - default: true, - restartNeeded: true - }, - enableStickerBypass: { - description: "Allow sending fake stickers", - type: OptionType.BOOLEAN, - default: true, - restartNeeded: true - }, - stickerSize: { - description: "Size of the stickers when sending", - type: OptionType.SLIDER, - default: 160, - markers: [32, 64, 128, 160, 256, 512] - }, - transformStickers: { - description: "Whether to transform fake stickers into real ones", - type: OptionType.BOOLEAN, - default: true, - restartNeeded: true - }, - transformCompoundSentence: { - description: "Whether to transform fake stickers and emojis in compound sentences (sentences with more content than just the fake emoji or sticker link)", - type: OptionType.BOOLEAN, - default: false - }, - enableStreamQualityBypass: { - description: "Allow streaming in nitro quality", - type: OptionType.BOOLEAN, - default: true, - restartNeeded: true - } -}); - -export default definePlugin({ - name: "FakeNitro", - authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.obscurity, Devs.captain, Devs.Nuckyz, Devs.AutumnVN], - description: "Allows you to stream in nitro quality, send fake emojis/stickers and use client themes.", - dependencies: ["MessageEventsAPI"], - - settings, - - patches: [ - { - find: ".PREMIUM_LOCKED;", - predicate: () => settings.store.enableEmojiBypass, - replacement: [ - { - match: /(?<=(\i)=\i\.intention)/, - replace: (_, intention) => `,fakeNitroIntention=${intention}` - }, - { - match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g, - replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0' - }, - { - match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, - replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))` - } - ] - }, - { - find: "canUseAnimatedEmojis:function", - predicate: () => settings.store.enableEmojiBypass, - replacement: { - match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g, - replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` - } - }, - { - find: "canUseStickersEverywhere:function", - predicate: () => settings.store.enableStickerBypass, - replacement: { - match: /canUseStickersEverywhere:function\(\i\){/, - replace: "$&return true;" - }, - }, - { - find: "\"SENDABLE\"", - predicate: () => settings.store.enableStickerBypass, - replacement: { - match: /(\w+)\.available\?/, - replace: "true?" - } - }, - { - find: "canStreamHighQuality:function", - predicate: () => settings.store.enableStreamQualityBypass, - replacement: [ - "canUseHighVideoUploadQuality", - "canStreamHighQuality", - "canStreamMidQuality" - ].map(func => { - return { - match: new RegExp(`${func}:function\\(\\i\\){`), - replace: "$&return true;" - }; - }) - }, - { - find: "STREAM_FPS_OPTION.format", - predicate: () => settings.store.enableStreamQualityBypass, - replacement: { - match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g, - replace: "" - } - }, - { - find: "canUseClientThemes:function", - replacement: { - match: /canUseClientThemes:function\(\i\){/, - replace: "$&return true;" - } - }, - { - find: '.displayName="UserSettingsProtoStore"', - replacement: [ - { - match: /CONNECTION_OPEN:function\((\i)\){/, - replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);` - }, - { - match: /=(\i)\.local;/, - replace: (m, props) => `${m}${props}.local||$self.handleProtoChange(${props}.settings.proto);` - } - ] - }, - { - find: "updateTheme:function", - replacement: { - match: /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)(\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\))/, - replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});` - } - }, - { - find: '["strong","em","u","text","inlineCode","s","spoiler"]', - replacement: [ - { - predicate: () => settings.store.transformEmojis, - match: /1!==(\i)\.length\|\|1!==\i\.length/, - replace: (m, content) => `${m}||$self.shouldKeepEmojiLink(${content}[0])` - }, - { - predicate: () => settings.store.transformEmojis || settings.store.transformStickers, - match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/, - replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);` - } - ] - }, - { - find: "renderEmbeds=function", - replacement: [ - { - predicate: () => settings.store.transformEmojis || settings.store.transformStickers, - match: /(renderEmbeds=function\((\i)\){)(.+?embeds\.map\(\(function\((\i)\){)/, - replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;` - }, - { - predicate: () => settings.store.transformStickers, - match: /renderStickersAccessories=function\((\i)\){var (\i)=\(0,\i\.\i\)\(\i\),/, - replace: (m, message, stickers) => `${m}${stickers}=$self.patchFakeNitroStickers(${stickers},${message}),` - }, - { - predicate: () => settings.store.transformStickers, - match: /renderAttachments=function\(\i\){var (\i)=\i.attachments.+?;/, - replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});` - } - ] - }, - { - find: ".STICKER_IN_MESSAGE_HOVER,", - predicate: () => settings.store.transformStickers, - replacement: [ - { - match: /var (\i)=\i\.renderableSticker,.{0,50}closePopout.+?channel:\i,closePopout:\i,/, - replace: (m, renderableSticker) => `${m}renderableSticker:${renderableSticker},` - }, - { - match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/, - replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice("STICKER",${reactNode},!!${props}.renderableSticker?.fake)` - } - ] - }, - { - find: ".Messages.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION", - predicate: () => settings.store.transformEmojis, - replacement: { - match: /((\i)=\i\.node,\i=\i\.emojiSourceDiscoverableGuild)(.+?return )(.{0,450}Messages\.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION.+?}\))/, - replace: (_, rest1, node, rest2, reactNode) => `${rest1},fakeNitroNode=${node}${rest2}$self.addFakeNotice("EMOJI",${reactNode},fakeNitroNode.fake)` - } - } - ], - - get guildId() { - return getCurrentGuild()?.id; - }, - - get canUseEmotes() { - return (UserStore.getCurrentUser().premiumType ?? 0) > 0; - }, - - get canUseStickers() { - return (UserStore.getCurrentUser().premiumType ?? 0) > 1; - }, - - handleProtoChange(proto: any, user: any) { - if (proto == null || typeof proto === "string" || !UserSettingsProtoStore || (!proto.appearance && !AppearanceSettingsProto)) return; - - const premiumType: number = user?.premium_type ?? UserStore?.getCurrentUser()?.premiumType ?? 0; - - if (premiumType !== 2) { - proto.appearance ??= AppearanceSettingsProto.create(); - - if (UserSettingsProtoStore.settings.appearance?.theme != null) { - proto.appearance.theme = UserSettingsProtoStore.settings.appearance.theme; - } - - if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null && ClientThemeSettingsProto) { - const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({ - backgroundGradientPresetId: { - value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value - } - }); - - proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto; - proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId; - } - } - }, - - handleGradientThemeSelect(backgroundGradientPresetId: number | undefined, theme: number, original: () => void) { - const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0; - if (premiumType === 2 || backgroundGradientPresetId == null) return original(); - - if (!AppearanceSettingsProto || !ClientThemeSettingsProto || !ReaderFactory) return; - - const currentAppearanceProto = PreloadedUserSettingsProtoHandler.getCurrentValue().appearance; - - const newAppearanceProto = currentAppearanceProto != null - ? AppearanceSettingsProto.fromBinary(AppearanceSettingsProto.toBinary(currentAppearanceProto), ReaderFactory) - : AppearanceSettingsProto.create(); - - newAppearanceProto.theme = theme; - - const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({ - backgroundGradientPresetId: { - value: backgroundGradientPresetId - } - }); - - newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummyProto; - newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId; - - const proto = PreloadedUserSettingsProtoHandler.ProtoClass.create(); - proto.appearance = newAppearanceProto; - - FluxDispatcher.dispatch({ - type: "USER_SETTINGS_PROTO_UPDATE", - local: true, - partial: true, - settings: { - type: 1, - proto - } - }); - }, - - patchFakeNitroEmojisOrRemoveStickersLinks(content: Array, inline: boolean) { - if (content.length > 1 && !settings.store.transformCompoundSentence) return content; - - const newContent: Array = []; - - let nextIndex = content.length; - - for (const element of content) { - if (element.props?.trusted == null) { - newContent.push(element); - continue; - } - - if (settings.store.transformEmojis) { - const fakeNitroMatch = element.props.href.match(fakeNitroEmojiRegex); - if (fakeNitroMatch) { - let url: URL | null = null; - try { - url = new URL(element.props.href); - } catch { } - - const emojiName = EmojiStore.getCustomEmojiById(fakeNitroMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroEmoji"; - - newContent.push(Parser.defaultRules.customEmoji.react({ - jumboable: !inline && content.length === 1, - animated: fakeNitroMatch[2] === "gif", - emojiId: fakeNitroMatch[1], - name: emojiName, - fake: true - }, void 0, { key: String(nextIndex++) })); - - continue; - } - } - - if (settings.store.transformStickers) { - if (fakeNitroStickerRegex.test(element.props.href)) continue; - - const gifMatch = element.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 StickerStore contains the id of the fake sticker - if (StickerStore.getStickerById(gifMatch[1])) continue; - } - } - - newContent.push(element); - } - - const firstContent = newContent[0]; - if (typeof firstContent === "string") newContent[0] = firstContent.trimStart(); - - return newContent; - }, - - patchFakeNitroStickers(stickers: Array, message: Message) { - const itemsToMaybePush: Array = []; - - const contentItems = message.content.split(/\s/); - if (contentItems.length === 1 && !settings.store.transformCompoundSentence) itemsToMaybePush.push(contentItems[0]); - else itemsToMaybePush.push(...contentItems); - - itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url)); - - for (const item of itemsToMaybePush) { - const imgMatch = item.match(fakeNitroStickerRegex); - if (imgMatch) { - let url: URL | null = null; - try { - url = new URL(item); - } catch { } - - const stickerName = StickerStore.getStickerById(imgMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroSticker"; - stickers.push({ - format_type: 1, - id: imgMatch[1], - name: stickerName, - fake: true - }); - - continue; - } - - const gifMatch = item.match(fakeNitroGifStickerRegex); - if (gifMatch) { - if (!StickerStore.getStickerById(gifMatch[1])) continue; - - const stickerName = StickerStore.getStickerById(gifMatch[1])?.name ?? "FakeNitroSticker"; - stickers.push({ - format_type: 2, - id: gifMatch[1], - name: stickerName, - fake: true - }); - } - } - - return stickers; - }, - - shouldIgnoreEmbed(embed: Message["embeds"][number], message: Message) { - if (message.content.split(/\s/).length > 1 && !settings.store.transformCompoundSentence) return false; - - switch (embed.type) { - case "image": { - if (settings.store.transformEmojis) { - if (fakeNitroEmojiRegex.test(embed.url!)) return true; - } - - if (settings.store.transformStickers) { - if (fakeNitroStickerRegex.test(embed.url!)) return true; - - const gifMatch = embed.url!.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 StickerStore contains the id of the fake sticker - if (StickerStore.getStickerById(gifMatch[1])) return true; - } - } - - break; - } - } - - return false; - }, - - filterAttachments(attachments: Message["attachments"]) { - return attachments.filter(attachment => { - if (attachment.content_type !== "image/gif") return true; - - const match = attachment.url.match(fakeNitroGifStickerRegex); - if (match) { - // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker - if (StickerStore.getStickerById(match[1])) return false; - } - - return true; - }); - }, - - shouldKeepEmojiLink(link: any) { - return link.target && fakeNitroEmojiRegex.test(link.target); - }, - - addFakeNotice(type: "STICKER" | "EMOJI", node: Array, fake: boolean) { - if (!fake) return node; - - node = Array.isArray(node) ? node : [node]; - - switch (type) { - case "STICKER": { - node.push(" This is a FakeNitro sticker and renders like a real sticker only for you. Appears as a link to non-plugin users."); - - return node; - } - case "EMOJI": { - node.push(" This is a FakeNitro emoji and renders like a real emoji only for you. Appears as a link to non-plugin users."); - - return node; - } - } - }, - - hasPermissionToUseExternalEmojis(channelId: string) { - const channel = ChannelStore.getChannel(channelId); - - if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true; - - return PermissionStore.can(USE_EXTERNAL_EMOJIS, channel); - }, - - hasPermissionToUseExternalStickers(channelId: string) { - const channel = ChannelStore.getChannel(channelId); - - if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true; - - return PermissionStore.can(USE_EXTERNAL_STICKERS, channel); - }, - - getStickerLink(stickerId: string) { - return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`; - }, - - async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { - const [{ parseURL }, { - GIFEncoder, - quantize, - applyPalette - }] = await Promise.all([importApngJs(), getGifEncoder()]); - - const { frames, width, height } = await parseURL(stickerLink); - - const gif = new GIFEncoder(); - const resolution = Settings.plugins.FakeNitro.stickerSize; - - const canvas = document.createElement("canvas"); - canvas.width = resolution; - canvas.height = resolution; - - const ctx = canvas.getContext("2d", { - willReadFrequently: true - })!; - - const scale = resolution / Math.max(width, height); - ctx.scale(scale, scale); - - let previousFrameData: ImageData; - - for (const frame of frames) { - const { left, top, width, height, img, delay, blendOp, disposeOp } = frame; - - previousFrameData = ctx.getImageData(left, top, width, height); - - if (blendOp === ApngBlendOp.SOURCE) { - ctx.clearRect(left, top, width, height); - } - - ctx.drawImage(img, left, top, width, height); - - const { data } = ctx.getImageData(0, 0, resolution, resolution); - - const palette = quantize(data, 256); - const index = applyPalette(data, palette); - - gif.writeFrame(index, resolution, resolution, { - transparent: true, - palette, - delay - }); - - if (disposeOp === ApngDisposeOp.BACKGROUND) { - ctx.clearRect(left, top, width, height); - } else if (disposeOp === ApngDisposeOp.PREVIOUS) { - ctx.putImageData(previousFrameData, left, top); - } - } - - gif.finish(); - - const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" }); - promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE); - }, - - start() { - const settings = Settings.plugins.FakeNitro; - if (!settings.enableEmojiBypass && !settings.enableStickerBypass) { - return; - } - - function getWordBoundary(origStr: string, offset: number) { - return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " "; - } - - this.preSend = addPreSendListener((channelId, messageObj, extra) => { - const { guildId } = this; - - stickerBypass: { - if (!settings.enableStickerBypass) - break stickerBypass; - - const sticker = StickerStore.getStickerById(extra.stickers?.[0]!); - if (!sticker) - break stickerBypass; - - if (sticker.available !== false && ((this.canUseStickers && this.hasPermissionToUseExternalStickers(channelId)) || (sticker as GuildSticker)?.guild_id === guildId)) - break stickerBypass; - - let link = this.getStickerLink(sticker.id); - if (sticker.format_type === 2) { - this.sendAnimatedSticker(link, sticker.id, channelId); - return { cancel: true }; - } else { - if ("pack_id" in sticker) { - const packId = sticker.pack_id === "847199849233514549" - // Discord moved these stickers into a different pack at some point, but - // Distok still uses the old id - ? "749043879713701898" - : sticker.pack_id; - - link = `https://distok.top/stickers/${packId}/${sticker.id}.gif`; - } - - extra.stickers!.length = 0; - messageObj.content += " " + link + `&name=${encodeURIComponent(sticker.name)}`; - } - } - - if ((!this.canUseEmotes || !this.hasPermissionToUseExternalEmojis(channelId)) && settings.enableEmojiBypass) { - for (const emoji of messageObj.validNonShortcutEmojis) { - if (!emoji.require_colons) continue; - if (emoji.guildId === guildId && !emoji.animated) continue; - - const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`; - const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({ - size: Settings.plugins.FakeNitro.emojiSize, - name: encodeURIComponent(emoji.name) - })); - messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { - return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`; - }); - } - } - - return { cancel: false }; - }); - - this.preEdit = addPreEditListener((channelId, __, messageObj) => { - if (this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId)) return; - - const { guildId } = this; - - for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?/ig)) { - const emoji = EmojiStore.getCustomEmojiById(emojiId); - if (emoji == null || (emoji.guildId === guildId && !emoji.animated)) continue; - if (!emoji.require_colons) continue; - - const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({ - size: Settings.plugins.FakeNitro.emojiSize, - name: encodeURIComponent(emoji.name) - })); - messageObj.content = messageObj.content.replace(emojiStr, (match, offset, origStr) => { - return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`; - }); - } - }); - }, - - stop() { - removePreSendListener(this.preSend); - removePreEditListener(this.preEdit); - } -}); -- cgit