405 lines
14 KiB
TypeScript
405 lines
14 KiB
TypeScript
import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js';
|
|
import { Counter, CounterUserLink, db } from '../lib/db';
|
|
import { counterAutocomplete, counterConfigs, getCounterConfigRaw, getCounterData, getOptions, parseConfig, setCounterConfig, toStringConfig, updateCounter } from '../lib/counter';
|
|
|
|
function extendOption(t: string) {
|
|
return {name: t, value: t};
|
|
}
|
|
|
|
module.exports = {
|
|
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')
|
|
.setDescription('The codename. Best to leave descriptive for later; used in searching for counters')
|
|
.setRequired(true)
|
|
)
|
|
.addStringOption(option =>
|
|
option
|
|
.setName('emoji')
|
|
.setDescription('An emoji or symbol or something to represent the counter')
|
|
.setRequired(true)
|
|
.setMaxLength(100)
|
|
)
|
|
.addNumberOption(option =>
|
|
option
|
|
.setName('value')
|
|
.setDescription('Initial value to start with')
|
|
)
|
|
)
|
|
.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)
|
|
)
|
|
)
|
|
.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)
|
|
)
|
|
)
|
|
.setDefaultMemberPermissions('0')
|
|
.setDMPermission(false),
|
|
|
|
execute: async (interaction: Interaction) => {
|
|
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 {
|
|
counter = await getCounterData(type);
|
|
} catch(err) {
|
|
await interaction.followUp({
|
|
content: 'No such counter!'
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (subcommand === 'add') {
|
|
const user = interaction.options.getUser('user', true);
|
|
const userType = interaction.options.getString('usertype', true);
|
|
|
|
const link = await db<CounterUserLink>('counterUserLink')
|
|
.where('key', type)
|
|
.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({
|
|
'key': type,
|
|
'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')
|
|
.where('key', type)
|
|
.where('user', user.id)
|
|
.where('producer', userType === 'producer')
|
|
.first();
|
|
|
|
if (!link) {
|
|
await interaction.followUp({
|
|
content: `<@${user.id}> is not in the ${counter.emoji} **${userType}** allowlist!`
|
|
});
|
|
return;
|
|
}
|
|
|
|
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')
|
|
.where('key', type)
|
|
.update({
|
|
'allowlistProducer': enabled
|
|
});
|
|
} else {
|
|
await db<Counter>('counters')
|
|
.where('key', type)
|
|
.update({
|
|
'allowlistConsumer': enabled
|
|
});
|
|
}
|
|
|
|
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')
|
|
.where('key', type)
|
|
.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!;
|
|
|
|
await db<Counter>('counters')
|
|
.insert({
|
|
'key': key,
|
|
'emoji': emoji,
|
|
'value': value,
|
|
'channel': channel.id,
|
|
'guild': guild
|
|
});
|
|
|
|
const counter = await db<Counter>('counters')
|
|
.where('key', key)
|
|
.first();
|
|
|
|
await updateCounter(interaction.client, counter!, value);
|
|
|
|
await interaction.followUp({
|
|
content: `<#${channel.id}> has been **enriched** with your new counter. Congratulations!`
|
|
});
|
|
} else if (subcommand === 'set') {
|
|
const type = interaction.options.getString('type')!;
|
|
|
|
let counter;
|
|
try {
|
|
counter = await getCounterData(type);
|
|
} catch(err) {
|
|
await interaction.followUp({
|
|
content: 'No such counter!'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const config = await getCounterConfigRaw(interaction.options.getString('type') || '', counter);
|
|
const key = interaction.options.getString('key', true);
|
|
const value = interaction.options.getString('value', true);
|
|
|
|
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);
|
|
|
|
await setCounterConfig(type, key, restringedValue);
|
|
|
|
await interaction.followUp(`${counter.emoji} \`${key}\` is now \`${restringedValue}\`. (was \`${config.get(key) || toStringConfig(defaultConfig.default, defaultConfig.type)}\`)`);
|
|
} else if (subcommand === 'delete') {
|
|
const type = interaction.options.getString('type')!;
|
|
|
|
let counter;
|
|
try {
|
|
counter = await getCounterData(type);
|
|
} catch(err) {
|
|
await interaction.followUp({
|
|
content: 'No such counter!'
|
|
});
|
|
return;
|
|
}
|
|
|
|
await db<Counter>('counters')
|
|
.where('key', type)
|
|
.delete();
|
|
|
|
await db<CounterUserLink>('counterUserLink')
|
|
.where('key', type)
|
|
.delete();
|
|
|
|
await interaction.followUp({
|
|
content: `The ${counter.emoji} counter has been removed. 😭`
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
autocomplete: async (interaction: AutocompleteInteraction) => {{
|
|
const focused = interaction.options.getFocused(true);
|
|
|
|
if (focused.name === 'type') {
|
|
return counterAutocomplete(interaction);
|
|
} else if (focused.name === 'value') {
|
|
const type = interaction.options.getString('type', true);
|
|
const counter = await getCounterData(type);
|
|
|
|
const config = await getCounterConfigRaw(type, counter);
|
|
const key = interaction.options.getString('key');
|
|
|
|
if (!key) return interaction.respond([]);
|
|
|
|
const defaultConfig = counterConfigs.get(key);
|
|
if (!defaultConfig) return interaction.respond([]);
|
|
|
|
const defaultOptions = getOptions(defaultConfig.type);
|
|
|
|
let options = [
|
|
{
|
|
value: `${config.get(key) || toStringConfig(defaultConfig.default, defaultConfig.type)}`,
|
|
name: `[CURRENT] ${config.get(key) || toStringConfig(defaultConfig.default, defaultConfig.type)}`.slice(0, 99)
|
|
},
|
|
...defaultOptions.filter(s => s.startsWith(focused.value)).map(extendOption)
|
|
];
|
|
|
|
if (focused.value !== '' && !options.find(opt => opt.value === focused.value)) {
|
|
options = [
|
|
{
|
|
value: focused.value,
|
|
name: focused.value
|
|
},
|
|
...options
|
|
];
|
|
}
|
|
|
|
await interaction.respond(options);
|
|
}
|
|
}}
|
|
}; |