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-04-05 03:06:04 +00:00
import { definePluginSettings , migratePluginSettings , 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-03-23 05:11:28 +00:00
import { proxyLazy } from "@utils/proxyLazy" ;
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-04-05 03:06:04 +00:00
import { ChannelStore , FluxDispatcher , Parser , PermissionStore , UserStore } from "@webpack/common" ;
import type { Message } from "discord-types/general" ;
2023-04-10 21:59:48 +00:00
import type { 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 ;
} ;
const EmojiStore = findStoreLazy ( "EmojiStore" ) ;
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-02-16 01:00:09 +00:00
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
}
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-11-14 17:05:41 +00:00
migratePluginSettings ( "FakeNitro" , "NitroBypass" ) ;
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-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-03-23 05:11:28 +00:00
if ( ( ! proto . appearance && ! AppearanceSettingsProto ) || ! UserSettingsProtoStore ) 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-04-05 03:06:04 +00:00
patchFakeNitroEmojisOrRemoveStickersLinks ( content : Array < any > , inline : boolean ) {
2023-04-13 02:22:38 +00:00
if ( content . length > 1 && ! settings . store . transformCompoundSentence ) return content ;
2023-03-23 10:45:39 +00:00
const newContent : Array < any > = [ ] ;
2023-04-05 03:06:04 +00:00
let nextIndex = content . length ;
2023-03-23 10:45:39 +00:00
for ( const element of content ) {
if ( element . props ? . trusted == null ) {
newContent . push ( element ) ;
continue ;
}
2023-04-05 03:06:04 +00:00
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 ( {
2023-04-13 02:22:38 +00:00
jumboable : ! inline && content . length === 1 ,
2023-04-05 03:06:04 +00:00
animated : fakeNitroMatch [ 2 ] === "gif" ,
emojiId : fakeNitroMatch [ 1 ] ,
name : emojiName ,
fake : true
} , void 0 , { key : String ( nextIndex ++ ) } ) ) ;
continue ;
}
2023-03-23 10:45:39 +00:00
}
2023-04-05 03:06:04 +00:00
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 ) ;
2023-03-23 10:45:39 +00:00
}
2023-04-13 02:22:38 +00:00
const firstContent = newContent [ 0 ] ;
if ( typeof firstContent === "string" ) newContent [ 0 ] = firstContent . trimStart ( ) ;
2023-04-05 03:06:04 +00:00
2023-03-23 10:45:39 +00:00
return newContent ;
} ,
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-04-13 02:22:38 +00:00
if ( contentItems . length === 1 && ! settings . store . transformCompoundSentence ) itemsToMaybePush . push ( contentItems [ 0 ] ) ;
else itemsToMaybePush . push ( . . . contentItems ) ;
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 ) {
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-04-13 02:22:38 +00:00
if ( message . content . split ( /\s/ ) . length > 1 && ! settings . store . transformCompoundSentence ) return false ;
2023-04-05 03:06:04 +00:00
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 ) ;
} ,
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-02-18 02:32:02 +00:00
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 ) ;
} ,
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() {
2022-11-14 17:05:41 +00:00
const settings = Settings . plugins . FakeNitro ;
2022-11-07 21:23:34 +00:00
if ( ! settings . enableEmojiBypass && ! settings . 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 : {
if ( ! settings . enableStickerBypass )
break stickerBypass ;
const sticker = StickerStore . getStickerById ( extra ? . stickerIds ? . [ 0 ] ! ) ;
if ( ! sticker )
break stickerBypass ;
2023-02-18 02:32:02 +00:00
if ( sticker . available !== false && ( ( this . canUseStickers && this . hasPermissionToUseExternalStickers ( channelId ) ) || ( sticker as GuildSticker ) ? . guild_id === guildId ) )
2022-11-12 15:25:28 +00:00
break stickerBypass ;
let link = this . getStickerLink ( sticker . id ) ;
if ( sticker . format_type === 2 ) {
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 {
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 ` ;
2022-11-07 21:23:34 +00:00
}
2022-11-12 15:25:28 +00:00
delete extra . stickerIds ;
2023-04-07 19:15:11 +00:00
messageObj . content += " " + link + ` &name= ${ encodeURIComponent ( sticker . name ) } ` ;
2022-11-07 21:23:34 +00:00
}
}
2023-02-18 02:32:02 +00:00
if ( ( ! this . canUseEmotes || ! this . hasPermissionToUseExternalEmojis ( channelId ) ) && settings . enableEmojiBypass ) {
2022-11-07 21:23:34 +00:00
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
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 ) = > {
if ( this . canUseEmotes && this . hasPermissionToUseExternalEmojis ( channelId ) ) return ;
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-02-18 02:32:02 +00:00
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 ;
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-02-18 02:32:02 +00:00
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
} ,
2022-08-31 20:08:05 +00:00
stop() {
removePreSendListener ( this . preSend ) ;
removePreEditListener ( this . preEdit ) ;
}
2022-08-31 18:55:58 +00:00
} ) ;