2021-03-28 14:34:31 +00:00
import { GuildEmoji } from "discord.js" ;
2020-12-15 01:44:28 +00:00
import { MessageEmbed } from "discord.js" ;
import Command from "../../core/command" ;
2021-03-30 10:25:07 +00:00
import { split } from "../../core/lib" ;
import { paginate } from "../../core/libd" ;
2021-03-28 17:49:45 +00:00
import vm from "vm" ;
2021-03-30 10:25:07 +00:00
import { TextChannel } from "discord.js" ;
import { DMChannel } from "discord.js" ;
import { NewsChannel } from "discord.js" ;
import { User } from "discord.js" ;
2021-03-28 17:49:45 +00:00
const REGEX_TIMEOUT_MS = 1000 ;
2020-10-20 12:04:13 +00:00
export default new Command ( {
2020-12-15 01:44:28 +00:00
description : "Lists all emotes the bot has in it's registry," ,
2021-03-28 14:34:31 +00:00
usage : "<regex pattern> (-flags)" ,
2021-03-30 10:25:07 +00:00
async run ( $ ) {
displayEmoteList ( $ . client . emojis . cache . array ( ) , $ . channel , $ . author ) ;
2021-03-28 14:34:31 +00:00
} ,
any : new Command ( {
description :
"Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i" ,
2021-03-30 10:25:07 +00:00
async run ( $ ) {
2021-03-28 14:34:31 +00:00
// If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward)
if ( $ . args . length === 1 && /^\d{17,19}$/ . test ( $ . args [ 0 ] ) ) {
const guildID : string = $ . args [ 0 ] ;
2021-03-30 10:25:07 +00:00
displayEmoteList (
$ . client . emojis . cache . filter ( ( emote ) = > emote . guild . id === guildID ) . array ( ) ,
$ . channel ,
$ . author
) ;
2021-03-28 14:34:31 +00:00
} else {
2021-03-28 17:49:45 +00:00
// Otherwise, search via a regex pattern
let flags : string | undefined = undefined ;
if ( /^-[dgimsuy]{1,7}$/ . test ( $ . args [ $ . args . length - 1 ] ) ) {
flags = $ . args . pop ( ) . substring ( 1 ) ;
}
let emoteCollection = $ . client . emojis . cache . array ( ) ;
// Creates a sandbox to stop a regular expression if it takes too much time to search.
// To avoid passing in a giant data structure, I'll just pass in the structure {[id: string]: [name: string]}.
//let emotes: {[id: string]: string} = {};
let emotes = new Map < string , string > ( ) ;
for ( const emote of emoteCollection ) {
emotes . set ( emote . id , emote . name ) ;
}
// The result will be sandbox.emotes because it'll be modified in-place.
const sandbox = {
regex : new RegExp ( $ . args . join ( " " ) , flags ) ,
emotes
} ;
const context = vm . createContext ( sandbox ) ;
2021-03-28 14:34:31 +00:00
2021-03-28 17:49:45 +00:00
if ( vm . isContext ( sandbox ) ) {
// Restrict an entire query to the timeout specified.
try {
const script = new vm . Script (
"for(const [id, name] of emotes.entries()) if(!regex.test(name)) emotes.delete(id);"
) ;
script . runInContext ( context , { timeout : REGEX_TIMEOUT_MS } ) ;
emotes = sandbox . emotes ;
2021-03-28 18:58:10 +00:00
emoteCollection = emoteCollection . filter ( ( emote ) = > emotes . has ( emote . id ) ) ; // Only allow emotes that haven't been deleted.
2021-03-30 10:25:07 +00:00
displayEmoteList ( emoteCollection , $ . channel , $ . author ) ;
2021-03-28 17:49:45 +00:00
} catch ( error ) {
if ( error . code === "ERR_SCRIPT_EXECUTION_TIMEOUT" ) {
$ . channel . send (
` The regular expression you entered exceeded the time limit of ${ REGEX_TIMEOUT_MS } milliseconds. `
) ;
} else {
throw new Error ( error ) ;
}
}
} else {
$ . channel . send ( "Failed to initialize sandbox." ) ;
}
2021-03-28 14:34:31 +00:00
}
}
} )
} ) ;
2021-03-30 10:25:07 +00:00
async function displayEmoteList ( emotes : GuildEmoji [ ] , channel : TextChannel | DMChannel | NewsChannel , author : User ) {
2021-03-28 14:34:31 +00:00
emotes . sort ( ( a , b ) = > {
const first = a . name . toLowerCase ( ) ;
const second = b . name . toLowerCase ( ) ;
if ( first > second ) return 1 ;
else if ( first < second ) return - 1 ;
else return 0 ;
} ) ;
2021-03-30 10:25:07 +00:00
const sections = split ( emotes , 20 ) ;
2021-03-28 14:34:31 +00:00
const pages = sections . length ;
2021-03-30 12:16:31 +00:00
const embed = new MessageEmbed ( ) . setColor ( "AQUA" ) ;
2021-03-28 14:34:31 +00:00
// Gather the first page (if it even exists, which it might not if there no valid emotes appear)
if ( pages > 0 ) {
2021-03-30 12:16:31 +00:00
paginate ( channel , author . id , pages , ( page , hasMultiplePages ) = > {
embed . setTitle ( hasMultiplePages ? ` **Emotes** (Page ${ page + 1 } of ${ pages } ) ` : "**Emotes**" ) ;
2020-12-15 01:44:28 +00:00
2021-03-30 12:16:31 +00:00
let desc = "" ;
for ( const emote of sections [ page ] ) {
desc += ` ${ emote } ${ emote . name } (** ${ emote . guild . name } **) \ n ` ;
}
embed . setDescription ( desc ) ;
2021-03-28 14:34:31 +00:00
2021-03-30 12:16:31 +00:00
return embed ;
} ) ;
2021-03-28 14:34:31 +00:00
} else {
2021-03-30 10:25:07 +00:00
channel . send ( "No valid emotes found by that query." ) ;
2020-12-15 01:44:28 +00:00
}
2021-03-28 14:34:31 +00:00
}