import { runInNewContext } from 'vm' ; import { Command , Interaction , Structures } from 'detritus-client' ; import { Markup } from 'detritus-client/lib/utils' ; import { utilitiesFetchMedia , utilitiesFetchText } from '../api' ; import * as DefaultParameters from './defaultparameters' ; import * as Parameters from './parameters' ; import { bigIntGenerateBetween , bigIntMax , bigIntMin , randomFromArray , randomFromIterator , splitString } from './tools' ; const findChannel = Parameters . channel ( { inGuild : true } ) ; const findMemberOrUser = Parameters . memberOrUser ( ) ; export const TagSymbols = Object . freeze ( { BRACKET_LEFT : '{' , BRACKET_RIGHT : '}' , IGNORE : '\\' , SPLITTER_ARGUMENT : '|' , SPLITTER_FUNCTION : ':' , } ) ; export const ATTACHMENT_EXTENSIONS_IMAGE = [ 'bmp' , 'heic' , 'gif' , 'ico' , 'jpg' , 'jpeg' , 'png' , 'raw' , 'tiff' , 'webp' , ] ; export const ATTACHMENT_EXTENSIONS_MEDIA = [ 'mov' , 'mp3' , 'mp4' , 'txt' , 'wav' , 'webm' , ] ; export const ATTACHMENT_EXTENSIONS = [ ... ATTACHMENT_EXTENSIONS_IMAGE , ... ATTACHMENT_EXTENSIONS_MEDIA ] ; export const MAX_ITERATIONS = 150 ; export const MAX_NETWORK_REQUESTS = 10 ; export const MAX_REGEX_TIME = 25 ; export const MAX_REPEAT_AMOUNT = 4000 ; export const MAX_STRING_LENGTH = 10000 ; export const MAX_VARIABLE_KEY_LENGTH = 64 ; export const MAX_VARIABLE_LENGTH = 4000 ; export const MAX_VARIABLES = 100 ; export const PRIVATE_VARIABLE_PREFIX = '__' ; export const SCRIPT_REGEX = /\{((?:(?!:)(?:.|\s))*):([\s\S]+)\}/ ; export const REGEX_ARGUMENT_SPLITTER = new RegExp ( `(?<!\\\\)[${ TagSymbols . SPLITTER_ARGUMENT }]` , 'g' ) ; export const REGEX_ARGUMENT_SPLITTER_ESCAPE_REPLACEMENT = new RegExp ( `\\\\\\${ TagSymbols . SPLITTER_ARGUMENT }` , 'g' ) ; export enum PrivateVariables { ITERATIONS_REMAINING = '__iterationsRemaining' , NETWORK_REQUESTS = '__networkRequests' } export const AllowedDiscordProperties = Object . freeze ( { [ Structures . Member . constructor . name ] : { functions : [ ] , variables : [ 'avatar' , 'avatarUrl' , 'color' , 'colorRole' , 'deaf' , 'discriminator' , 'highestRole' , 'hoistedRole' , 'id' , 'isBoosting' , 'isOffline' , 'isOwner' , 'joinedAt' , 'joinedAtUnix' , 'mention' , 'mute' , 'nick' , 'name' , 'username' ] , } , [ Structures . Role . constructor . name ] : { functions : [ ] , variables : [ 'botId' , 'color' , 'createdAt' , 'createdAtUnix' , 'hoist' , 'id' , 'integrationId' , 'isBoosterRole' , 'isDefault' , 'managed' , 'mention' , 'mentionable' , 'name' , 'permissions' , 'position' ] , } , } ) ; export enum TagIfComparisons { EQUAL = '=' , GREATER_THAN = '>' , GREATER_THAN_OR_EQUAL = '>=' , LESS_THAN = '<' , LESS_THAN_OR_EQUAL = '<=' , TILDE = '~' } export const TAG_IF_COMPARISONS = [ TagIfComparisons . EQUAL , TagIfComparisons . GREATER_THAN , TagIfComparisons . GREATER_THAN_OR_EQUAL , TagIfComparisons . LESS_THAN , TagIfComparisons . LESS_THAN_OR_EQUAL , TagIfComparisons . TILDE , ] ; export enum TagFunctions { ARG = 'ARG' , ARGS = 'ARGS' , ARGSLEN = 'ARGSLEN' , ATTACHMENT = 'ATTACHMENT' , ATTACHMENT_LAST = 'ATTACHMENT_LAST' , ATTACHMENT_SPOILER = 'ATTACHMENT_SPOILER' , AVATAR = 'AVATAR' , CHANNEL = 'CHANNEL' , CHANNEL_ID = 'CHANNEL_ID' , CHANNEL_MENTION = 'CHANNEL_MENTION' , CHANNEL_RANDOM = 'CHANNEL_RANDOM' , CHANNEL_RANDOM_ID = 'CHANNEL_RANDOM_ID' , CHANNEL_RANDOM_MENTION = 'CHANNEL_RANDOM_MENTION' , DISCORD = 'DISCORD' , DOWNLOAD = 'DOWNLOAD' , EVAL = 'EVAL' , GUILD = 'GUILD' , GUILD_COUNT = 'GUILD_COUNT' , GUILD_ID = 'GUILD_ID' , HASTEBIN = 'HASTEBIN' , IMAGE = 'IMAGE' , IMAGESCRIPT = 'IMAGESCRIPT' , LOGICAL_DELETE = 'LOGICAL_DELETE' , LOGICAL_GET = 'LOGICAL_GET' , LOGICAL_IF = 'LOGICAL_IF' , LOGICAL_SET = 'LOGICAL_SET' , MATH = 'MATH' , MATH_ABS = 'MATH_ABS' , MATH_COS = 'MATH_COS' , MATH_E = 'MATH_E' , MATH_MAX = 'MATH_MAX' , MATH_MIN = 'MATH_MIN' , MATH_PI = 'MATH_PI' , MATH_SIN = 'MATH_SIN' , MATH_TAN = 'MATH_TAN' , NSFW = 'NSFW' , PREFIX = 'PREFIX' , RNG_CHOOSE = 'RNG_CHOOSE' , RNG_RANGE = 'RNG_RANGE' , STRING_CODEBLOCK = 'STRING_CODEBLOCK' , STRING_LENGTH = 'STRING_LENGTH' , STRING_LOWER = 'STRING_LOWER' , STRING_NEWLINE = 'STRING_NEWLINE' , STRING_REPEAT = 'STRING_REPEAT' , STRING_REPLACE = 'STRING_REPLACE' , STRING_REVERSE = 'STRING_REVERSE' , STRING_SUB = 'STRING_SUB' , STRING_UPPER = 'STRING_UPPER' , STRING_URL_ENCODE = 'STRING_URL_ENCODE' , USER_DISCRIMINATOR = 'USER_DISCRIMINATOR' , USER_MENTION = 'USER_MENTION' , USER_NAME = 'USER_NAME' , USER_NICK = 'USER_NICK' , USER_ID = 'USER_ID' , USER_RANDOM = 'USER_RANDOM' , USER_RANDOM_ID = 'USER_RANDOM_ID' , USER_RANDOM_ONLINE = 'USER_RANDOM_ONLINE' , USER_RANDOM_ONLINE_ID = 'USER_RANDOM_ONLINE_ID' , USER_RANDOM_ONLINE_TAG = 'USER_RANDOM_ONLINE_TAG' , USER_RANDOM_TAG = 'USER_RANDOM_TAG' , USER_TAG = 'USER_TAG' } export const TagFunctionsToString = Object . freeze ( { IGNORE : [ 'ignore' ] , NOTE : [ 'note' ] , [ TagFunctions . ARG ] : [ 'arg' ] , [ TagFunctions . ARGS ] : [ 'args' ] , [ TagFunctions . ARGSLEN ] : [ 'argslen' ] , [ TagFunctions . ATTACHMENT ] : [ 'attachment' , 'attach' , 'file' ] , [ TagFunctions . ATTACHMENT_LAST ] : [ 'last_attachment' , 'lastattachment' , 'lattachment' , 'lattach' ] , [ TagFunctions . ATTACHMENT_SPOILER ] : [ 'attachmentspoiler' , 'attachspoiler' , 'filespoiler' ] , [ TagFunctions . AVATAR ] : [ 'avatar' ] , [ TagFunctions . CHANNEL ] : [ 'channel' ] , [ TagFunctions . CHANNEL_ID ] : [ 'channelid' ] , [ TagFunctions . CHANNEL_MENTION ] : [ 'channelmention' ] , [ TagFunctions . CHANNEL_RANDOM ] : [ 'randchannel' ] , [ TagFunctions . CHANNEL_RANDOM_ID ] : [ 'randchannelid' ] , [ TagFunctions . CHANNEL_RANDOM_MENTION ] : [ 'randchannelmention' ] , [ TagFunctions . DISCORD ] : [ 'discord' ] , [ TagFunctions . DOWNLOAD ] : [ 'download' , 'text' ] , [ TagFunctions . EVAL ] : [ 'eval' ] , [ TagFunctions . GUILD ] : [ 'guild' , 'server' ] , [ TagFunctions . GUILD_COUNT ] : [ 'guildcount' , 'membercount' , 'servercount' ] , [ TagFunctions . GUILD_ID ] : [ 'guildid' , 'serverid' , 'sid' , 'gid' ] , [ TagFunctions . HASTEBIN ] : [ 'hastebin' , 'haste' ] , [ TagFunctions . IMAGE ] : [ 'image' , 'iscript' ] , [ TagFunctions . IMAGESCRIPT ] : [ 'image2' , 'iscript2' , 'imagescript' ] , [ TagFunctions . LOGICAL_DELETE ] : [ 'delete' ] , [ TagFunctions . LOGICAL_GET ] : [ 'get' ] , [ TagFunctions . LOGICAL_IF ] : [ 'if' ] , [ TagFunctions . LOGICAL_SET ] : [ 'set' ] , [ TagFunctions . MATH ] : [ 'math' ] , [ TagFunctions . MATH_ABS ] : [ 'abs' ] , [ TagFunctions . MATH_COS ] : [ 'cos' ] , [ TagFunctions . MATH_E ] : [ 'e' ] , [ TagFunctions . MATH_MAX ] : [ 'max' ] , [ TagFunctions . MATH_MIN ] : [ 'min' ] , [ TagFunctions . MATH_PI ] : [ 'pi' ] , [ TagFunctions . MATH_SIN ] : [ 'sin' ] , [ TagFunctions . MATH_TAN ] : [ 'tan' ] , [ TagFunctions . NSFW ] : [ 'nsfw' ] , [ TagFunctions . PREFIX ] : [ 'prefix' ] , [ TagFunctions . RNG_CHOOSE ] : [ 'choose' ] , [ TagFunctions . RNG_RANGE ] : [ 'range' ] , [ TagFunctions . STRING_CODEBLOCK ] : [ 'code' ] , [ TagFunctions . STRING_LENGTH ] : [ 'len' , 'length' ] , [ TagFunctions . STRING_LOWER ] : [ 'lower' ] , [ TagFunctions . STRING_NEWLINE ] : [ 'newline' ] , [ TagFunctions . STRING_REPEAT ] : [ 'repeat' ] , [ TagFunctions . STRING_REPLACE ] : [ 'replace' , 'replaceregex' ] , [ TagFunctions . STRING_REVERSE ] : [ 'reverse' ] , [ TagFunctions . STRING_SUB ] : [ 'substring' ] , [ TagFunctions . STRING_UPPER ] : [ 'upper' ] , [ TagFunctions . STRING_URL_ENCODE ] : [ 'url' ] , [ TagFunctions . USER_DISCRIMINATOR ] : [ 'discrim' ] , [ TagFunctions . USER_MENTION ] : [ 'mention' ] , [ TagFunctions . USER_NAME ] : [ 'name' , 'user' ] , [ TagFunctions . USER_NICK ] : [ 'nick' ] , [ TagFunctions . USER_ID ] : [ 'id' , 'userid' ] , [ TagFunctions . USER_RANDOM ] : [ 'randuser' ] , [ TagFunctions . USER_RANDOM_ID ] : [ 'randuserid' ] , [ TagFunctions . USER_RANDOM_ONLINE ] : [ 'randonline' ] , [ TagFunctions . USER_RANDOM_ONLINE_ID ] : [ 'randonlineid' ] , [ TagFunctions . USER_RANDOM_ONLINE_TAG ] : [ 'randonlinetag' ] , [ TagFunctions . USER_RANDOM_TAG ] : [ 'randusertag' ] , [ TagFunctions . USER_TAG ] : [ 'usertag' ] , } ) ; export interface TagVariables { [ PrivateVariables . ITERATIONS_REMAINING ] : number ; [ PrivateVariables . NETWORK_REQUESTS ] : number ; [ key ] : number | string ; } export interface TagResult { files : Array < { buffer : null | Buffer ; filename : string ; spoiler ? : boolean ; url : string ; } > ; text : string ; variables : TagVariables ; } export async function parse ( context , value , args , variables = Object . create ( null ) ) { let isFirstParse = true ; if ( PrivateVariables . ITERATIONS_REMAINING in variables ) { isFirstParse = false ; } else { variables [ PrivateVariables . ITERATIONS_REMAINING ] = MAX_ITERATIONS ; } if ( ! ( PrivateVariables . NETWORK_REQUESTS in variables ) ) { variables [ PrivateVariables . NETWORK_REQUESTS ] = 0 ; } const tag = { files : [ ] , text : '' , variables } ; tag . variables [ PrivateVariables . ITERATIONS_REMAINING ] -- ; let depth = 0 ; let scriptBuffer = '' ; let position = 0 ; while ( position < value . length ) { if ( tag . variables [ PrivateVariables . ITERATIONS_REMAINING ] <= 0 ) { tag . text += value . slice ( position ) ; position = value . length ; continue ; } if ( depth === 0 ) { const nextLeftBracket = value . indexOf ( TagSymbols . BRACKET_LEFT , position ) ; if ( nextLeftBracket === - 1 ) { tag . text += value . slice ( position ) ; position = value . length ; continue ; } tag . text += value . slice ( position , nextLeftBracket ) ; position = nextLeftBracket ; } if ( MAX_NETWORK_REQUESTS <= tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ) { throw new Error ( `Tag attempted to use too many network requests (Max ${ MAX_NETWORK_REQUESTS . toLocaleString ( ) } Requests)` ) ; } let result = value . slice ( position , ++ position ) ; scriptBuffer += result ; switch ( result ) { case TagSymbols . IGNORE : { const nextValue = value . slice ( position , position + 1 ) ; if ( nextValue === TagSymbols . BRACKET_LEFT ) { depth -- ; } else if ( nextValue === TagSymbols . BRACKET_RIGHT ) { depth ++ ; } } ; break ; case TagSymbols . BRACKET_LEFT : { depth ++ ; } ; break ; case TagSymbols . BRACKET_RIGHT : { depth -- ; if ( depth <= 0 ) { let [ scriptName , arg ] = parseInnerScript ( scriptBuffer ) ; if ( TagFunctionsToString . IGNORE . includes ( scriptName ) ) { tag . text += arg ; } else if ( TagFunctionsToString . NOTE . includes ( scriptName ) ) { } else if ( TagFunctionsToString . LOGICAL_IF . includes ( scriptName ) ) { const wasValid = await ScriptTags [ TagFunctions . LOGICAL_IF ] ( context , arg , args , tag ) ; if ( ! wasValid ) { tag . text += scriptBuffer ; } } else if ( TagFunctionsToString . RNG_CHOOSE . includes ( scriptName ) ) { const wasValid = await ScriptTags [ TagFunctions . RNG_CHOOSE ] ( context , arg , args , tag ) ; if ( ! wasValid ) { tag . text += scriptBuffer ; } } else if ( TagFunctionsToString . STRING_SUB . includes ( scriptName ) ) { const wasValid = await ScriptTags [ TagFunctions . STRING_SUB ] ( context , arg , args , tag ) ; if ( ! wasValid ) { tag . text += scriptBuffer ; } } else { const argParsed = await parse ( context , arg , args , tag . variables ) ; arg = argParsed . text ; for ( let file of argParsed . files ) { tag . files . push ( file ) ; } let found = false ; for ( let TAG_FUNCTION of Object . values ( TagFunctions ) ) { if ( TagFunctionsToString [ TAG_FUNCTION ] . includes ( scriptName ) ) { found = true ; const wasValid = await ScriptTags [ TAG_FUNCTION ] ( context , arg , args , tag ) ; if ( ! wasValid ) { tag . text += scriptBuffer ; } break ; } } if ( ! found ) { tag . text += scriptBuffer ; } } scriptBuffer = '' ; } } ; break ; } } tag . text = ( tag . text + scriptBuffer ) ; if ( isFirstParse ) { tag . text = tag . text . replace ( /\u200B/g , '\n' ) ; } return tag ; } export function split ( value ) { let depth = 0 ; let position = 0 ; let text = '' ; const args = [ ] ; while ( position < value . length ) { if ( depth === 0 && ! text ) { const nextLeftBracket = value . indexOf ( TagSymbols . BRACKET_LEFT , position ) ; if ( nextLeftBracket === - 1 ) { for ( let x of value . slice ( position ) . split ( REGEX_ARGUMENT_SPLITTER ) ) { x = x . replace ( REGEX_ARGUMENT_SPLITTER_ESCAPE_REPLACEMENT , TagSymbols . SPLITTER_ARGUMENT ) ; args . push ( x ) ; } position = value . length ; continue ; } } let result = value . slice ( position , ++ position ) ; text += result ; switch ( result ) { case TagSymbols . SPLITTER_ARGUMENT : { if ( depth <= 0 ) { args . push ( text . slice ( 0 , - 1 ) ) ; text = '' ; } } ; break ; case TagSymbols . IGNORE : { const nextValue = value . slice ( position , position + 1 ) ; if ( nextValue === TagSymbols . BRACKET_LEFT ) { depth -- ; } else if ( nextValue === TagSymbols . BRACKET_RIGHT ) { depth ++ ; } else if ( nextValue === TagSymbols . SPLITTER_ARGUMENT ) { position ++ ; } } ; break ; case TagSymbols . BRACKET_LEFT : { depth ++ ; } ; break ; case TagSymbols . BRACKET_RIGHT : { depth -- ; } ; break ; } } if ( text ) { args . push ( text ) ; } return args ; } function parseInnerScript ( value ) { let scriptName ; let arg ; value = value . slice ( 1 , value . length - 1 ) . trim ( ) ; const firstSplitter = value . indexOf ( TagSymbols . SPLITTER_FUNCTION ) ; if ( firstSplitter === - 1 ) { scriptName = value . toLowerCase ( ) ; arg = '' ; } else { scriptName = value . slice ( 0 , firstSplitter ) ; arg = value . slice ( firstSplitter + 1 ) ; } return [ scriptName . toLowerCase ( ) , arg ] ; } const ScriptTags = Object . freeze ( { [ TagFunctions . ARG ] : async ( context , arg , args , tag ) : Promise < boolean > => { const index = parseInt ( arg || '0' ) ; if ( isNaN ( index ) ) { return false ; } if ( index in args ) { tag . text += args [ index ] ; } return true ; } , [ TagFunctions . ARGS ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += args . join ( ' ' ) ; return true ; } , [ TagFunctions . ARGSLEN ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += args . length ; return true ; } , [ TagFunctions . ATTACHMENT ] : async ( context , arg , args , tag , spoiler ? ) : Promise < boolean > => { const url = Parameters . url ( arg . trim ( ) ) ; tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; try { const response = await utilitiesFetchMedia ( context , { url } ) ; const filename = ( response . headers . get ( 'content-disposition' ) || '' ) . split ( ';' ) . pop ( ) ! . split ( 'filename=' ) . pop ( ) ! . slice ( 1 , - 1 ) || 'unknown.lmao' ; tag . files . push ( { buffer : await response . buffer ( ) , filename , spoiler , url , } ) ; } catch ( error ) { console . log ( error ) ; throw error ; } return true ; } , [ TagFunctions . ATTACHMENT_LAST ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; const url = await Parameters . lastImageUrl ( '' , context ) ; if ( url ) { tag . text += url ; } return true ; } , [ TagFunctions . ATTACHMENT_SPOILER ] : async ( context , arg , args , tag ) : Promise < boolean > => { return ScriptTags [ TagFunctions . ATTACHMENT ] ( context , arg , args , tag , true ) ; } , [ TagFunctions . AVATAR ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; const user = await findMemberOrUser ( arg , context ) ; if ( user ) { tag . text += user . avatarUrl ; } } else { tag . text += context . user . avatarUrl ; } return true ; } , [ TagFunctions . CHANNEL ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { const channel = await findChannel ( arg , context ) ; if ( channel ) { tag . text += channel . name ; } } else { tag . text += ( context . channel && context . guildId ) ? context . channel . name : 'Direct Message' ; } return true ; } , [ TagFunctions . CHANNEL_ID ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { const channel = await findChannel ( arg , context ) ; if ( channel ) { tag . text += channel . id ; } } else { tag . text += context . channelId ; } return true ; } , [ TagFunctions . CHANNEL_MENTION ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { const channel = await findChannel ( arg , context ) ; if ( channel ) { tag . text += channel . mention ; } } else { tag . text += `<#${ context . channelId }>` ; } return true ; } , [ TagFunctions . CHANNEL_RANDOM ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { const channel = randomFromIterator < Structures . Channel > ( context . guild . channels . length , context . guild . channels . values ( ) ) ; tag . text += ( channel ) ? channel . name : '' ; } else { tag . text += 'Direct Message' ; } return true ; } , [ TagFunctions . CHANNEL_RANDOM_ID ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { const channel = randomFromIterator < Structures . Channel > ( context . guild . channels . length , context . guild . channels . values ( ) ) ; tag . text += ( channel ) ? channel . id : context . channelId ; } else { tag . text += context . channelId ; } return true ; } , [ TagFunctions . CHANNEL_RANDOM_MENTION ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { const channel = randomFromIterator < Structures . Channel > ( context . guild . channels . length , context . guild . channels . values ( ) ) ; tag . text += ( channel ) ? channel . mention : `<#${ context . channelId }>` ; } else { tag . text += `<#${ context . channelId }>` ; } return true ; } , [ TagFunctions . DISCORD ] : async ( context , arg , args , tag ) : Promise < boolean > => { return false ; } , [ TagFunctions . DOWNLOAD ] : async ( context , arg , args , tag ) : Promise < boolean > => { const url = Parameters . url ( arg . trim ( ) ) ; tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; try { const response = await utilitiesFetchText ( context , { url } ) ; tag . text += await response . text ( ) ; } catch ( error ) { console . log ( error ) ; throw error ; } return true ; } , [ TagFunctions . EVAL ] : async ( context , arg , args , tag ) : Promise < boolean > => { const argParsed = await parse ( context , arg , args , tag . variables ) ; tag . text += argParsed . text ; for ( let file of argParsed . files ) { tag . files . push ( file ) ; } return true ; } , [ TagFunctions . GUILD ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { tag . text += context . guild . name ; } else { tag . text += 'Direct Message' ; } return true ; } , [ TagFunctions . GUILD_COUNT ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { tag . text += context . guild . memberCount ; } else { tag . text += '2' ; } return true ; } , [ TagFunctions . GUILD_ID ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += ( context . guildId ) ? context . guildId : '0' ; return true ; } , [ TagFunctions . HASTEBIN ] : async ( context , arg , args , tag ) : Promise < boolean > => { return false ; } , [ TagFunctions . IMAGE ] : async ( context , arg , args , tag ) : Promise < boolean > => { return false ; } , [ TagFunctions . IMAGESCRIPT ] : async ( context , arg , args , tag ) : Promise < boolean > => { return false ; } , [ TagFunctions . LOGICAL_DELETE ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg . startsWith ( PRIVATE_VARIABLE_PREFIX ) ) { throw new Error ( `Tried to delete a private variable, cannot start with '${ PRIVATE_VARIABLE_PREFIX }'.` ) ; } if ( MAX_VARIABLE_KEY_LENGTH < arg . length ) { throw new Error ( `Variable cannot be more than ${ MAX_VARIABLE_KEY_LENGTH } characters` ) ; } delete tag . variables [ arg ] ; return true ; } , [ TagFunctions . LOGICAL_GET ] : async ( context , arg , args , tag ) : Promise < boolean > => { const key = arg . trim ( ) ; if ( key . startsWith ( PRIVATE_VARIABLE_PREFIX ) ) { throw new Error ( `Tried to access a private variable, cannot start with '${ PRIVATE_VARIABLE_PREFIX }'.` ) ; } if ( MAX_VARIABLE_KEY_LENGTH < key . length ) { throw new Error ( `Variable cannot be more than ${ MAX_VARIABLE_KEY_LENGTH } characters` ) ; } if ( key in tag . variables ) { tag . text += tag . variables [ key ] ; } return true ; } , [ TagFunctions . LOGICAL_IF ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg . includes ( TagSymbols . SPLITTER_ARGUMENT ) ) { return false ; } const [ value1 , comparison , value2 , then , elseValue ] = split ( arg ) ; if ( value1 === undefined || comparison === undefined || value2 === undefined || then === undefined ) { return false ; } if ( ! then . startsWith ( 'then:' ) ) { return false ; } if ( ! TAG_IF_COMPARISONS . includes ( comparison as TagIfComparisons ) ) { return false ; } const values = [ value1 , value2 ] ; for ( let i in values ) { const x = values [ i ] ; if ( x . includes ( TagSymbols . BRACKET_LEFT ) ) { const argParsed = await parse ( context , x , args , tag . variables ) ; for ( let file of argParsed . files ) { tag . files . push ( file ) ; } values [ i ] = argParsed . text ; } else { values [ i ] = x ; } } let compared ; switch ( comparison ) { case TagIfComparisons . EQUAL : { compared = values [ 0 ] === values [ 1 ] ; } ; break ; case TagIfComparisons . GREATER_THAN : case TagIfComparisons . GREATER_THAN_OR_EQUAL : case TagIfComparisons . LESS_THAN : case TagIfComparisons . LESS_THAN_OR_EQUAL : case TagIfComparisons . TILDE : { try { const [ int1 , int2 ] = values . map ( BigInt ) as [ bigint , bigint ] ; switch ( comparison ) { case TagIfComparisons . GREATER_THAN : { compared = int1 > int2 ; } ; break ; case TagIfComparisons . GREATER_THAN_OR_EQUAL : { compared = int1 >= int2 ; } ; break ; case TagIfComparisons . LESS_THAN : { compared = int1 < int2 ; } ; break ; case TagIfComparisons . LESS_THAN_OR_EQUAL : { compared = int1 <= int2 ; } ; break ; case TagIfComparisons . TILDE : { compared = ~ int1 == ~ int2 ; } ; break ; } } catch ( error ) { if ( comparison === TagIfComparisons . TILDE ) { compared = values [ 0 ] . toLowerCase ( ) === values [ 1 ] . toLowerCase ( ) ; } else { compared = false ; } } } ; break ; } if ( compared === undefined ) { return false ; } const text = ( compared ) ? then . slice ( 5 ) : ( elseValue || '' ) . slice ( 5 ) ; if ( text . includes ( TagSymbols . BRACKET_LEFT ) ) { const argParsed = await parse ( context , text , args , tag . variables ) ; for ( let file of argParsed . files ) { tag . files . push ( file ) ; } tag . text += argParsed . text ; } else { tag . text += text ; } return true ; } , [ TagFunctions . LOGICAL_SET ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg . includes ( TagSymbols . SPLITTER_ARGUMENT ) ) { return false ; } let [ key , ... value ] = split ( arg ) ; key = key . trim ( ) ; if ( key . startsWith ( PRIVATE_VARIABLE_PREFIX ) ) { throw new Error ( `Tried to set a private variable, cannot start with '${ PRIVATE_VARIABLE_PREFIX }'.` ) ; } if ( MAX_VARIABLE_KEY_LENGTH < key . length ) { throw new Error ( `Variable cannot be more than ${ MAX_VARIABLE_KEY_LENGTH } characters` ) ; } if ( ! ( key in tag . variables ) ) { if ( MAX_VARIABLES <= Object . keys ( tag . variables ) . filter ( ( key ) => ! key . startsWith ( PRIVATE_VARIABLE_PREFIX ) ) . length ) { throw new Error ( `Reached max variable amount (Max ${ MAX_VARIABLES . toLocaleString ( ) } Variables)` ) ; } } tag . variables [ key ] = value . join ( TagSymbols . SPLITTER_ARGUMENT ) . slice ( 0 , MAX_VARIABLE_LENGTH ) . trim ( ) ; return true ; } , [ TagFunctions . MATH ] : async ( context , arg , args , tag ) : Promise < boolean > => { return false ; } , [ TagFunctions . MATH_ABS ] : async ( context , arg , args , tag ) : Promise < boolean > => { const value = parseInt ( arg ) ; if ( isNaN ( value ) ) { return false ; } tag . text += Math . abs ( value ) ; return true ; } , [ TagFunctions . MATH_COS ] : async ( context , arg , args , tag ) : Promise < boolean > => { const value = parseInt ( arg ) ; if ( isNaN ( value ) ) { return false ; } tag . text += Math . cos ( value ) ; return true ; } , [ TagFunctions . MATH_E ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += Math . E ; return true ; } , [ TagFunctions . MATH_MAX ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg ) { return false ; } const numbers = split ( arg ) . map ( ( x ) => x . split ( '.' ) . shift ( ) ! ) ; if ( numbers . some ( ( x ) => isNaN ( x as any ) ) ) { return false ; } let value ; if ( numbers . length === 1 ) { value = BigInt ( numbers [ 0 ] ) ; } else { value = bigIntMax ( ... numbers . map ( BigInt ) ) ; } tag . text += value ; return true ; } , [ TagFunctions . MATH_MIN ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg ) { return false ; } const numbers = split ( arg ) . map ( ( x ) => x . split ( '.' ) . shift ( ) ! ) ; if ( numbers . some ( ( x ) => isNaN ( x as any ) ) ) { return false ; } let value ; if ( numbers . length === 1 ) { value = BigInt ( numbers [ 0 ] ) ; } else { value = bigIntMin ( ... numbers . map ( BigInt ) ) ; } tag . text += value ; return true ; } , [ TagFunctions . MATH_PI ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += Math . PI ; return true ; } , [ TagFunctions . MATH_SIN ] : async ( context , arg , args , tag ) : Promise < boolean > => { const value = parseInt ( arg ) ; if ( isNaN ( value ) ) { return false ; } tag . text += Math . sin ( value ) ; return true ; } , [ TagFunctions . MATH_TAN ] : async ( context , arg , args , tag ) : Promise < boolean > => { const value = parseInt ( arg ) ; if ( isNaN ( value ) ) { return false ; } tag . text += Math . tan ( value ) ; return true ; } , [ TagFunctions . NSFW ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( DefaultParameters . safe ( context ) ) { throw new Error ( 'Cannot use a NSFW tag here!' ) ; } return true ; } , [ TagFunctions . PREFIX ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context instanceof Interaction . InteractionContext ) { tag . text += '/' ; } else { tag . text += context . prefix ; } return true ; } , [ TagFunctions . RNG_CHOOSE ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg ) { return false ; } let value ; if ( arg . includes ( TagSymbols . SPLITTER_ARGUMENT ) ) { const choices = split ( arg ) ; value = randomFromArray < string > ( choices ) ; } else { value = arg ; } if ( value . includes ( TagSymbols . BRACKET_LEFT ) ) { const argParsed = await parse ( context , value , args , tag . variables ) ; tag . text += argParsed . text ; for ( let file of argParsed . files ) { tag . files . push ( file ) ; } } else { tag . text += value ; } return true ; } , [ TagFunctions . RNG_RANGE ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg ) { return false ; } let firstValue = '0' , secondValue = '0' ; if ( ! arg . includes ( TagSymbols . SPLITTER_ARGUMENT ) ) { secondValue = arg . split ( '.' ) . shift ( ) as string ; } else { const firstSplitter = arg . indexOf ( TagSymbols . SPLITTER_ARGUMENT ) ; firstValue = arg . slice ( 0 , firstSplitter ) . split ( '.' ) . shift ( ) as string ; secondValue = arg . slice ( firstSplitter + 1 ) . split ( '.' ) . shift ( ) as string ; } if ( isNaN ( firstValue as any ) || isNaN ( secondValue as any ) ) { return false ; } tag . text += bigIntGenerateBetween ( BigInt ( firstValue ) , BigInt ( secondValue ) ) ; return true ; } , [ TagFunctions . STRING_CODEBLOCK ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += Markup . codeblock ( arg ) ; return true ; } , [ TagFunctions . STRING_NEWLINE ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { const amount = parseInt ( arg ) ; if ( isNaN ( amount ) ) { return false ; } tag . text += '\u200b' . repeat ( amount ) ; } else { tag . text += '\u200b' ; } return true ; } , [ TagFunctions . STRING_LENGTH ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += arg . length ; return true ; } , [ TagFunctions . STRING_LOWER ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += arg . toLowerCase ( ) ; return true ; } , [ TagFunctions . STRING_REPEAT ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg . includes ( TagSymbols . SPLITTER_ARGUMENT ) ) { return false ; } const [ amountText , ... value ] = split ( arg ) ; const amount = parseInt ( amountText . trim ( ) ) ; if ( isNaN ( amount ) ) { return false ; } if ( MAX_REPEAT_AMOUNT < amount ) { throw new Error ( `really man, more than ${ MAX_REPEAT_AMOUNT } repeats?` ) ; } const text = value . join ( TagSymbols . SPLITTER_ARGUMENT ) . trim ( ) ; if ( MAX_STRING_LENGTH < text . length * amount ) { throw new Error ( 'ok buddy, dont repeat too much text man' ) ; } tag . text += text . repeat ( amount ) ; return true ; } , [ TagFunctions . STRING_REPLACE ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg . includes ( TagSymbols . SPLITTER_ARGUMENT ) ) { return false ; } let [ regex , replaceWith , ... sourceText ] = split ( arg ) ; if ( regex === undefined || replaceWith === undefined || ! sourceText . length ) { return false ; } if ( replaceWith . startsWith ( 'with:' ) ) { replaceWith = replaceWith . slice ( 5 ) ; } let source = sourceText . join ( TagSymbols . SPLITTER_ARGUMENT ) ; if ( source . startsWith ( 'in:' ) ) { source = source . slice ( 3 ) ; } try { tag . text += runInNewContext ( `source.replace(new RegExp(regex, 'gi'), replaceWith);` , { regex : regex . trim ( ) , source , replaceWith , } , { timeout : MAX_REGEX_TIME } ) ; } catch { throw new Error ( 'text replacing errored or timed out' ) ; } return true ; } , [ TagFunctions . STRING_REVERSE ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += arg . split ( '' ) . reverse ( ) . join ( '' ) ; return true ; } , [ TagFunctions . STRING_SUB ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( ! arg . includes ( TagSymbols . SPLITTER_ARGUMENT ) ) { return false ; } const [ text , startText , endText ] = split ( arg ) ; if ( text === undefined || startText === undefined ) { return false ; } let start ; { const parsed = await parse ( context , startText , args , tag . variables ) ; start = parseInt ( parsed . text . trim ( ) ) ; for ( let file of parsed . files ) { tag . files . push ( file ) ; } if ( isNaN ( start ) ) { return false ; } } let end ; if ( endText !== undefined ) { const parsed = await parse ( context , endText , args , tag . variables ) ; end = parseInt ( parsed . text . trim ( ) ) ; for ( let file of parsed . files ) { tag . files . push ( file ) ; } if ( isNaN ( end ) ) { return false ; } } const argParsed = await parse ( context , text , args , tag . variables ) ; tag . text += argParsed . text . substring ( start , end ) ; for ( let file of argParsed . files ) { tag . files . push ( file ) ; } return true ; } , [ TagFunctions . STRING_UPPER ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += arg . toUpperCase ( ) ; return true ; } , [ TagFunctions . STRING_URL_ENCODE ] : async ( context , arg , args , tag ) : Promise < boolean > => { tag . text += encodeURIComponent ( arg ) ; return true ; } , [ TagFunctions . USER_DISCRIMINATOR ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; const user = await findMemberOrUser ( arg , context ) ; if ( user ) { tag . text += user . discriminator ; } } else { tag . text += context . user . discriminator ; } return true ; } , [ TagFunctions . USER_MENTION ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; const user = await findMemberOrUser ( arg , context ) ; if ( user ) { tag . text += user . mention ; } } else { tag . text += context . user . mention ; } return true ; } , [ TagFunctions . USER_NAME ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; const user = await findMemberOrUser ( arg , context ) ; if ( user ) { tag . text += user . username ; } } else { tag . text += context . user . username ; } return true ; } , [ TagFunctions . USER_NICK ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; const user = await findMemberOrUser ( arg , context ) ; if ( user ) { tag . text += user . name ; } } else { tag . text += ( context . member ) ? context . member . name : context . user . name ; } return true ; } , [ TagFunctions . USER_ID ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; const user = await findMemberOrUser ( arg , context ) ; if ( user ) { tag . text += user . id ; } } else { tag . text += context . user . id ; } return true ; } , [ TagFunctions . USER_RANDOM ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { if ( ! context . guild . isReady ) { await context . guild . requestMembers ( { limit : 0 , presences : true , query : '' , timeout : 10000 , } ) ; } const member = randomFromIterator < Structures . Member > ( context . guild . members . length , context . guild . members . values ( ) ) ; tag . text += member . name ; } else { const user = randomFromArray < Structures . User > ( [ context . user , context . client . user as Structures . User ] ) ; tag . text += user . name ; } return true ; } , [ TagFunctions . USER_RANDOM_ID ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { if ( ! context . guild . isReady ) { await context . guild . requestMembers ( { limit : 0 , presences : true , query : '' , timeout : 10000 , } ) ; } const member = randomFromIterator < Structures . Member > ( context . guild . members . length , context . guild . members . values ( ) ) ; tag . text += member . id ; } else { const user = randomFromArray < Structures . User > ( [ context . user , context . client . user as Structures . User ] ) ; tag . text += user . id ; } return true ; } , [ TagFunctions . USER_RANDOM_ONLINE ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { const member = randomFromArray < Structures . Member > ( context . guild . members . filter ( ( member ) => ! ! member . presence ) ) ; tag . text += member . name ; } else { const user = randomFromArray < Structures . User > ( [ context . user , context . client . user as Structures . User ] ) ; tag . text += user . name ; } return true ; } , [ TagFunctions . USER_RANDOM_ONLINE_ID ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { const member = randomFromArray < Structures . Member > ( context . guild . members . filter ( ( member ) => ! ! member . presence ) ) ; tag . text += member . id ; } else { const user = randomFromArray < Structures . User > ( [ context . user , context . client . user as Structures . User ] ) ; tag . text += user . id ; } return true ; } , [ TagFunctions . USER_RANDOM_ONLINE_TAG ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { const member = randomFromArray < Structures . Member > ( context . guild . members . filter ( ( member ) => ! ! member . presence ) ) ; tag . text += member . toString ( ) ; } else { const user = randomFromArray < Structures . User > ( [ context . user , context . client . user as Structures . User ] ) ; tag . text += user . toString ( ) ; } return true ; } , [ TagFunctions . USER_RANDOM_TAG ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( context . guild ) { if ( ! context . guild . isReady ) { await context . guild . requestMembers ( { limit : 0 , presences : true , query : '' , timeout : 10000 , } ) ; } const member = randomFromIterator < Structures . Member > ( context . guild . members . length , context . guild . members . values ( ) ) ; tag . text += member . toString ( ) ; } else { const user = randomFromArray < Structures . User > ( [ context . user , context . client . user as Structures . User ] ) ; tag . text += user . toString ( ) ; } return true ; } , [ TagFunctions . USER_TAG ] : async ( context , arg , args , tag ) : Promise < boolean > => { if ( arg ) { tag . variables [ PrivateVariables . NETWORK_REQUESTS ] ++ ; const user = await findMemberOrUser ( arg , context ) ; if ( user ) { tag . text += user . toString ( ) ; } } else { tag . text += context . user . toString ( ) ; } return true ; } , } ) ;