2023-11-16 11:33:11 +01:00
import { AutocompleteInteraction , CommandInteraction , SlashCommandBuilder } from 'discord.js' ;
2023-11-12 20:44:03 +01:00
import { Counter , CounterUserLink , db } from '../lib/db' ;
2023-11-15 11:57:20 +01:00
import { counterAutocomplete , counterConfigs , findCounter , getCounterConfigRaw , getOptions , parseConfig , setCounterConfig , toStringConfig , updateCounter } from '../lib/rpg/counter' ;
2023-11-13 21:54:09 +01:00
import { outdent } from 'outdent' ;
2023-11-15 11:57:20 +01:00
import { formatItem , formatItems , getItem , itemAutocomplete } from '../lib/rpg/items' ;
2023-11-16 11:33:11 +01:00
import { Command } from '../types/index' ;
2023-11-22 14:45:55 +01:00
import { set } from '../lib/autocomplete' ;
2023-11-12 20:44:03 +01:00
function extendOption ( t : string ) {
return { name : t , value : t } ;
}
2023-11-13 21:54:09 +01:00
const help = new Map ( [
[ 'message templates' , outdent `
2023-11-15 11:03:01 +01:00
When using \ ` messageTemplate \` , \` messageTemplateIncrease \` , \` messageTemplateDecrease \` , \` messageTemplatePut \` or \` messageTemplateTake \` , you are providing a **template string**.
2023-11-13 21:54:09 +01:00
A template string is a * * specially - formatted * * string with placeholder values . For instance , a template string like so :
> * * % user * * has % action the counter by * * % amt * * .
Could be formatted as such :
> * * @oatmealine * * has incremented the counter by * * 1 * * .
Here are the keys you can use as special replacement values :
- \ ` %user \` - The user that changed the counter
- \ ` %action \` - "decremented" or "incremented"
- \ ` %amt \` - Amount by which the counter has changed
- \ ` %total \` - The new counter value
` ]
] ) ;
2023-11-16 11:33:11 +01:00
export default {
2023-11-12 20:44:03 +01:00
data : new SlashCommandBuilder ( )
. setName ( 'counter' )
. setDescription ( '[ADMIN] Counter management' )
. addSubcommandGroup ( grp = >
grp
. setName ( 'allowlist' )
. setDescription ( '[ADMIN] Counter allowlist management' )
. addSubcommand ( sub = >
sub
. setName ( 'add' )
. setDescription ( '[ADMIN] Add a user to the allowlist' )
. addStringOption ( opt = >
opt
. setName ( 'type' )
. setDescription ( 'The counter to operate on' )
. setRequired ( true )
. setAutocomplete ( true )
)
. addStringOption ( opt = >
opt
. setName ( 'usertype' )
. setDescription ( 'Type of user in this predicament' )
. setChoices ( . . . [ 'consumer' , 'producer' ] . map ( extendOption ) )
. setRequired ( true )
)
. addUserOption ( opt = >
opt
. setName ( 'user' )
. setDescription ( 'The user to add' )
. setRequired ( true )
)
)
. addSubcommand ( sub = >
sub
. setName ( 'remove' )
. setDescription ( '[ADMIN] Remove a user from the allowlist' )
. addStringOption ( opt = >
opt
. setName ( 'type' )
. setDescription ( 'The counter to operate on' )
. setRequired ( true )
. setAutocomplete ( true )
)
. addStringOption ( opt = >
opt
. setName ( 'usertype' )
. setDescription ( 'Type of user in this predicament' )
. setChoices ( . . . [ 'consumer' , 'producer' ] . map ( extendOption ) )
. setRequired ( true )
)
. addUserOption ( opt = >
opt
. setName ( 'user' )
. setDescription ( 'The user to remove' )
. setRequired ( true )
)
)
. addSubcommand ( sub = >
sub
. setName ( 'toggle' )
. setDescription ( '[ADMIN] Enable or disable the allowlist.' )
. addStringOption ( opt = >
opt
. setName ( 'type' )
. setDescription ( 'The counter to operate on' )
. setRequired ( true )
. setAutocomplete ( true )
)
. addStringOption ( opt = >
opt
. setName ( 'usertype' )
. setDescription ( 'Type of user in this predicament' )
. setChoices ( . . . [ 'consumer' , 'producer' ] . map ( extendOption ) )
. setRequired ( true )
)
. addBooleanOption ( opt = >
opt
. setName ( 'enabled' )
. setDescription ( 'Enable or disable the allowlist' )
. setRequired ( true )
)
)
. addSubcommand ( sub = >
sub
. setName ( 'list' )
. setDescription ( '[ADMIN] List people in the allowlist' )
. addStringOption ( opt = >
opt
. setName ( 'type' )
. setDescription ( 'The counter to operate on' )
. setRequired ( true )
. setAutocomplete ( true )
)
. addStringOption ( opt = >
opt
. setName ( 'usertype' )
. setDescription ( 'Type of user in this predicament' )
. setChoices ( . . . [ 'consumer' , 'producer' ] . map ( extendOption ) )
. setRequired ( true )
)
)
)
. addSubcommand ( sub = >
sub
. setName ( 'create' )
. setDescription ( '[ADMIN] Create a counter' )
. addChannelOption ( option = >
option
. setName ( 'channel' )
. setDescription ( 'Channel to put updates into' )
. setRequired ( true )
)
. addStringOption ( option = >
option
. setName ( 'key' )
2023-11-15 11:08:01 +01:00
. setDescription ( 'Give your counter a simple name' )
2023-11-12 20:44:03 +01:00
. setRequired ( true )
)
. addStringOption ( option = >
option
. setName ( 'emoji' )
. setDescription ( 'An emoji or symbol or something to represent the counter' )
. setRequired ( true )
2023-11-13 16:10:33 +01:00
. setMaxLength ( 100 )
2023-11-12 20:44:03 +01:00
)
. addNumberOption ( option = >
option
. setName ( 'value' )
. setDescription ( 'Initial value to start with' )
)
)
2023-11-13 16:10:33 +01:00
. addSubcommand ( sub = >
sub
. setName ( 'set' )
. setDescription ( '[ADMIN] Configure a counter' )
. addStringOption ( opt = >
opt
. setName ( 'type' )
. setDescription ( 'The counter to operate on' )
. setRequired ( true )
. setAutocomplete ( true )
)
. addStringOption ( opt = >
opt
. setName ( 'key' )
. setDescription ( 'The config name' )
. setRequired ( true )
. setChoices ( . . . [ . . . counterConfigs . keys ( ) ] . map ( extendOption ) )
)
. addStringOption ( opt = >
opt
. setName ( 'value' )
. setDescription ( 'The new value' )
. setRequired ( true )
. setAutocomplete ( true )
. setMaxLength ( 100 )
)
)
2023-11-12 20:44:03 +01:00
. addSubcommand ( sub = >
sub
. setName ( 'delete' )
. setDescription ( '[ADMIN] Delete a counter' )
. addStringOption ( opt = >
opt
. setName ( 'type' )
. setDescription ( 'The counter to operate on' )
. setRequired ( true )
. setAutocomplete ( true )
)
)
2023-11-13 17:32:37 +01:00
. addSubcommand ( sub = >
sub
. setName ( 'list' )
. setDescription ( '[ADMIN] List every counter in this server' )
)
2023-11-13 21:54:09 +01:00
. addSubcommand ( sub = >
sub
. setName ( 'help' )
. setDescription ( 'Help guides for working with counters' )
. addStringOption ( opt = >
opt
. setName ( 'topic' )
. setDescription ( 'The topic to get help on' )
. setRequired ( true )
. setChoices ( . . . [ . . . help . keys ( ) ] . map ( extendOption ) )
)
)
2023-11-15 11:03:01 +01:00
. addSubcommand ( sub = >
sub
. setName ( 'link' )
. setDescription ( '[ADMIN] THIS IS IRREVERSIBLE! Attach an item to this counter, letting you take or put items in.' )
. addStringOption ( opt = >
opt
. setName ( 'type' )
. setDescription ( 'The counter to operate on' )
. setRequired ( true )
. setAutocomplete ( true )
)
. addStringOption ( opt = >
opt
. setName ( 'item' )
. setDescription ( 'The item' )
. setAutocomplete ( true )
. setRequired ( true )
)
)
2023-11-12 20:44:03 +01:00
. setDefaultMemberPermissions ( '0' )
. setDMPermission ( false ) ,
2023-11-16 11:33:11 +01:00
execute : async ( interaction : CommandInteraction ) = > {
2023-11-12 20:44:03 +01:00
if ( ! interaction . isChatInputCommand ( ) ) return ;
await interaction . deferReply ( { ephemeral : true } ) ;
const subcommand = interaction . options . getSubcommand ( true ) ;
const group = interaction . options . getSubcommandGroup ( ) ;
if ( group === 'allowlist' ) {
const type = interaction . options . getString ( 'type' ) ! ;
let counter ;
try {
2023-11-13 17:29:49 +01:00
counter = await findCounter ( type , interaction . guildId ! ) ;
2023-11-12 20:44:03 +01:00
} catch ( err ) {
2023-11-15 11:03:01 +01:00
return interaction . followUp ( 'No such counter!' ) ;
2023-11-12 20:44:03 +01:00
}
if ( subcommand === 'add' ) {
const user = interaction . options . getUser ( 'user' , true ) ;
const userType = interaction . options . getString ( 'usertype' , true ) ;
const link = await db < CounterUserLink > ( 'counterUserLink' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id )
2023-11-12 20:44:03 +01:00
. where ( 'user' , user . id )
. where ( 'producer' , userType === 'producer' )
. first ( ) ;
if ( link ) {
await interaction . followUp ( {
content : ` <@ ${ user . id } > is already in the ${ counter . emoji } ** ${ userType } ** allowlist! `
} ) ;
return ;
}
await db < CounterUserLink > ( 'counterUserLink' )
. insert ( {
2023-11-13 17:29:49 +01:00
'id' : counter . id ,
2023-11-12 20:44:03 +01:00
'user' : user . id ,
'producer' : userType === 'producer'
} ) ;
await interaction . followUp ( {
content : ` <@ ${ user . id } > added to the ${ counter . emoji } ** ${ userType } ** allowlist. `
} ) ;
} else if ( subcommand === 'remove' ) {
const user = interaction . options . getUser ( 'user' , true ) ;
const userType = interaction . options . getString ( 'usertype' , true ) ;
const link = await db < CounterUserLink > ( 'counterUserLink' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id )
2023-11-12 20:44:03 +01:00
. where ( 'user' , user . id )
. where ( 'producer' , userType === 'producer' )
. first ( ) ;
2023-11-15 11:03:01 +01:00
if ( ! link ) return interaction . followUp ( ` <@ ${ user . id } > is not in the ${ counter . emoji } ** ${ userType } ** allowlist! ` ) ;
2023-11-12 20:44:03 +01:00
await interaction . followUp ( {
content : ` <@ ${ user . id } > has been removed from the ${ counter . emoji } ** ${ userType } ** allowlist. `
} ) ;
} else if ( subcommand === 'toggle' ) {
const enabled = interaction . options . getBoolean ( 'enabled' , true ) ;
const userType = interaction . options . getString ( 'usertype' , true ) ;
if ( userType === 'producer' ) {
await db < Counter > ( 'counters' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id )
2023-11-12 20:44:03 +01:00
. update ( {
'allowlistProducer' : enabled
} ) ;
} else {
await db < Counter > ( 'counters' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id )
2023-11-12 20:44:03 +01:00
. update ( {
2023-11-12 21:03:04 +01:00
'allowlistConsumer' : enabled
2023-11-12 20:44:03 +01:00
} ) ;
}
await interaction . followUp ( {
content : ` ${ counter . emoji } ${ userType . slice ( 0 , 1 ) . toUpperCase ( ) + userType . slice ( 1 ) } allowlist is now ** ${ enabled ? 'enabled' : 'disabled' } **. `
} ) ;
} else if ( subcommand === 'list' ) {
const userType = interaction . options . getString ( 'usertype' , true ) ;
const users = await db < CounterUserLink > ( 'counterUserLink' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id )
2023-11-12 20:44:03 +01:00
. where ( 'producer' , userType === 'producer' ) ;
const enabled = ( userType === 'producer' ) ? counter.allowlistProducer : counter.allowlistConsumer ;
await interaction . followUp ( {
content : ` ${ counter . emoji } ${ userType . slice ( 0 , 1 ) . toUpperCase ( ) + userType . slice ( 1 ) } s: \ n ${ users . map ( u = > ` - <@ ${ u . user } > ` ) } \ nThe ${ userType } allowlist is currently ** ${ enabled ? 'enabled' : 'disabled' } **. `
} ) ;
}
} else {
if ( subcommand === 'create' ) {
const channel = interaction . options . getChannel ( 'channel' , true ) ;
const key = interaction . options . getString ( 'key' , true ) ;
const emoji = interaction . options . getString ( 'emoji' , true ) ;
const value = interaction . options . getNumber ( 'value' ) || 0 ;
const guild = interaction . guildId ! ;
2023-11-13 17:29:49 +01:00
const [ counter ] = await db < Counter > ( 'counters' )
2023-11-12 20:44:03 +01:00
. insert ( {
'key' : key ,
'emoji' : emoji ,
'value' : value ,
'channel' : channel . id ,
'guild' : guild
2023-11-13 17:29:49 +01:00
} )
. returning ( '*' ) ;
2023-11-12 20:44:03 +01:00
2023-11-13 17:29:49 +01:00
await updateCounter ( interaction . client , counter , value ) ;
2023-11-12 20:44:03 +01:00
await interaction . followUp ( {
content : ` <# ${ channel . id } > has been **enriched** with your new counter. Congratulations! `
} ) ;
2023-11-13 16:10:33 +01:00
} else if ( subcommand === 'set' ) {
const type = interaction . options . getString ( 'type' ) ! ;
let counter ;
try {
2023-11-13 17:29:49 +01:00
counter = await findCounter ( type , interaction . guildId ! ) ;
2023-11-13 16:10:33 +01:00
} catch ( err ) {
2023-11-15 11:03:01 +01:00
return interaction . followUp ( 'No such counter!' ) ;
2023-11-13 16:10:33 +01:00
}
2023-11-13 17:29:49 +01:00
const config = await getCounterConfigRaw ( counter ) ;
2023-11-13 16:10:33 +01:00
const key = interaction . options . getString ( 'key' , true ) ;
const value = interaction . options . getString ( 'value' , true ) ;
2023-11-15 11:03:01 +01:00
if ( key === 'emoji' && counter . linkedItem ) return interaction . followUp ( ` Cannot modify emoji - this counter is linked to ${ formatItem ( await getItem ( counter . linkedItem ) ) } ` ) ;
2023-11-13 16:10:33 +01:00
const defaultConfig = counterConfigs . get ( key ) ;
if ( ! defaultConfig ) return interaction . followUp ( ` No config named \` ${ key } \` exists! ` ) ;
const parsedValue = parseConfig ( value , defaultConfig . type ) ;
const restringedValue = toStringConfig ( parsedValue , defaultConfig . type ) ;
2023-11-15 11:03:01 +01:00
await setCounterConfig ( counter , key , restringedValue ) ;
2023-11-13 16:10:33 +01:00
await interaction . followUp ( ` ${ counter . emoji } \` ${ key } \` is now \` ${ restringedValue } \` . (was \` ${ config . get ( key ) || toStringConfig ( defaultConfig . default , defaultConfig . type ) } \` ) ` ) ;
2023-11-12 20:44:03 +01:00
} else if ( subcommand === 'delete' ) {
const type = interaction . options . getString ( 'type' ) ! ;
let counter ;
try {
2023-11-13 17:29:49 +01:00
counter = await findCounter ( type , interaction . guildId ! ) ;
2023-11-12 20:44:03 +01:00
} catch ( err ) {
2023-11-15 11:03:01 +01:00
return interaction . followUp ( 'No such counter!' ) ;
2023-11-12 20:44:03 +01:00
}
await db < Counter > ( 'counters' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id )
2023-11-12 20:44:03 +01:00
. delete ( ) ;
await db < CounterUserLink > ( 'counterUserLink' )
2023-11-13 17:29:49 +01:00
. where ( 'id' , counter . id )
2023-11-12 20:44:03 +01:00
. delete ( ) ;
await interaction . followUp ( {
2023-11-15 11:08:01 +01:00
content : ` The ${ counter . emoji } ${ counter . key } counter has been removed. 😭 `
2023-11-12 20:44:03 +01:00
} ) ;
2023-11-13 17:32:37 +01:00
} else if ( subcommand === 'list' ) {
const counters = await db < Counter > ( 'counters' )
. where ( 'guild' , interaction . guildId ! ) ;
2023-11-15 11:08:01 +01:00
await interaction . followUp ( counters . map ( c = > ` ${ c . emoji } ${ c . key } : ** ${ c . value } ** <# ${ c . channel } > ` ) . join ( '\n' ) ) ;
2023-11-13 21:54:09 +01:00
} else if ( subcommand === 'help' ) {
await interaction . followUp ( help . get ( interaction . options . getString ( 'topic' , true ) ) ! ) ;
2023-11-15 11:03:01 +01:00
} else if ( subcommand === 'link' ) {
const type = interaction . options . getString ( 'type' , true ) ;
const itemID = parseInt ( interaction . options . getString ( 'item' , true ) ) ;
let counter ;
try {
counter = await findCounter ( type , interaction . guildId ! ) ;
} catch ( err ) {
return interaction . followUp ( 'No such counter!' ) ;
}
const item = await getItem ( itemID ) ;
if ( ! item ) return interaction . followUp ( 'No such item exists!' ) ;
2023-11-15 11:08:01 +01:00
if ( item . untradable ) return interaction . followUp ( 'This item is untradable!' ) ;
2023-11-15 11:03:01 +01:00
await db < Counter > ( 'counters' )
. where ( 'id' , counter . id )
. update ( {
'linkedItem' : item . id ,
'emoji' : item . emoji ,
2023-11-15 11:08:01 +01:00
'key' : item . name ,
2023-11-15 11:03:01 +01:00
'value' : 0
} ) ;
await setCounterConfig ( counter , 'canIncrement' , 'false' ) ;
await setCounterConfig ( counter , 'canDecrement' , 'false' ) ;
2023-11-15 11:18:25 +01:00
await setCounterConfig ( counter , 'min' , '0' ) ;
2023-11-15 11:03:01 +01:00
2023-11-15 11:18:25 +01:00
await interaction . followUp ( ` Done. **The counter has been reset** to ${ formatItems ( item , 0 ) } . Users will not be able to take out or put in items until you enable this with \` canTake \` or \` canPut \` . \ n \` canIncrement \` and \` canDecrement \` have also been **automatically disabled** and \` min \` has been set to **0**, and you are recommended to keep these values as such if you want to maintain balance in the universe. ` ) ;
2023-11-12 20:44:03 +01:00
}
}
} ,
2023-11-22 14:45:55 +01:00
autocomplete : set ( {
2023-11-22 14:32:54 +01:00
type : counterAutocomplete ,
item : itemAutocomplete ,
value : async ( interaction : AutocompleteInteraction ) = > {
const focused = interaction . options . getFocused ( ) ;
2023-11-13 16:10:33 +01:00
const type = interaction . options . getString ( 'type' , true ) ;
2023-11-13 17:29:49 +01:00
const counter = await findCounter ( type , interaction . guildId ! ) ;
2023-11-13 16:10:33 +01:00
2023-11-13 17:29:49 +01:00
const config = await getCounterConfigRaw ( counter ) ;
2023-11-13 16:10:33 +01:00
const key = interaction . options . getString ( 'key' ) ;
2023-11-22 14:32:54 +01:00
if ( ! key ) return [ ] ;
2023-11-13 16:10:33 +01:00
const defaultConfig = counterConfigs . get ( key ) ;
2023-11-22 14:32:54 +01:00
if ( ! defaultConfig ) return [ ] ;
2023-11-13 16:10:33 +01:00
const defaultOptions = getOptions ( defaultConfig . type ) ;
let options = [
{
value : ` ${ config . get ( key ) || toStringConfig ( defaultConfig . default , defaultConfig . type ) } ` ,
2023-11-14 16:32:54 +01:00
name : ` Current: ${ config . get ( key ) || toStringConfig ( defaultConfig . default , defaultConfig . type ) } ` . slice ( 0 , 99 )
} ,
{
value : ` ${ toStringConfig ( defaultConfig . default , defaultConfig . type ) } ` ,
name : ` Default: ${ toStringConfig ( defaultConfig . default , defaultConfig . type ) } `
2023-11-13 16:10:33 +01:00
} ,
2023-11-22 14:32:54 +01:00
. . . defaultOptions . filter ( s = > s . startsWith ( focused ) ) . map ( extendOption )
2023-11-13 16:10:33 +01:00
] ;
2023-11-22 14:32:54 +01:00
if ( focused !== '' && ! options . find ( opt = > opt . value === focused ) ) {
2023-11-13 16:10:33 +01:00
options = [
{
2023-11-22 14:32:54 +01:00
value : focused ,
name : focused
2023-11-13 16:10:33 +01:00
} ,
. . . options
] ;
}
2023-11-22 14:32:54 +01:00
return options ;
2023-11-13 16:10:33 +01:00
}
2023-11-22 14:45:55 +01:00
} ) ,
2023-11-16 11:33:11 +01:00
} satisfies Command ;