feat(nitroBypass): add sticker bypass (#184)
Co-authored-by: Vendicated <vendicated@riseup.net>
This commit is contained in:
		
							parent
							
								
									d69dfd6205
								
							
						
					
					
						commit
						7d5ade21fc
					
				
					 6 changed files with 307 additions and 55 deletions
				
			
		| 
						 | 
				
			
			@ -37,25 +37,33 @@ export interface MessageObject {
 | 
			
		|||
    validNonShortcutEmojis: Emoji[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: any) => void;
 | 
			
		||||
export interface MessageExtra {
 | 
			
		||||
    stickerIds?: string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => void | { cancel: boolean };
 | 
			
		||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => void;
 | 
			
		||||
 | 
			
		||||
const sendListeners = new Set<SendListener>();
 | 
			
		||||
const editListeners = new Set<EditListener>();
 | 
			
		||||
 | 
			
		||||
export function _handlePreSend(channelId: string, messageObj: MessageObject, extra: any) {
 | 
			
		||||
export function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra) {
 | 
			
		||||
    for (const listener of sendListeners) {
 | 
			
		||||
        try {
 | 
			
		||||
            listener(channelId, messageObj, extra);
 | 
			
		||||
        } catch (e) { MessageEventsLogger.error(`MessageSendHandler: Listener encoutered an unknown error. (${e})`); }
 | 
			
		||||
            const result = listener(channelId, messageObj, extra);
 | 
			
		||||
            if (result && result.cancel === true) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e) { MessageEventsLogger.error("MessageSendHandler: Listener encountered an unknown error\n", e); }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function _handlePreEdit(channeld: string, messageId: string, messageObj: MessageObject) {
 | 
			
		||||
export function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) {
 | 
			
		||||
    for (const listener of editListeners) {
 | 
			
		||||
        try {
 | 
			
		||||
            listener(channeld, messageId, messageObj);
 | 
			
		||||
        } catch (e) { MessageEventsLogger.error(`MessageEditHandler: Listener encoutered an unknown error. (${e})`); }
 | 
			
		||||
            listener(channelId, messageId, messageObj);
 | 
			
		||||
        } catch (e) { MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e); }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -90,7 +98,7 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
 | 
			
		|||
    for (const listener of listeners) {
 | 
			
		||||
        try {
 | 
			
		||||
            listener(message, channel, event);
 | 
			
		||||
        } catch (e) { MessageEventsLogger.error(`MessageClickHandler: Listener encoutered an unknown error. (${e})`); }
 | 
			
		||||
        } catch (e) { MessageEventsLogger.error("MessageClickHandler: Listener encountered an unknown error\n", e); }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								src/globals.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/globals.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -21,7 +21,7 @@ declare global {
 | 
			
		|||
    /**
 | 
			
		||||
     * This exists only at build time, so references to it in patches should insert it
 | 
			
		||||
     * via String interpolation OR use different replacement code based on this
 | 
			
		||||
     * but NEVER refrence it inside the patched code
 | 
			
		||||
     * but NEVER reference it inside the patched code
 | 
			
		||||
     *
 | 
			
		||||
     * @example
 | 
			
		||||
     * // BAD
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ export default definePlugin({
 | 
			
		|||
            find: "sendMessage:function",
 | 
			
		||||
            replacement: [{
 | 
			
		||||
                match: /(?<=_sendMessage:function\([^)]+\)){/,
 | 
			
		||||
                replace: "{Vencord.Api.MessageEvents._handlePreSend(...arguments);"
 | 
			
		||||
                replace: "{if(Vencord.Api.MessageEvents._handlePreSend(...arguments)){return;};"
 | 
			
		||||
            }, {
 | 
			
		||||
                match: /(?<=\beditMessage:function\([^)]+\)){/,
 | 
			
		||||
                replace: "{Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,18 +16,50 @@
 | 
			
		|||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import { addPreEditListener, addPreSendListener, removePreEditListener,removePreSendListener } from "../api/MessageEvents";
 | 
			
		||||
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "../api/MessageEvents";
 | 
			
		||||
import { lazyWebpack } from "../utils";
 | 
			
		||||
import { Devs } from "../utils/constants";
 | 
			
		||||
import { ApngDisposeOp, getGifEncoder, importApngJs } from "../utils/dependencies";
 | 
			
		||||
import definePlugin, { OptionType } from "../utils/types";
 | 
			
		||||
import { Settings } from "../Vencord";
 | 
			
		||||
import { findByProps } from "../webpack";
 | 
			
		||||
import { UserStore } from "../webpack/common";
 | 
			
		||||
import { filters } from "../webpack";
 | 
			
		||||
import { ChannelStore, UserStore } from "../webpack/common";
 | 
			
		||||
 | 
			
		||||
const DRAFT_TYPE = 0;
 | 
			
		||||
const promptToUpload = lazyWebpack(filters.byCode("UPLOAD_FILE_LIMIT_ERROR"));
 | 
			
		||||
 | 
			
		||||
interface Sticker {
 | 
			
		||||
    available: boolean;
 | 
			
		||||
    description: string;
 | 
			
		||||
    format_type: number;
 | 
			
		||||
    guild_id: string;
 | 
			
		||||
    id: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    tags: string;
 | 
			
		||||
    type: number;
 | 
			
		||||
    _notAvailable?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface StickerPack {
 | 
			
		||||
    id: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    sku_id: string;
 | 
			
		||||
    description: string;
 | 
			
		||||
    cover_sticker_id: string;
 | 
			
		||||
    banner_asset_id: string;
 | 
			
		||||
    stickers: Sticker[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default definePlugin({
 | 
			
		||||
    name: "NitroBypass",
 | 
			
		||||
    authors: [Devs.Arjix],
 | 
			
		||||
    description: "Allows you to stream in nitro quality and send fake emojis.",
 | 
			
		||||
    authors: [
 | 
			
		||||
        Devs.Arjix,
 | 
			
		||||
        Devs.D3SOX,
 | 
			
		||||
        Devs.Ven
 | 
			
		||||
    ],
 | 
			
		||||
    description: "Allows you to stream in nitro quality and send fake emojis/stickers.",
 | 
			
		||||
    dependencies: ["MessageEventsAPI"],
 | 
			
		||||
 | 
			
		||||
    patches: [
 | 
			
		||||
        {
 | 
			
		||||
            find: "canUseAnimatedEmojis:function",
 | 
			
		||||
| 
						 | 
				
			
			@ -38,10 +70,25 @@ export default definePlugin({
 | 
			
		|||
            ].map(func => {
 | 
			
		||||
                return {
 | 
			
		||||
                    match: new RegExp(`${func}:function\\(.+?}`),
 | 
			
		||||
                    replace: `${func}:function (e) { return true; }`
 | 
			
		||||
                    replace: `${func}:function(e){return true;}`
 | 
			
		||||
                };
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            find: "canUseAnimatedEmojis:function",
 | 
			
		||||
            predicate: () => Settings.plugins.NitroBypass.enableStickerBypass === true,
 | 
			
		||||
            replacement: {
 | 
			
		||||
                match: /canUseStickersEverywhere:function\(.+?}/,
 | 
			
		||||
                replace: "canUseStickersEverywhere:function(e){return true;}"
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            find: "\"SENDABLE\"",
 | 
			
		||||
            replacement: {
 | 
			
		||||
                match: /(\w+)\.available\?/,
 | 
			
		||||
                replace: "true?"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            find: "canUseAnimatedEmojis:function",
 | 
			
		||||
            predicate: () => Settings.plugins.NitroBypass.enableStreamQualityBypass === true,
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +99,7 @@ export default definePlugin({
 | 
			
		|||
            ].map(func => {
 | 
			
		||||
                return {
 | 
			
		||||
                    match: new RegExp(`${func}:function\\(.+?}`),
 | 
			
		||||
                    replace: `${func}:function (e) { return true; }`
 | 
			
		||||
                    replace: `${func}:function(e){return true;}`
 | 
			
		||||
                };
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -63,8 +110,9 @@ export default definePlugin({
 | 
			
		|||
                match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g,
 | 
			
		||||
                replace: ""
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    options: {
 | 
			
		||||
        enableEmojiBypass: {
 | 
			
		||||
            description: "Allow sending fake emojis",
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +120,18 @@ export default definePlugin({
 | 
			
		|||
            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],
 | 
			
		||||
        },
 | 
			
		||||
        enableStreamQualityBypass: {
 | 
			
		||||
            description: "Allow streaming in nitro quality",
 | 
			
		||||
            type: OptionType.BOOLEAN,
 | 
			
		||||
| 
						 | 
				
			
			@ -88,50 +148,162 @@ export default definePlugin({
 | 
			
		|||
        return Boolean(UserStore.getCurrentUser().premiumType);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getStickerLink(stickerId: string) {
 | 
			
		||||
        return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.NitroBypass.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.NitroBypass.stickerSize;
 | 
			
		||||
 | 
			
		||||
        const canvas = document.createElement("canvas");
 | 
			
		||||
        canvas.width = width;
 | 
			
		||||
        canvas.height = height;
 | 
			
		||||
 | 
			
		||||
        const ctx = canvas.getContext("2d", {
 | 
			
		||||
            willReadFrequently: true
 | 
			
		||||
        })!;
 | 
			
		||||
 | 
			
		||||
        const scale = resolution / width;
 | 
			
		||||
        ctx.scale(scale, scale);
 | 
			
		||||
 | 
			
		||||
        let lastImg: HTMLImageElement | null = null;
 | 
			
		||||
        for (const { left, top, width, height, disposeOp, img, delay } of frames) {
 | 
			
		||||
            if (disposeOp === ApngDisposeOp.BACKGROUND) {
 | 
			
		||||
                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.PREVIOUS && lastImg) {
 | 
			
		||||
                ctx.drawImage(lastImg, left, top, width, height);
 | 
			
		||||
            }
 | 
			
		||||
            lastImg = img;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        gif.finish();
 | 
			
		||||
        const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" });
 | 
			
		||||
        promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    start() {
 | 
			
		||||
        if (!Settings.plugins.NitroBypass.enableEmojiBypass) {
 | 
			
		||||
        const settings = Settings.plugins.NitroBypass;
 | 
			
		||||
        if (!settings.enableEmojiBypass && !settings.enableStickerBypass) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.canUseEmotes) {
 | 
			
		||||
            console.info("[NitroBypass] Skipping start because you have nitro");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const EmojiStore = lazyWebpack(filters.byProps("getCustomEmojiById"));
 | 
			
		||||
        const StickerStore = lazyWebpack(filters.byProps("getAllGuildStickers")) as {
 | 
			
		||||
            getPremiumPacks(): StickerPack[];
 | 
			
		||||
            getAllGuildStickers(): Map<string, Sticker[]>;
 | 
			
		||||
            getStickerById(id: string): Sticker | undefined;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const { getCustomEmojiById } = findByProps("getCustomEmojiById");
 | 
			
		||||
 | 
			
		||||
        function getWordBoundary(origStr, offset) {
 | 
			
		||||
        function getWordBoundary(origStr: string, offset: number) {
 | 
			
		||||
            return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " ";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.preSend = addPreSendListener((_, messageObj) => {
 | 
			
		||||
            const { guildId } = this;
 | 
			
		||||
            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=[0-9]+/, "?size=48");
 | 
			
		||||
                messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
 | 
			
		||||
                    return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.preEdit = addPreEditListener((_, __, messageObj) => {
 | 
			
		||||
        this.preSend = addPreSendListener((channelId, messageObj, extra) => {
 | 
			
		||||
            const { guildId } = this;
 | 
			
		||||
 | 
			
		||||
            for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) {
 | 
			
		||||
                const emoji = getCustomEmojiById(emojiId);
 | 
			
		||||
                if (emoji == null || (emoji.guildId === guildId && !emoji.animated)) continue;
 | 
			
		||||
                if (!emoji.require_colons) continue;
 | 
			
		||||
            if (settings.enableStickerBypass) {
 | 
			
		||||
                const stickerId = extra?.stickerIds?.[0];
 | 
			
		||||
 | 
			
		||||
                const url = emoji.url.replace(/\?size=[0-9]+/, "?size=48");
 | 
			
		||||
                messageObj.content = messageObj.content.replace(emojiStr, (match, offset, origStr) => {
 | 
			
		||||
                    return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
 | 
			
		||||
                });
 | 
			
		||||
                if (stickerId) {
 | 
			
		||||
                    let stickerLink = this.getStickerLink(stickerId);
 | 
			
		||||
                    let sticker: Sticker | undefined;
 | 
			
		||||
 | 
			
		||||
                    const discordStickerPack = StickerStore.getPremiumPacks().find(pack => {
 | 
			
		||||
                        const discordSticker = pack.stickers.find(sticker => sticker.id === stickerId);
 | 
			
		||||
                        if (discordSticker) {
 | 
			
		||||
                            sticker = discordSticker;
 | 
			
		||||
                        }
 | 
			
		||||
                        return discordSticker;
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    if (discordStickerPack) {
 | 
			
		||||
                        // discord stickers provided by the Distok project
 | 
			
		||||
                        stickerLink = `https://distok.top/stickers/${discordStickerPack.id}/${stickerId}.gif`;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // guild stickers
 | 
			
		||||
                        sticker = StickerStore.getStickerById(stickerId);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (sticker) {
 | 
			
		||||
                        // when the user has Nitro and the sticker is available, send the sticker normally
 | 
			
		||||
                        if (this.canUseEmotes && sticker.available) {
 | 
			
		||||
                            return { cancel: false };
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // only modify if sticker is not from current guild
 | 
			
		||||
                        if (sticker.guild_id !== guildId) {
 | 
			
		||||
                            // if it's an animated guild sticker, download it, convert to gif and send it
 | 
			
		||||
                            const isAnimated = sticker.format_type === 2;
 | 
			
		||||
                            if (!discordStickerPack && isAnimated) {
 | 
			
		||||
                                this.sendAnimatedSticker(stickerLink, stickerId, channelId);
 | 
			
		||||
                                return { cancel: true };
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            if (messageObj.content)
 | 
			
		||||
                                messageObj.content += " ";
 | 
			
		||||
                            messageObj.content += stickerLink;
 | 
			
		||||
 | 
			
		||||
                            delete extra.stickerIds;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!this.canUseEmotes && 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+/, "?size=48");
 | 
			
		||||
                    messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
 | 
			
		||||
                        return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return { cancel: false };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (!this.canUseEmotes && settings.enableEmojiBypass) {
 | 
			
		||||
            this.preEdit = addPreEditListener((_, __, messageObj) => {
 | 
			
		||||
                const { guildId } = this;
 | 
			
		||||
 | 
			
		||||
                for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/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+/, "?size=48");
 | 
			
		||||
                    messageObj.content = messageObj.content.replace(emojiStr, (match, offset, origStr) => {
 | 
			
		||||
                        return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    stop() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,8 +17,9 @@
 | 
			
		|||
*/
 | 
			
		||||
 | 
			
		||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption } from "../api/Commands";
 | 
			
		||||
import { lazyWebpack, makeLazy } from "../utils";
 | 
			
		||||
import { Devs } from "../utils/constants";
 | 
			
		||||
import { lazyWebpack, makeLazy } from "../utils/misc";
 | 
			
		||||
import { getGifEncoder } from "../utils/dependencies";
 | 
			
		||||
import definePlugin from "../utils/types";
 | 
			
		||||
import { filters } from "../webpack";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,11 +28,6 @@ const DEFAULT_DELAY = 20;
 | 
			
		|||
const DEFAULT_RESOLUTION = 128;
 | 
			
		||||
const FRAMES = 10;
 | 
			
		||||
 | 
			
		||||
// https://github.com/mattdesl/gifenc
 | 
			
		||||
// this lib is way better than gif.js and all other libs, they're all so terrible but this one is nice
 | 
			
		||||
// @ts-ignore ts mad
 | 
			
		||||
const getGifEncoder = makeLazy(() => import("https://unpkg.com/gifenc@1.0.3/dist/gifenc.esm.js"));
 | 
			
		||||
 | 
			
		||||
const getFrames = makeLazy(() => Promise.all(
 | 
			
		||||
    Array.from(
 | 
			
		||||
        { length: FRAMES },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										76
									
								
								src/utils/dependencies.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/utils/dependencies.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import { makeLazy } from "./misc";
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
    Add dynamically loaded dependencies for plugins here.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// https://github.com/mattdesl/gifenc
 | 
			
		||||
// this lib is way better than gif.js and all other libs, they're all so terrible but this one is nice
 | 
			
		||||
// @ts-ignore ts mad
 | 
			
		||||
export const getGifEncoder = makeLazy(() => import("https://unpkg.com/gifenc@1.0.3/dist/gifenc.esm.js"));
 | 
			
		||||
 | 
			
		||||
// needed to parse APNGs in the nitroBypass plugin
 | 
			
		||||
export const importApngJs = makeLazy(async () => {
 | 
			
		||||
    const exports = {};
 | 
			
		||||
    const winProxy = new Proxy(window, { set: (_, k, v) => exports[k] = v });
 | 
			
		||||
    Function("self", await fetch("https://cdnjs.cloudflare.com/ajax/libs/apng-canvas/2.1.1/apng-canvas.min.js").then(r => r.text()))(winProxy);
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    return exports.APNG as { parseURL(url: string): Promise<ApngFrameData>; };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
 | 
			
		||||
export enum ApngDisposeOp {
 | 
			
		||||
    /**
 | 
			
		||||
     * no disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
 | 
			
		||||
     */
 | 
			
		||||
    NONE,
 | 
			
		||||
    /**
 | 
			
		||||
     * the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
 | 
			
		||||
     */
 | 
			
		||||
    BACKGROUND,
 | 
			
		||||
    /**
 | 
			
		||||
     * the frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
 | 
			
		||||
     */
 | 
			
		||||
    PREVIOUS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Might need to somehow implement this
 | 
			
		||||
export enum ApngBlendOp {
 | 
			
		||||
    SOURCE,
 | 
			
		||||
    OVER
 | 
			
		||||
}
 | 
			
		||||
export interface ApngFrame {
 | 
			
		||||
    left: number;
 | 
			
		||||
    top: number;
 | 
			
		||||
    width: number;
 | 
			
		||||
    height: number;
 | 
			
		||||
    img: HTMLImageElement;
 | 
			
		||||
    delay: number;
 | 
			
		||||
    blendOp: ApngBlendOp;
 | 
			
		||||
    disposeOp: ApngDisposeOp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ApngFrameData {
 | 
			
		||||
    width: number;
 | 
			
		||||
    height: number;
 | 
			
		||||
    frames: ApngFrame[];
 | 
			
		||||
    playTime: number;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue