counter configuration!
This commit is contained in:
parent
2c3443a639
commit
bccf905e68
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema
|
||||
.createTable('counterConfigurations', table => {
|
||||
table.string('counter').references('key').inTable('counters').onDelete('CASCADE');
|
||||
table.string('guild').notNullable();
|
||||
table.string('configName').notNullable();
|
||||
table.string('value').nullable();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema
|
||||
.dropTable('counterConfigurations');
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import { Interaction, SlashCommandBuilder } from 'discord.js';
|
||||
import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js';
|
||||
import { Counter, CounterUserLink, db } from '../lib/db';
|
||||
import { counterAutocomplete, getCounterData, updateCounter } from '../lib/counter';
|
||||
import { counterAutocomplete, counterConfigs, getCounterConfigRaw, getCounterData, getOptions, parseConfig, setCounterConfig, toStringConfig, updateCounter } from '../lib/counter';
|
||||
|
||||
function extendOption(t: string) {
|
||||
return {name: t, value: t};
|
||||
|
@ -130,6 +130,7 @@ module.exports = {
|
|||
.setName('emoji')
|
||||
.setDescription('An emoji or symbol or something to represent the counter')
|
||||
.setRequired(true)
|
||||
.setMaxLength(100)
|
||||
)
|
||||
.addNumberOption(option =>
|
||||
option
|
||||
|
@ -137,6 +138,33 @@ module.exports = {
|
|||
.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')
|
||||
|
@ -280,6 +308,32 @@ module.exports = {
|
|||
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')!;
|
||||
|
||||
|
@ -308,5 +362,44 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
|
||||
autocomplete: counterAutocomplete
|
||||
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);
|
||||
}
|
||||
}}
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, AutocompleteInteraction } from 'discord.js';
|
||||
import { getSign } from './util';
|
||||
import { Counter, CounterUserLink, db } from './db';
|
||||
import { Counter, CounterConfiguration, CounterUserLink, db } from './db';
|
||||
|
||||
export async function getCounter(type: string) {
|
||||
const counter = await db<Counter>('counters')
|
||||
|
@ -36,6 +36,138 @@ export async function getCounterData(type: string) {
|
|||
return counter;
|
||||
}
|
||||
|
||||
export async function getCounterConfigRaw(type: string, counter: Counter) {
|
||||
const configs = await db<CounterConfiguration>('counterConfigurations')
|
||||
.select('configName', 'value')
|
||||
.where('counter', type);
|
||||
|
||||
const config = new Map<string, string>();
|
||||
configs.forEach(({ configName, value }) => {
|
||||
config.set(configName, value);
|
||||
});
|
||||
|
||||
// just the ugly way of life
|
||||
config.set('emoji', counter.emoji);
|
||||
config.set('messageTemplate', counter.messageTemplate || (counterConfigs.get('messageTemplate')!.default! as string)); // wow! this line is truly awful
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export async function getCounterConfig(type: string, key: string) {
|
||||
const config = await db<CounterConfiguration>('counterConfigurations')
|
||||
.select('value')
|
||||
.where('counter', type)
|
||||
.first();
|
||||
|
||||
const valueStr = config?.value;
|
||||
let value;
|
||||
if (valueStr) {
|
||||
value = parseConfig(valueStr, counterConfigs.get(key)!.type);
|
||||
} else {
|
||||
value = counterConfigs.get(key)!.default;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function setCounterConfig(type: string, option: string, value: string) {
|
||||
// just the ugly way of life
|
||||
if (option === 'emoji') {
|
||||
await db<Counter>('counters')
|
||||
.update({
|
||||
'emoji': value
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (option === 'messageTemplate') {
|
||||
await db<Counter>('counters')
|
||||
.update({
|
||||
'messageTemplate': value
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = await db<CounterConfiguration>('counterConfigurations')
|
||||
.update({
|
||||
value: value
|
||||
})
|
||||
.where('counter', type)
|
||||
.where('configName', option);
|
||||
|
||||
if (updated === 0) {
|
||||
await db<CounterConfiguration>('counterConfigurations')
|
||||
.insert({
|
||||
'counter': type,
|
||||
'guild': '0', //TODO
|
||||
'configName': option,
|
||||
'value': value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export enum ConfigType {
|
||||
Bool,
|
||||
String,
|
||||
Number
|
||||
}
|
||||
|
||||
export function parseConfig(str: string, type: ConfigType.Bool): boolean
|
||||
export function parseConfig(str: string, type: ConfigType.String): string
|
||||
export function parseConfig(str: string, type: ConfigType.Number): number
|
||||
export function parseConfig(str: string, type: ConfigType): boolean | string | number
|
||||
export function parseConfig(str: string, type: ConfigType) {
|
||||
switch(type) {
|
||||
case ConfigType.Bool:
|
||||
return str === 'true';
|
||||
case ConfigType.String:
|
||||
return str.trim();
|
||||
case ConfigType.Number: {
|
||||
const n = parseInt(str);
|
||||
if (isNaN(n)) throw 'Not a valid number';
|
||||
return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getOptions(type: ConfigType): string[] {
|
||||
switch(type) {
|
||||
case ConfigType.Bool:
|
||||
return ['true', 'false'];
|
||||
case ConfigType.String:
|
||||
return [];
|
||||
case ConfigType.Number:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function toStringConfig(value: boolean | string | number, type: ConfigType): string {
|
||||
switch(type) {
|
||||
case ConfigType.Bool:
|
||||
return value ? 'true' : 'false';
|
||||
case ConfigType.String:
|
||||
return (value as string);
|
||||
case ConfigType.Number:
|
||||
return (value as number).toString();
|
||||
}
|
||||
}
|
||||
|
||||
export const counterConfigs = new Map([
|
||||
['anonymous', {
|
||||
type: ConfigType.Bool,
|
||||
default: false
|
||||
}],
|
||||
|
||||
// these ones are fake and are just stand-ins for values defined inside the actual counters table
|
||||
['emoji', {
|
||||
type: ConfigType.String,
|
||||
default: ''
|
||||
}],
|
||||
['messageTemplate', {
|
||||
type: ConfigType.String,
|
||||
default: '**%user** has %action the counter by **%amt**.'
|
||||
}]
|
||||
]);
|
||||
|
||||
export async function updateCounter(bot: Client, counter: Counter, value: number) {
|
||||
const channel = await bot.channels.fetch(counter.channel) as TextChannel;
|
||||
const messageID = counter.message;
|
||||
|
@ -67,18 +199,31 @@ export async function updateCounter(bot: Client, counter: Counter, value: number
|
|||
export async function announceCounterUpdate(bot: Client, member: GuildMember, delta: number, counter: Counter, value: number) {
|
||||
const channel = await bot.channels.fetch(counter.channel) as TextChannel;
|
||||
|
||||
const template = counter.messageTemplate || counterConfigs.get('messageTemplate')!.default as string;
|
||||
const anonymous = await getCounterConfig(counter.key, 'anonymous');
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setAuthor({
|
||||
name: `${member.user.username}#${member.user.discriminator}`,
|
||||
iconURL: member.user.displayAvatarURL()
|
||||
})
|
||||
.setDescription(`**${member.toString()}** has ${delta > 0 ? 'increased' : 'decreased'} the counter by **${Math.abs(delta)}**.`)
|
||||
.setColor(member.displayColor)
|
||||
//.setDescription(`**${member.toString()}** has ${delta > 0 ? 'increased' : 'decreased'} the counter by **${Math.abs(delta)}**.`)
|
||||
.setDescription(
|
||||
template
|
||||
.replaceAll('%user', anonymous ? 'someone' : member.toString())
|
||||
.replaceAll('%action', delta > 0 ? 'increased' : 'decreased')
|
||||
.replaceAll('%amt', Math.abs(delta).toString())
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({
|
||||
text: `[${counter.emoji}] x${value}`
|
||||
});
|
||||
|
||||
if (!anonymous) {
|
||||
embed
|
||||
.setAuthor({
|
||||
name: `${member.user.username}#${member.user.discriminator}`,
|
||||
iconURL: member.user.displayAvatarURL()
|
||||
})
|
||||
.setColor(member.displayColor);
|
||||
}
|
||||
|
||||
await channel.send({
|
||||
embeds: [embed]
|
||||
});
|
||||
|
|
|
@ -39,4 +39,10 @@ export interface CounterUserLink {
|
|||
key: string,
|
||||
user: string,
|
||||
producer: boolean
|
||||
}
|
||||
export interface CounterConfiguration {
|
||||
counter: string,
|
||||
guild: string,
|
||||
configName: string,
|
||||
value: string
|
||||
}
|
Loading…
Reference in New Issue