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 / > .
* /
2022-11-28 12:37:55 +00:00
import { addPreEditListener , addPreSendListener , removePreEditListener , removePreSendListener } from "@api/MessageEvents" ;
2023-05-05 23:36:00 +00:00
import { definePluginSettings , Settings } from "@api/Settings" ;
2022-11-28 12:37:55 +00:00
import { Devs } from "@utils/constants" ;
2023-04-05 03:06:04 +00:00
import { ApngBlendOp , ApngDisposeOp , getGifEncoder , importApngJs } from "@utils/dependencies" ;
2023-03-19 08:44:11 +00:00
import { getCurrentGuild } from "@utils/discord" ;
2023-05-05 23:36:00 +00:00
import { proxyLazy } from "@utils/lazy" ;
2023-05-23 03:25:48 +00:00
import { Logger } from "@utils/Logger" ;
2022-11-28 12:37:55 +00:00
import definePlugin , { OptionType } from "@utils/types" ;
2023-03-22 03:01:32 +00:00
import { findByCodeLazy , findByPropsLazy , findLazy , findStoreLazy } from "@webpack" ;
2023-05-17 02:38:15 +00:00
import { ChannelStore , EmojiStore , FluxDispatcher , Parser , PermissionStore , UserStore } from "@webpack/common" ;
2023-04-05 03:06:04 +00:00
import type { Message } from "discord-types/general" ;
2023-05-23 03:25:48 +00:00
import type { ReactElement , ReactNode } from "react" ;
2022-11-07 21:23:34 +00:00
const DRAFT_TYPE = 0 ;
2022-11-28 12:37:55 +00:00
const promptToUpload = findByCodeLazy ( "UPLOAD_FILE_LIMIT_ERROR" ) ;
2023-03-21 09:03:28 +00:00
const UserSettingsProtoStore = findStoreLazy ( "UserSettingsProtoStore" ) ;
2023-03-23 05:11:28 +00:00
const PreloadedUserSettingsProtoHandler = findLazy ( m = > m . ProtoClass ? . typeName === "discord_protos.discord_users.v1.PreloadedUserSettings" ) ;
const ReaderFactory = findByPropsLazy ( "readerFactory" ) ;
2023-04-05 03:06:04 +00:00
const StickerStore = findStoreLazy ( "StickersStore" ) as {
getPremiumPacks ( ) : StickerPack [ ] ;
getAllGuildStickers ( ) : Map < string , Sticker [ ] > ;
getStickerById ( id : string ) : Sticker | undefined ;
} ;
2023-03-23 05:11:28 +00:00
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 ) ) ;
2022-11-07 21:23:34 +00:00
2023-02-18 02:32:02 +00:00
const USE_EXTERNAL_EMOJIS = 1 n << 18 n ;
const USE_EXTERNAL_STICKERS = 1 n << 37 n ;
2023-05-23 01:02:48 +00:00
const enum EmojiIntentions {
2023-02-16 01:00:09 +00:00
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
}
2023-05-23 01:02:48 +00:00
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
}
2022-11-12 15:25:28 +00:00
interface BaseSticker {
2022-11-07 21:23:34 +00:00
available : boolean ;
description : string ;
format_type : number ;
id : string ;
name : string ;
tags : string ;
type : number ;
}
2022-11-12 15:25:28 +00:00
interface GuildSticker extends BaseSticker {
guild_id : string ;
}
interface DiscordSticker extends BaseSticker {
pack_id : string ;
}
type Sticker = GuildSticker | DiscordSticker ;
2022-11-07 21:23:34 +00:00
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
2023-04-05 03:06:04 +00:00
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
} ,
2023-04-13 02:22:38 +00:00
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
} ,
2023-04-05 03:06:04 +00:00
enableStreamQualityBypass : {
description : "Allow streaming in nitro quality" ,
type : OptionType . BOOLEAN ,
default : true ,
restartNeeded : true
}
} ) ;
2022-08-31 18:53:36 +00:00
export default definePlugin ( {
2022-11-14 17:05:41 +00:00
name : "FakeNitro" ,
2023-04-07 19:15:11 +00:00
authors : [ Devs . Arjix , Devs . D3SOX , Devs . Ven , Devs . obscurity , Devs . captain , Devs . Nuckyz , Devs . AutumnVN ] ,
2023-02-18 02:32:02 +00:00
description : "Allows you to stream in nitro quality, send fake emojis/stickers and use client themes." ,
2022-08-31 18:58:21 +00:00
dependencies : [ "MessageEventsAPI" ] ,
2022-11-07 21:23:34 +00:00
2023-04-05 03:06:04 +00:00
settings ,
2022-08-31 18:53:36 +00:00
patches : [
{
2023-02-16 01:00:09 +00:00
find : ".PREMIUM_LOCKED;" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . enableEmojiBypass ,
2022-08-31 18:53:36 +00:00
replacement : [
2023-02-16 01:00:09 +00:00
{
2023-03-08 03:11:59 +00:00
match : /(?<=(\i)=\i\.intention)/ ,
replace : ( _ , intention ) = > ` ,fakeNitroIntention= ${ intention } `
2023-02-18 02:32:02 +00:00
} ,
{
2023-03-21 06:07:16 +00:00
match : /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g ,
replace : '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
2023-02-16 01:00:09 +00:00
} ,
{
2023-03-21 06:41:11 +00:00
match : /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/ ,
2023-03-21 06:07:16 +00:00
replace : ( _ , rest , canUseExternal ) = > ` ${ rest } (! ${ canUseExternal } &&(typeof fakeNitroIntention==="undefined"||![ ${ EmojiIntentions . CHAT } , ${ EmojiIntentions . GUILD_STICKER_RELATED_EMOJI } ].includes(fakeNitroIntention))) `
2023-05-23 01:02:48 +00:00
} ,
{
match : /if\(!\i\.available/ ,
replace : m = > ` ${ m } &&(typeof fakeNitroIntention==="undefined"||![ ${ EmojiIntentions . CHAT } , ${ EmojiIntentions . GUILD_STICKER_RELATED_EMOJI } ].includes(fakeNitroIntention)) `
2023-02-16 01:00:09 +00:00
}
]
} ,
{
find : "canUseAnimatedEmojis:function" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . enableEmojiBypass ,
2023-02-16 01:00:09 +00:00
replacement : {
2023-03-21 06:07:16 +00:00
match : /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g ,
replace : ( _ , rest , premiumCheck ) = > ` ${ rest } ,fakeNitroIntention){ ${ premiumCheck } ||fakeNitroIntention==null||[ ${ EmojiIntentions . CHAT } , ${ EmojiIntentions . GUILD_STICKER_RELATED_EMOJI } ].includes(fakeNitroIntention) `
2023-02-16 01:00:09 +00:00
}
2022-10-21 11:37:53 +00:00
} ,
2022-11-07 21:23:34 +00:00
{
2023-02-18 02:32:02 +00:00
find : "canUseStickersEverywhere:function" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . enableStickerBypass ,
2022-11-07 21:23:34 +00:00
replacement : {
2023-03-21 06:07:16 +00:00
match : /canUseStickersEverywhere:function\(\i\){/ ,
replace : "$&return true;"
2022-11-07 21:23:34 +00:00
} ,
} ,
{
find : "\"SENDABLE\"" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . enableStickerBypass ,
2022-11-07 21:23:34 +00:00
replacement : {
match : /(\w+)\.available\?/ ,
replace : "true?"
}
} ,
2022-10-21 11:37:53 +00:00
{
2023-02-18 02:32:02 +00:00
find : "canStreamHighQuality:function" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . enableStreamQualityBypass ,
2022-10-21 11:37:53 +00:00
replacement : [
2022-10-19 10:27:20 +00:00
"canUseHighVideoUploadQuality" ,
2022-10-06 14:33:30 +00:00
"canStreamHighQuality" ,
"canStreamMidQuality"
2022-08-31 18:53:36 +00:00
] . map ( func = > {
return {
2023-03-21 06:07:16 +00:00
match : new RegExp ( ` ${ func } :function \\ ( \\ i \\ ){ ` ) ,
replace : "$&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" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . enableStreamQualityBypass ,
2022-10-01 15:04:57 +00:00
replacement : {
match : /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g ,
replace : ""
}
2022-11-07 21:23:34 +00:00
} ,
2023-02-18 02:32:02 +00:00
{
find : "canUseClientThemes:function" ,
replacement : {
2023-03-21 06:07:16 +00:00
match : /canUseClientThemes:function\(\i\){/ ,
replace : "$&return true;"
2023-02-18 02:32:02 +00:00
}
2023-03-21 09:03:28 +00:00
} ,
{
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); `
}
]
2023-03-22 03:01:32 +00:00
} ,
{
find : "updateTheme:function" ,
replacement : {
2023-03-23 05:11:28 +00:00
match : /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)(\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\))/ ,
replace : ( _ , rest , backgroundGradientPresetId , originalCall , theme ) = > ` ${ rest } $ self.handleGradientThemeSelect( ${ backgroundGradientPresetId } , ${ theme } ,()=> ${ originalCall } ); `
2023-03-22 03:01:32 +00:00
}
2023-03-23 10:45:39 +00:00
} ,
{
find : '["strong","em","u","text","inlineCode","s","spoiler"]' ,
replacement : [
{
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . transformEmojis ,
2023-03-23 10:45:39 +00:00
match : /1!==(\i)\.length\|\|1!==\i\.length/ ,
2023-04-05 03:06:04 +00:00
replace : ( m , content ) = > ` ${ m } || $ self.shouldKeepEmojiLink( ${ content } [0]) `
2023-03-23 10:45:39 +00:00
} ,
{
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . transformEmojis || settings . store . transformStickers ,
2023-03-23 10:45:39 +00:00
match : /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/ ,
2023-04-05 03:06:04 +00:00
replace : ( _ , content ) = > ` ${ content } = $ self.patchFakeNitroEmojisOrRemoveStickersLinks( ${ content } ,arguments[2]?.formatInline); `
2023-03-23 10:45:39 +00:00
}
]
} ,
{
find : "renderEmbeds=function" ,
2023-04-05 03:06:04 +00:00
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 } , `
} ,
{
2023-04-10 21:59:48 +00:00
match : /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/ ,
replace : ( _ , rest , reactNode , props ) = > ` ${ rest } $ self.addFakeNotice("STICKER", ${ reactNode } ,!! ${ props } .renderableSticker?.fake) `
2023-04-05 03:06:04 +00:00
}
]
} ,
{
find : ".Messages.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION" ,
predicate : ( ) = > settings . store . transformEmojis ,
2023-03-23 10:45:39 +00:00
replacement : {
2023-04-10 21:59:48 +00:00
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) `
2023-03-23 10:45:39 +00:00
}
2023-02-18 02:32:02 +00:00
}
2022-08-31 18:53:36 +00:00
] ,
2022-11-07 21:23:34 +00:00
2022-10-01 15:04:57 +00:00
get guildId() {
2023-03-19 08:44:11 +00:00
return getCurrentGuild ( ) ? . id ;
2022-10-01 15:04:57 +00:00
} ,
get canUseEmotes() {
2022-11-08 16:51:09 +00:00
return ( UserStore . getCurrentUser ( ) . premiumType ? ? 0 ) > 0 ;
} ,
2022-11-12 15:25:28 +00:00
get canUseStickers() {
2022-11-08 16:51:09 +00:00
return ( UserStore . getCurrentUser ( ) . premiumType ? ? 0 ) > 1 ;
2022-10-01 15:04:57 +00:00
} ,
2023-03-21 09:03:28 +00:00
handleProtoChange ( proto : any , user : any ) {
2023-05-16 14:53:17 +00:00
if ( proto == null || typeof proto === "string" || ! UserSettingsProtoStore || ( ! proto . appearance && ! AppearanceSettingsProto ) ) return ;
2023-03-21 09:03:28 +00:00
2023-03-23 05:11:28 +00:00
const premiumType : number = user ? . premium_type ? ? UserStore ? . getCurrentUser ( ) ? . premiumType ? ? 0 ;
2023-03-22 03:01:32 +00:00
2023-03-23 05:11:28 +00:00
if ( premiumType !== 2 ) {
proto . appearance ? ? = AppearanceSettingsProto . create ( ) ;
2023-03-22 03:01:32 +00:00
if ( UserSettingsProtoStore . settings . appearance ? . theme != null ) {
proto . appearance . theme = UserSettingsProtoStore . settings . appearance . theme ;
}
2023-03-21 09:03:28 +00:00
2023-03-23 05:11:28 +00:00
if ( UserSettingsProtoStore . settings . appearance ? . clientThemeSettings ? . backgroundGradientPresetId ? . value != null && ClientThemeSettingsProto ) {
const clientThemeSettingsDummyProto = ClientThemeSettingsProto . create ( {
backgroundGradientPresetId : {
value : UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
2023-03-22 03:01:32 +00:00
}
} ) ;
2023-03-23 05:11:28 +00:00
proto . appearance . clientThemeSettings ? ? = clientThemeSettingsDummyProto ;
proto . appearance . clientThemeSettings . backgroundGradientPresetId = clientThemeSettingsDummyProto . backgroundGradientPresetId ;
2023-03-21 09:03:28 +00:00
}
}
} ,
2023-03-23 05:11:28 +00:00
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
2023-03-22 03:01:32 +00:00
}
} ) ;
2023-03-23 05:11:28 +00:00
newAppearanceProto . clientThemeSettings ? ? = clientThemeSettingsDummyProto ;
newAppearanceProto . clientThemeSettings . backgroundGradientPresetId = clientThemeSettingsDummyProto . backgroundGradientPresetId ;
const proto = PreloadedUserSettingsProtoHandler . ProtoClass . create ( ) ;
proto . appearance = newAppearanceProto ;
2023-03-22 03:01:32 +00:00
FluxDispatcher . dispatch ( {
type : "USER_SETTINGS_PROTO_UPDATE" ,
local : true ,
partial : true ,
settings : {
type : 1 ,
proto
}
} ) ;
} ,
2023-05-23 03:25:48 +00:00
trimContent ( content : Array < any > ) {
const firstContent = content [ 0 ] ;
if ( typeof firstContent === "string" ) content [ 0 ] = firstContent . trimStart ( ) ;
if ( content [ 0 ] === "" ) content . shift ( ) ;
2023-03-23 10:45:39 +00:00
2023-05-23 03:25:48 +00:00
const lastIndex = content . length - 1 ;
const lastContent = content [ lastIndex ] ;
if ( typeof lastContent === "string" ) content [ lastIndex ] = lastContent . trimEnd ( ) ;
if ( content [ lastIndex ] === "" ) content . pop ( ) ;
} ,
2023-03-23 10:45:39 +00:00
2023-05-23 03:25:48 +00:00
clearEmptyArrayItems ( array : Array < any > ) {
return array . filter ( item = > item != null ) ;
} ,
2023-04-05 03:06:04 +00:00
2023-05-23 03:25:48 +00:00
ensureChildrenIsArray ( child : ReactElement ) {
if ( ! Array . isArray ( child . props . children ) ) child . props . children = [ child . props . children ] ;
} ,
patchFakeNitroEmojisOrRemoveStickersLinks ( content : Array < any > , inline : boolean ) {
// If content has more than one child or it's a single ReactElement like a header or list
if ( ( content . length > 1 || typeof content [ 0 ] ? . type === "string" ) && ! settings . store . transformCompoundSentence ) return content ;
2023-03-23 10:45:39 +00:00
2023-05-23 03:25:48 +00:00
let nextIndex = content . length ;
const transformLinkChild = ( child : ReactElement ) = > {
2023-04-05 03:06:04 +00:00
if ( settings . store . transformEmojis ) {
2023-05-23 03:25:48 +00:00
const fakeNitroMatch = child . props . href . match ( fakeNitroEmojiRegex ) ;
2023-04-05 03:06:04 +00:00
if ( fakeNitroMatch ) {
let url : URL | null = null ;
try {
2023-05-23 03:25:48 +00:00
url = new URL ( child . props . href ) ;
2023-04-05 03:06:04 +00:00
} catch { }
const emojiName = EmojiStore . getCustomEmojiById ( fakeNitroMatch [ 1 ] ) ? . name ? ? url ? . searchParams . get ( "name" ) ? ? "FakeNitroEmoji" ;
2023-05-23 03:25:48 +00:00
return Parser . defaultRules . customEmoji . react ( {
jumboable : ! inline && content . length === 1 && typeof content [ 0 ] . type !== "string" ,
2023-04-05 03:06:04 +00:00
animated : fakeNitroMatch [ 2 ] === "gif" ,
emojiId : fakeNitroMatch [ 1 ] ,
name : emojiName ,
fake : true
2023-05-23 03:25:48 +00:00
} , void 0 , { key : String ( nextIndex ++ ) } ) ;
2023-04-05 03:06:04 +00:00
}
2023-03-23 10:45:39 +00:00
}
2023-04-05 03:06:04 +00:00
if ( settings . store . transformStickers ) {
2023-05-23 03:25:48 +00:00
if ( fakeNitroStickerRegex . test ( child . props . href ) ) return null ;
2023-04-05 03:06:04 +00:00
2023-05-23 03:25:48 +00:00
const gifMatch = child . props . href . match ( fakeNitroGifStickerRegex ) ;
2023-04-05 03:06:04 +00:00
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
2023-05-23 03:25:48 +00:00
if ( StickerStore . getStickerById ( gifMatch [ 1 ] ) ) return null ;
2023-04-05 03:06:04 +00:00
}
}
2023-05-23 03:25:48 +00:00
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 ;
}
2023-03-23 10:45:39 +00:00
2023-05-23 03:25:48 +00:00
return child ;
} ;
2023-04-05 03:06:04 +00:00
2023-05-23 03:25:48 +00:00
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 < ReactElement > ) = > {
for ( const [ index , child ] of children . entries ( ) ) children [ index ] = modifyChild ( child ) ;
children = this . clearEmptyArrayItems ( children ) ;
this . trimContent ( children ) ;
return children ;
} ;
try {
return modifyChildren ( window . _ . cloneDeep ( content ) ) ;
} catch ( err ) {
new Logger ( "FakeNitro" ) . error ( err ) ;
return content ;
}
2023-03-23 10:45:39 +00:00
} ,
2023-04-05 03:06:04 +00:00
patchFakeNitroStickers ( stickers : Array < any > , message : Message ) {
const itemsToMaybePush : Array < string > = [ ] ;
const contentItems = message . content . split ( /\s/ ) ;
2023-05-23 03:25:48 +00:00
if ( settings . store . transformCompoundSentence ) itemsToMaybePush . push ( . . . contentItems ) ;
else if ( contentItems . length === 1 ) itemsToMaybePush . push ( contentItems [ 0 ] ) ;
2023-04-05 03:06:04 +00:00
itemsToMaybePush . push ( . . . message . attachments . filter ( attachment = > attachment . content_type === "image/gif" ) . map ( attachment = > attachment . url ) ) ;
for ( const item of itemsToMaybePush ) {
2023-05-23 03:25:48 +00:00
if ( ! settings . store . transformCompoundSentence && ! item . startsWith ( "http" ) ) continue ;
2023-04-05 03:06:04 +00:00
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 ) {
2023-05-23 03:25:48 +00:00
const contentItems = message . content . split ( /\s/ ) ;
if ( contentItems . length > 1 && ! settings . store . transformCompoundSentence ) return false ;
2023-04-05 03:06:04 +00:00
switch ( embed . type ) {
case "image" : {
2023-07-10 20:39:40 +00:00
if (
! settings . store . transformCompoundSentence
&& ! contentItems . includes ( embed . url ! )
&& ! contentItems . includes ( embed . image ? . proxyURL ! )
) return false ;
2023-05-23 03:25:48 +00:00
2023-04-05 03:06:04 +00:00
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 ) ;
} ,
2023-04-10 21:59:48 +00:00
addFakeNotice ( type : "STICKER" | "EMOJI" , node : Array < ReactNode > , fake : boolean ) {
if ( ! fake ) return node ;
node = Array . isArray ( node ) ? node : [ node ] ;
switch ( type ) {
case "STICKER" : {
2023-04-16 23:43:11 +00:00
node . push ( " This is a FakeNitro sticker and renders like a real sticker only for you. Appears as a link to non-plugin users." ) ;
2023-04-10 21:59:48 +00:00
return node ;
}
case "EMOJI" : {
2023-04-16 23:43:11 +00:00
node . push ( " This is a FakeNitro emoji and renders like a real emoji only for you. Appears as a link to non-plugin users." ) ;
2023-04-10 21:59:48 +00:00
return node ;
}
}
} ,
2023-05-23 01:02:48 +00:00
hasPermissionToUseExternalEmojis ( channelId : string ) : boolean {
2023-02-18 02:32:02 +00:00
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 ) ;
} ,
2022-11-07 21:23:34 +00:00
getStickerLink ( stickerId : string ) {
2022-11-14 17:05:41 +00:00
return ` https://media.discordapp.net/stickers/ ${ stickerId } .png?size= ${ Settings . plugins . FakeNitro . stickerSize } ` ;
2022-11-07 21:23:34 +00:00
} ,
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 ( ) ;
2022-11-14 17:05:41 +00:00
const resolution = Settings . plugins . FakeNitro . stickerSize ;
2022-11-07 21:23:34 +00:00
const canvas = document . createElement ( "canvas" ) ;
2022-11-09 16:30:37 +00:00
canvas . width = resolution ;
canvas . height = resolution ;
2022-11-07 21:23:34 +00:00
const ctx = canvas . getContext ( "2d" , {
willReadFrequently : true
} ) ! ;
2022-11-09 16:30:37 +00:00
const scale = resolution / Math . max ( width , height ) ;
2022-11-07 21:23:34 +00:00
ctx . scale ( scale , scale ) ;
2023-04-05 03:06:04 +00:00
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 ) ;
}
2022-11-07 21:23:34 +00:00
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 ,
2023-04-05 03:06:04 +00:00
delay
2022-11-07 21:23:34 +00:00
} ) ;
2022-11-09 19:29:35 +00:00
if ( disposeOp === ApngDisposeOp . BACKGROUND ) {
ctx . clearRect ( left , top , width , height ) ;
2023-04-05 03:06:04 +00:00
} else if ( disposeOp === ApngDisposeOp . PREVIOUS ) {
ctx . putImageData ( previousFrameData , left , top ) ;
2022-11-07 21:23:34 +00:00
}
2022-10-21 11:37:53 +00:00
}
2022-11-07 21:23:34 +00:00
gif . finish ( ) ;
2023-04-05 03:06:04 +00:00
2022-11-07 21:23:34 +00:00
const file = new File ( [ gif . bytesView ( ) ] , ` ${ stickerId } .gif ` , { type : "image/gif" } ) ;
promptToUpload ( [ file ] , ChannelStore . getChannel ( channelId ) , DRAFT_TYPE ) ;
} ,
start() {
2023-05-23 01:02:48 +00:00
const s = settings . store ;
if ( ! s . enableEmojiBypass && ! s . enableStickerBypass ) {
2022-10-01 15:04:57 +00:00
return ;
}
2022-08-31 18:53:36 +00:00
2022-11-07 21:23:34 +00:00
function getWordBoundary ( origStr : string , offset : number ) {
2022-10-02 20:12:48 +00:00
return ( ! origStr [ offset ] || /\s/ . test ( origStr [ offset ] ) ) ? "" : " " ;
}
2022-11-07 21:23:34 +00:00
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
2022-11-12 15:25:28 +00:00
stickerBypass : {
2023-05-23 01:02:48 +00:00
if ( ! s . enableStickerBypass )
2022-11-12 15:25:28 +00:00
break stickerBypass ;
2023-05-05 00:47:08 +00:00
const sticker = StickerStore . getStickerById ( extra . stickers ? . [ 0 ] ! ) ;
2022-11-12 15:25:28 +00:00
if ( ! sticker )
break stickerBypass ;
2023-05-23 01:02:48 +00:00
// 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 ) )
2022-11-12 15:25:28 +00:00
break stickerBypass ;
2023-05-23 01:02:48 +00:00
const link = this . getStickerLink ( sticker . id ) ;
if ( sticker . format_type === StickerType . APNG ) {
2023-04-05 03:06:04 +00:00
this . sendAnimatedSticker ( link , sticker . id , channelId ) ;
2022-11-12 15:25:28 +00:00
return { cancel : true } ;
} else {
2023-05-05 00:47:08 +00:00
extra . stickers ! . length = 0 ;
2023-05-23 01:02:48 +00:00
messageObj . content += ` ${ link } &name= ${ encodeURIComponent ( sticker . name ) } ` ;
2022-11-07 21:23:34 +00:00
}
}
2023-05-23 01:02:48 +00:00
if ( s . enableEmojiBypass ) {
const canUseEmotes = this . canUseEmotes && this . hasPermissionToUseExternalEmojis ( channelId ) ;
2022-11-07 21:23:34 +00:00
for ( const emoji of messageObj . validNonShortcutEmojis ) {
if ( ! emoji . require_colons ) continue ;
2023-05-23 01:02:48 +00:00
if ( emoji . available !== false && canUseEmotes ) continue ;
2022-11-07 21:23:34 +00:00
if ( emoji . guildId === guildId && ! emoji . animated ) continue ;
2022-08-31 18:53:36 +00:00
2022-11-07 21:23:34 +00:00
const emojiString = ` < ${ emoji . animated ? "a" : "" } : ${ emoji . originalName || emoji . name } : ${ emoji . id } > ` ;
2023-04-05 03:06:04 +00:00
const url = emoji . url . replace ( /\?size=\d+/ , "?" + new URLSearchParams ( {
size : Settings.plugins.FakeNitro.emojiSize ,
name : encodeURIComponent ( emoji . name )
} ) ) ;
2022-11-07 21:23:34 +00:00
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
}
2022-11-07 21:23:34 +00:00
return { cancel : false } ;
2022-08-31 18:55:58 +00:00
} ) ;
2022-11-07 21:23:34 +00:00
2023-02-18 02:32:02 +00:00
this . preEdit = addPreEditListener ( ( channelId , __ , messageObj ) = > {
2023-05-23 01:02:48 +00:00
if ( ! s . enableEmojiBypass ) return ;
const canUseEmotes = this . canUseEmotes && this . hasPermissionToUseExternalEmojis ( channelId ) ;
2022-11-07 21:23:34 +00:00
2023-02-18 02:32:02 +00:00
const { guildId } = this ;
2022-11-07 21:23:34 +00:00
2023-05-23 01:02:48 +00:00
messageObj . content = messageObj . content . replace ( /(?<!\\)<a?:(?:\w+):(\d+)>/ig , ( emojiStr , emojiId , offset , origStr ) = > {
2023-02-18 02:32:02 +00:00
const emoji = EmojiStore . getCustomEmojiById ( emojiId ) ;
2023-05-23 01:02:48 +00:00
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 ;
2023-02-18 02:32:02 +00:00
2023-04-05 03:06:04 +00:00
const url = emoji . url . replace ( /\?size=\d+/ , "?" + new URLSearchParams ( {
size : Settings.plugins.FakeNitro.emojiSize ,
name : encodeURIComponent ( emoji . name )
} ) ) ;
2023-05-23 01:02:48 +00:00
return ` ${ getWordBoundary ( origStr , offset - 1 ) } ${ url } ${ getWordBoundary ( origStr , offset + emojiStr . length ) } ` ;
} ) ;
2023-02-18 02:32:02 +00:00
} ) ;
2022-08-31 18:53:36 +00:00
} ,
2022-08-31 20:08:05 +00:00
stop() {
removePreSendListener ( this . preSend ) ;
removePreEditListener ( this . preEdit ) ;
}
2022-08-31 18:55:58 +00:00
} ) ;