314 lines
8.5 KiB
TypeScript
314 lines
8.5 KiB
TypeScript
import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, AutocompleteInteraction } from 'discord.js';
|
|
import { getSign } from './util';
|
|
import { Counter, CounterConfiguration, CounterUserLink, db } from './db';
|
|
|
|
export async function getCounter(id: number) {
|
|
const counter = await db<Counter>('counters')
|
|
.select('value')
|
|
.where('id', id)
|
|
.first();
|
|
|
|
if (!counter) throw 'No such counter';
|
|
|
|
return counter.value;
|
|
}
|
|
|
|
export async function changeCounter(id: number, delta: number) {
|
|
const value = await getCounter(id);
|
|
const newValue = value + delta;
|
|
|
|
await db<Counter>('counters')
|
|
.where('id', id)
|
|
.update({
|
|
'value': newValue
|
|
});
|
|
|
|
return newValue;
|
|
}
|
|
|
|
export async function getCounterData(id: number) {
|
|
const counter = await db<Counter>('counters')
|
|
.select('*')
|
|
.where('id', id)
|
|
.first();
|
|
|
|
if (!counter) throw 'No such counter';
|
|
|
|
return counter;
|
|
}
|
|
|
|
export async function findCounter(key: string, guild: string) {
|
|
const counter = await db<Counter>('counters')
|
|
.select('*')
|
|
.where('key', key)
|
|
.where('guild', guild)
|
|
.first();
|
|
|
|
if (!counter) throw 'No such counter';
|
|
|
|
return counter;
|
|
}
|
|
|
|
export async function getCounterConfigRaw(counter: Counter) {
|
|
const configs = await db<CounterConfiguration>('counterConfigurations')
|
|
.select('configName', 'value')
|
|
.where('id', counter.id);
|
|
|
|
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);
|
|
|
|
return config;
|
|
}
|
|
|
|
export async function getCounterConfig(id: number, key: string) {
|
|
const config = await db<CounterConfiguration>('counterConfigurations')
|
|
.select('value')
|
|
.where('id', id)
|
|
.where('configName', key)
|
|
.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(id: number, option: string, value: string) {
|
|
// just the ugly way of life
|
|
if (option === 'emoji') {
|
|
await db<Counter>('counters')
|
|
.where('id', id)
|
|
.update({
|
|
'emoji': value
|
|
});
|
|
return;
|
|
}
|
|
|
|
const updated = await db<CounterConfiguration>('counterConfigurations')
|
|
.update({
|
|
value: value
|
|
})
|
|
.where('id', id)
|
|
.where('configName', option);
|
|
|
|
if (updated === 0) {
|
|
await db<CounterConfiguration>('counterConfigurations')
|
|
.insert({
|
|
'id': id,
|
|
'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
|
|
}],
|
|
['messageTemplate', {
|
|
type: ConfigType.String,
|
|
default: '**%user** has %action the counter by **%amt**.'
|
|
}],
|
|
['messageTemplateIncrease', {
|
|
type: ConfigType.String,
|
|
default: 'null'
|
|
}],
|
|
['messageTemplateDecrease', {
|
|
type: ConfigType.String,
|
|
default: 'null'
|
|
}],
|
|
|
|
// these ones are fake and are just stand-ins for values defined inside the actual counters table
|
|
['emoji', {
|
|
type: ConfigType.String,
|
|
default: ''
|
|
}]
|
|
]);
|
|
|
|
export async function updateCounter(bot: Client, counter: Counter, value: number) {
|
|
const channel = await bot.channels.fetch(counter.channel) as TextChannel;
|
|
const messageID = counter.message;
|
|
|
|
const content = `[${counter.emoji}] x${value}`;
|
|
|
|
// bit janky
|
|
// yeah you don't say
|
|
try {
|
|
if (messageID) {
|
|
const message = await channel.messages.fetch(messageID);
|
|
if (!message) throw new Error();
|
|
await message.edit(content);
|
|
} else {
|
|
throw new Error();
|
|
}
|
|
} catch(err) {
|
|
const message = await channel.send(content);
|
|
message.pin();
|
|
|
|
await db<Counter>('counters')
|
|
.where('id', counter.id)
|
|
.update({
|
|
'message': message.id
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function announceCounterUpdate(bot: Client, member: GuildMember, delta: number, counter: Counter, value: number) {
|
|
const channel = await bot.channels.fetch(counter.channel) as TextChannel;
|
|
|
|
let template = await getCounterConfig(counter.id, 'messageTemplate') as string;
|
|
const templateIncrease = await getCounterConfig(counter.id, 'messageTemplateIncrease') as string;
|
|
if (templateIncrease !== 'null' && delta > 0) template = templateIncrease;
|
|
const templateDecrease = await getCounterConfig(counter.id, 'messageTemplateDecrease') as string;
|
|
if (templateDecrease !== 'null' && delta < 0) template = templateDecrease;
|
|
|
|
const anonymous = await getCounterConfig(counter.id, 'anonymous') as boolean;
|
|
|
|
const embed = new EmbedBuilder()
|
|
//.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())
|
|
.replaceAll('%total', value.toString())
|
|
)
|
|
.setTimestamp()
|
|
.setFooter({
|
|
text: `[${counter.emoji}] x${value}`
|
|
});
|
|
|
|
if (!anonymous) {
|
|
embed
|
|
.setAuthor({
|
|
name: member.displayName,
|
|
iconURL: member.displayAvatarURL()
|
|
})
|
|
.setColor(member.displayColor);
|
|
}
|
|
|
|
await channel.send({
|
|
embeds: [embed]
|
|
});
|
|
}
|
|
|
|
export async function changeCounterInteraction(interaction: CommandInteraction, member: GuildMember, amount: number, type: string) {
|
|
try {
|
|
const counter = await findCounter(type, member.guild.id);
|
|
|
|
let canUse = true;
|
|
if (amount > 0 && counter.allowlistProducer) {
|
|
const userLink = await db<CounterUserLink>('counterUserLink')
|
|
.where('id', counter.id)
|
|
.where('user', member.id)
|
|
.where('producer', true)
|
|
.first();
|
|
|
|
if (!userLink) canUse = false;
|
|
}
|
|
if (amount < 0 && counter.allowlistConsumer) {
|
|
const userLink = await db<CounterUserLink>('counterUserLink')
|
|
.where('id', counter.id)
|
|
.where('user', member.id)
|
|
.where('producer', false)
|
|
.first();
|
|
|
|
if (!userLink) canUse = false;
|
|
}
|
|
|
|
if (!canUse) {
|
|
await interaction.followUp({
|
|
content: `You cannot **${amount > 0 ? 'produce' : 'consume'}** ${counter.emoji}.`
|
|
});
|
|
return;
|
|
}
|
|
|
|
const newCount = await changeCounter(counter.id, amount);
|
|
await updateCounter(interaction.client, counter, newCount);
|
|
await announceCounterUpdate(interaction.client, member, amount, counter, newCount);
|
|
await interaction.followUp({
|
|
content: `${counter.emoji} **You have ${amount > 0 ? 'increased' : 'decreased'} the counter.**\n\`\`\`diff\n ${newCount - amount}\n${getSign(amount)}${Math.abs(amount)}\n ${newCount}\`\`\``
|
|
});
|
|
} catch(err) {
|
|
await interaction.followUp({
|
|
content: (err as Error).toString()
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function counterAutocomplete(interaction: AutocompleteInteraction) {
|
|
const focusedValue = interaction.options.getFocused();
|
|
const guild = interaction.guildId;
|
|
|
|
const query = db<Counter>('counters')
|
|
.select('emoji', 'key')
|
|
.whereLike('key', `%${focusedValue.toLowerCase()}%`)
|
|
.limit(25);
|
|
|
|
if (guild) {
|
|
query.where('guild', guild);
|
|
}
|
|
|
|
const foundCounters = await query;
|
|
|
|
await interaction.respond(
|
|
foundCounters.map(choice => ({ name: choice.emoji, value: choice.key }))
|
|
);
|
|
} |