Vencord/src/plugins/nitroBypass.ts

314 lines
12 KiB
TypeScript
Raw Normal View History

2022-10-21 23:17:06 +00:00
/*
* 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 { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "../api/MessageEvents";
import { Devs } from "../utils/constants";
import { ApngDisposeOp, getGifEncoder, importApngJs } from "../utils/dependencies";
import { lazyWebpack } from "../utils/misc";
2022-10-22 16:18:41 +00:00
import definePlugin, { OptionType } from "../utils/types";
2022-10-21 11:37:53 +00:00
import { Settings } from "../Vencord";
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[];
}
2022-08-31 18:53:36 +00:00
export default definePlugin({
2022-10-01 15:04:57 +00:00
name: "NitroBypass",
2022-11-11 22:50:09 +00:00
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven],
description: "Allows you to stream in nitro quality and send fake emojis/stickers.",
2022-08-31 18:58:21 +00:00
dependencies: ["MessageEventsAPI"],
2022-08-31 18:53:36 +00:00
patches: [
{
find: "canUseAnimatedEmojis:function",
2022-10-21 11:37:53 +00:00
predicate: () => Settings.plugins.NitroBypass.enableEmojiBypass === true,
2022-08-31 18:53:36 +00:00
replacement: [
"canUseAnimatedEmojis",
2022-10-21 11:37:53 +00:00
"canUseEmojisEverywhere"
].map(func => {
return {
match: new RegExp(`${func}:function\\(.+?}`),
replace: `${func}:function(e){return true;}`
2022-10-21 11:37:53 +00:00
};
})
},
{
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?"
}
},
2022-10-21 11:37:53 +00:00
{
find: "canUseAnimatedEmojis:function",
predicate: () => Settings.plugins.NitroBypass.enableStreamQualityBypass === true,
replacement: [
2022-10-19 10:27:20 +00:00
"canUseHighVideoUploadQuality",
"canStreamHighQuality",
"canStreamMidQuality"
2022-08-31 18:53:36 +00:00
].map(func => {
return {
match: new RegExp(`${func}:function\\(.+?}`),
replace: `${func}:function(e){return true;}`
2022-08-31 18:55:58 +00:00
};
2022-08-31 18:53:36 +00:00
})
},
2022-10-01 15:04:57 +00:00
{
find: "STREAM_FPS_OPTION.format",
2022-10-21 11:37:53 +00:00
predicate: () => Settings.plugins.NitroBypass.enableStreamQualityBypass === true,
2022-10-01 15:04:57 +00:00
replacement: {
match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g,
replace: ""
}
},
2022-08-31 18:53:36 +00:00
],
2022-10-21 11:37:53 +00:00
options: {
enableEmojiBypass: {
description: "Allow sending fake emojis",
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],
},
2022-10-21 11:37:53 +00:00
enableStreamQualityBypass: {
description: "Allow streaming in nitro quality",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true,
}
},
2022-10-01 15:04:57 +00:00
get guildId() {
return window.location.href.split("channels/")[1].split("/")[0];
},
get canUseEmotes() {
return (UserStore.getCurrentUser().premiumType ?? 0) > 0;
},
get canUseSticker() {
return (UserStore.getCurrentUser().premiumType ?? 0) > 1;
2022-10-01 15:04:57 +00:00
},
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 = resolution;
canvas.height = resolution;
const ctx = canvas.getContext("2d", {
willReadFrequently: true
})!;
const scale = resolution / Math.max(width, height);
ctx.scale(scale, scale);
let lastImg: HTMLImageElement | null = null;
for (const { left, top, width, height, disposeOp, img, delay } of frames) {
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 && lastImg) {
ctx.drawImage(lastImg, left, top, width, height);
}
lastImg = img;
2022-10-21 11:37:53 +00:00
}
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.NitroBypass;
if (!settings.enableEmojiBypass && !settings.enableStickerBypass) {
2022-10-01 15:04:57 +00:00
return;
}
2022-08-31 18:53:36 +00:00
const EmojiStore = lazyWebpack(filters.byProps("getCustomEmojiById"));
const StickerStore = lazyWebpack(filters.byProps("getAllGuildStickers")) as {
getPremiumPacks(): StickerPack[];
getAllGuildStickers(): Map<string, Sticker[]>;
getStickerById(id: string): Sticker | undefined;
};
2022-08-31 18:53:36 +00:00
function getWordBoundary(origStr: string, offset: number) {
return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " ";
}
this.preSend = addPreSendListener((channelId, messageObj, extra) => {
2022-10-08 18:36:57 +00:00
const { guildId } = this;
2022-10-01 15:04:57 +00:00
if (settings.enableStickerBypass) {
const stickerId = extra?.stickerIds?.[0];
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.canUseSticker && 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;
2022-08-31 18:53:36 +00:00
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;
2022-08-31 18:53:36 +00:00
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)}`;
});
}
2022-08-31 18:53:36 +00:00
}
return { cancel: false };
2022-08-31 18:55:58 +00:00
});
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)}`;
});
}
});
}
2022-08-31 18:53:36 +00:00
},
stop() {
removePreSendListener(this.preSend);
removePreEditListener(this.preEdit);
}
2022-08-31 18:55:58 +00:00
});