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-09-19 02:07:24 +00:00
import { ApngBlendOp , ApngDisposeOp , 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-10-26 16:49:06 +00:00
import { findByPropsLazy , findStoreLazy } from "@webpack" ;
import { ChannelStore , EmojiStore , FluxDispatcher , lodash , Parser , PermissionStore , UploadHandler , UserSettingsActionCreators , UserStore } from "@webpack/common" ;
2023-04-05 03:06:04 +00:00
import type { Message } from "discord-types/general" ;
2023-09-19 02:07:24 +00:00
import { applyPalette , GIFEncoder , quantize } from "gifenc" ;
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 ;
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
2023-10-26 03:19:26 +00:00
const UserSettingsProtoStore = findStoreLazy ( "UserSettingsProtoStore" ) ;
const ProtoUtils = findByPropsLazy ( "BINARY_READ_OPTIONS" ) ;
2023-03-23 05:11:28 +00:00
2023-10-26 03:19:26 +00:00
function searchProtoClassField ( localName : string , protoClass : any ) {
const field = protoClass ? . fields ? . find ( ( field : any ) = > field . localName === localName ) ;
2023-03-23 05:11:28 +00:00
if ( ! field ) return ;
2023-10-26 03:19:26 +00:00
const fieldGetter = Object . values ( field ) . find ( value = > typeof value === "function" ) as any ;
return fieldGetter ? . ( ) ;
2023-03-23 05:11:28 +00:00
}
2023-10-26 03:19:26 +00:00
const PreloadedUserSettingsActionCreators = proxyLazy ( ( ) = > UserSettingsActionCreators . PreloadedUserSettingsActionCreators ) ;
const AppearanceSettingsActionCreators = proxyLazy ( ( ) = > searchProtoClassField ( "appearance" , PreloadedUserSettingsActionCreators . ProtoClass ) ) ;
const ClientThemeSettingsActionsCreators = proxyLazy ( ( ) = > searchProtoClassField ( "clientThemeSettings" , AppearanceSettingsActionCreators ) ) ;
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-09-20 05:44:31 +00:00
const enum FakeNoticeType {
Sticker ,
Emoji
}
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-10-26 03:19:26 +00:00
// Create a variable for the intention of listing the emoji
2023-10-25 22:37:52 +00:00
match : /(?<=,intention:(\i).+?;)/ ,
2023-10-26 03:19:26 +00:00
replace : ( _ , intention ) = > ` let fakeNitroIntention= ${ intention } ; `
2023-02-18 02:32:02 +00:00
} ,
{
2023-10-26 03:19:26 +00:00
// Send the intention of listing the emoji to the nitro permission check functions
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-10-26 03:19:26 +00:00
// Disallow the emoji if the intention doesn't allow it
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
} ,
{
2023-10-26 03:19:26 +00:00
// Make the emoji always available if the intention allows it
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
}
]
} ,
2023-10-26 03:19:26 +00:00
// Allow emojis and animated emojis to be sent everywhere
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-10-26 03:19:26 +00:00
match : /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g ,
2023-11-04 17:44:53 +00:00
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
} ,
2023-10-26 03:19:26 +00:00
// Allow stickers to be sent everywhere
2022-11-07 21:23:34 +00:00
{
2023-11-07 18:58:10 +00:00
find : "canUseCustomStickersEverywhere:function" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . enableStickerBypass ,
2022-11-07 21:23:34 +00:00
replacement : {
2023-11-07 18:58:10 +00:00
match : /canUseCustomStickersEverywhere:function\(\i\){/ ,
2023-03-21 06:07:16 +00:00
replace : "$&return true;"
2022-11-07 21:23:34 +00:00
} ,
} ,
2023-10-26 03:19:26 +00:00
// Make stickers always available
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?"
}
} ,
2023-10-26 03:19:26 +00:00
// Allow streaming with high quality
2022-10-21 11:37:53 +00:00
{
2023-09-12 08:01:18 +00:00
find : "canUseHighVideoUploadQuality: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" ,
2023-10-06 16:01:19 +00:00
"canStreamQuality" ,
2022-08-31 18:53:36 +00:00
] . map ( func = > {
return {
2023-09-12 08:10:44 +00:00
match : new RegExp ( ` ${ func } :function \\ ( \\ i(?:, \\ i)? \\ ){ ` , "g" ) ,
2023-03-21 06:07:16 +00:00
replace : "$&return true;"
2022-08-31 18:55:58 +00:00
} ;
2022-08-31 18:53:36 +00:00
} )
} ,
2023-10-26 03:19:26 +00:00
// Remove boost requirements to stream with high quality
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 : {
2023-10-25 22:37:52 +00:00
match : /guildPremiumTier:\i\.\i\.TIER_\d,?/g ,
2022-10-01 15:04:57 +00:00
replace : ""
}
2022-11-07 21:23:34 +00:00
} ,
2023-10-26 03:19:26 +00:00
// Allow client themes to be changeable
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 : [
{
2023-10-26 03:19:26 +00:00
// Overwrite incoming connection settings proto with our local settings
2023-03-21 09:03:28 +00:00
match : /CONNECTION_OPEN:function\((\i)\){/ ,
replace : ( m , props ) = > ` ${ m } $ self.handleProtoChange( ${ props } .userSettingsProto, ${ props } .user); `
} ,
{
2023-10-26 03:19:26 +00:00
// Overwrite non local proto changes with our local settings
match : /let{settings:/ ,
replace : "arguments[0].local||$self.handleProtoChange(arguments[0].settings.proto);$&"
2023-03-21 09:03:28 +00:00
}
]
2023-03-22 03:01:32 +00:00
} ,
2023-10-26 03:19:26 +00:00
// Call our function to handle changing the gradient theme when selecting a new one
2023-03-22 03:01:32 +00:00
{
2023-10-26 03:19:26 +00:00
find : ",updateTheme(" ,
2023-03-22 03:01:32 +00:00
replacement : {
2023-10-26 03:19:26 +00:00
match : /(function \i\(\i\){let{backgroundGradientPresetId:(\i).+?)(\i\.\i\.updateAsync.+?theme=(.+?),.+?},\i\))/ ,
2023-03-23 05:11:28 +00:00
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-10-26 03:19:26 +00:00
// Call our function to decide whether the emoji link should be kept or not
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-10-26 03:19:26 +00:00
// Patch the rendered message content to add fake nitro emojis or remove sticker links
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
}
]
} ,
{
2023-10-26 03:19:26 +00:00
find : "renderEmbeds(" ,
2023-04-05 03:06:04 +00:00
replacement : [
{
2023-10-26 03:19:26 +00:00
// Call our function to decide whether the embed should be ignored or not
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . transformEmojis || settings . store . transformStickers ,
2023-10-26 03:19:26 +00:00
match : /(renderEmbeds\((\i)\){)(.+?embeds\.map\((\i)=>{)/ ,
2023-04-05 03:06:04 +00:00
replace : ( _ , rest1 , message , rest2 , embed ) = > ` ${ rest1 } const fakeNitroMessage= ${ message } ; ${ rest2 } if( $ self.shouldIgnoreEmbed( ${ embed } ,fakeNitroMessage))return null; `
} ,
{
2023-10-26 03:19:26 +00:00
// Patch the stickers array to add fake nitro stickers
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . transformStickers ,
2023-10-26 03:19:26 +00:00
match : /(?<=renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;)/ ,
replace : ( _ , message , stickers ) = > ` ${ stickers } = $ self.patchFakeNitroStickers( ${ stickers } , ${ message } ); `
2023-04-05 03:06:04 +00:00
} ,
{
2023-10-26 03:19:26 +00:00
// Filter attachments to remove fake nitro stickers or emojis
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . transformStickers ,
2023-10-26 03:19:26 +00:00
match : /renderAttachments\(\i\){let{attachments:(\i).+?;/ ,
2023-04-05 03:06:04 +00:00
replace : ( m , attachments ) = > ` ${ m } ${ attachments } = $ self.filterAttachments( ${ attachments } ); `
}
]
} ,
{
2023-10-26 03:19:26 +00:00
find : ".Messages.STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION.format" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . transformStickers ,
replacement : [
{
2023-10-26 03:19:26 +00:00
// Export the renderable sticker to be used in the fake nitro sticker notice
match : /let{renderableSticker:(\i).{0,250}isGuildSticker.+?channel:\i,/ ,
replace : ( m , renderableSticker ) = > ` ${ m } fakeNitroRenderableSticker: ${ renderableSticker } , `
2023-04-05 03:06:04 +00:00
} ,
{
2023-10-26 03:19:26 +00:00
// Add the fake nitro sticker notice
match : /(let \i,{sticker:\i,channel:\i,closePopout:\i.+?}=(\i).+?;)(.+?description:)(\i)(?=,sticker:\i)/ ,
replace : ( _ , rest , props , rest2 , reactNode ) = > ` ${ rest } let{fakeNitroRenderableSticker}= ${ props } ; ${ rest2 } $ self.addFakeNotice( ${ FakeNoticeType . Sticker } , ${ reactNode } ,!!fakeNitroRenderableSticker?.fake) `
2023-04-05 03:06:04 +00:00
}
]
} ,
{
2023-09-20 05:44:31 +00:00
find : ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED," ,
predicate : ( ) = > settings . store . transformEmojis ,
replacement : {
2023-10-26 03:19:26 +00:00
// Export the emoji node to be used in the fake nitro emoji notice
2023-10-25 22:37:52 +00:00
match : /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<={node:(\i),.+?)/ ,
2023-09-20 05:44:31 +00:00
replace : ( m , node ) = > ` ${ m } fakeNitroNode: ${ node } , `
}
} ,
{
find : ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION" ,
2023-04-05 03:06:04 +00:00
predicate : ( ) = > settings . store . transformEmojis ,
2023-03-23 10:45:39 +00:00
replacement : {
2023-10-26 03:19:26 +00:00
// Add the fake nitro emoji notice
2023-10-25 22:37:52 +00:00
match : /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.+?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/ ,
2023-10-26 03:19:26 +00:00
replace : ( _ , props , rest , reactNode ) = > ` let{fakeNitroNode}= ${ props } ; ${ rest } $ self.addFakeNotice( ${ FakeNoticeType . Emoji } , ${ reactNode } ,!!fakeNitroNode?.fake) `
2023-03-23 10:45:39 +00:00
}
2023-10-21 15:53:00 +00:00
} ,
2023-10-26 03:19:26 +00:00
// Allow using custom app icons
2023-10-21 15:53:00 +00:00
{
find : "canUsePremiumAppIcons:function" ,
replacement : {
match : /canUsePremiumAppIcons:function\(\i\){/ ,
replace : "$&return true;"
}
} ,
2023-10-26 03:19:26 +00:00
// Separate patch for allowing using custom app icons
2023-10-21 15:53:00 +00:00
{
find : "location:\"AppIconHome\"" ,
replacement : {
match : /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/ ,
replace : "true"
}
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-10-26 03:19:26 +00:00
if ( proto == null || typeof proto === "string" || ! UserSettingsProtoStore || ! PreloadedUserSettingsActionCreators || ! AppearanceSettingsActionCreators || ! ClientThemeSettingsActionsCreators ) 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 ) {
2023-10-26 03:19:26 +00:00
proto . appearance ? ? = AppearanceSettingsActionCreators . create ( ) ;
2023-03-22 03:01:32 +00:00
if ( UserSettingsProtoStore . settings . appearance ? . theme != null ) {
2023-10-26 03:19:26 +00:00
const appearanceSettingsDummy = AppearanceSettingsActionCreators . create ( {
theme : UserSettingsProtoStore.settings.appearance.theme
} ) ;
proto . appearance . theme = appearanceSettingsDummy . theme ;
2023-03-22 03:01:32 +00:00
}
2023-03-21 09:03:28 +00:00
2023-10-26 03:19:26 +00:00
if ( UserSettingsProtoStore . settings . appearance ? . clientThemeSettings ? . backgroundGradientPresetId ? . value != null ) {
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators . create ( {
2023-03-23 05:11:28 +00:00
backgroundGradientPresetId : {
value : UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
2023-03-22 03:01:32 +00:00
}
} ) ;
2023-10-26 03:19:26 +00:00
proto . appearance . clientThemeSettings ? ? = clientThemeSettingsDummy ;
proto . appearance . clientThemeSettings . backgroundGradientPresetId = clientThemeSettingsDummy . 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 ( ) ;
2023-10-26 03:19:26 +00:00
if ( ! PreloadedUserSettingsActionCreators || ! AppearanceSettingsActionCreators || ! ClientThemeSettingsActionsCreators || ! ProtoUtils ) return ;
2023-03-23 05:11:28 +00:00
2023-10-26 03:19:26 +00:00
const currentAppearanceSettings = PreloadedUserSettingsActionCreators . getCurrentValue ( ) . appearance ;
2023-03-23 05:11:28 +00:00
2023-10-26 03:19:26 +00:00
const newAppearanceProto = currentAppearanceSettings != null
? AppearanceSettingsActionCreators . fromBinary ( AppearanceSettingsActionCreators . toBinary ( currentAppearanceSettings ) , ProtoUtils . BINARY_READ_OPTIONS )
: AppearanceSettingsActionCreators . create ( ) ;
2023-03-23 05:11:28 +00:00
newAppearanceProto . theme = theme ;
2023-10-26 03:19:26 +00:00
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators . create ( {
2023-03-23 05:11:28 +00:00
backgroundGradientPresetId : {
value : backgroundGradientPresetId
2023-03-22 03:01:32 +00:00
}
} ) ;
2023-10-26 03:19:26 +00:00
newAppearanceProto . clientThemeSettings ? ? = clientThemeSettingsDummy ;
newAppearanceProto . clientThemeSettings . backgroundGradientPresetId = clientThemeSettingsDummy . backgroundGradientPresetId ;
2023-03-23 05:11:28 +00:00
2023-10-26 03:19:26 +00:00
const proto = PreloadedUserSettingsActionCreators . ProtoClass . create ( ) ;
2023-03-23 05:11:28 +00:00
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 {
2023-10-25 18:29:32 +00:00
return modifyChildren ( lodash . cloneDeep ( content ) ) ;
2023-05-23 03:25:48 +00:00
} 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-09-20 05:44:31 +00:00
addFakeNotice ( type : FakeNoticeType , node : Array < ReactNode > , fake : boolean ) {
2023-04-10 21:59:48 +00:00
if ( ! fake ) return node ;
node = Array . isArray ( node ) ? node : [ node ] ;
switch ( type ) {
2023-09-20 05:44:31 +00:00
case FakeNoticeType . 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 ;
}
2023-09-20 05:44:31 +00:00
case FakeNoticeType . 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 ) {
2023-09-19 02:07:24 +00:00
const { parseURL } = importApngJs ( ) ;
2022-11-07 21:23:34 +00:00
const { frames , width , height } = await parseURL ( stickerLink ) ;
2023-09-19 02:07:24 +00:00
const gif = 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" } ) ;
2023-10-26 16:49:06 +00:00
UploadHandler . promptToUpload ( [ file ] , ChannelStore . getChannel ( channelId ) , DRAFT_TYPE ) ;
2022-11-07 21:23:34 +00:00
} ,
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
} ) ;