diff --git a/src/commands/createcounter.ts b/src/commands/createcounter.ts new file mode 100644 index 0000000..04aa3b9 --- /dev/null +++ b/src/commands/createcounter.ts @@ -0,0 +1,68 @@ +import { Interaction, SlashCommandBuilder } from 'discord.js'; +import { Counter, db } from '../lib/db'; +import { updateCounter } from '../lib/counter'; + +module.exports = { + data: new SlashCommandBuilder() + .setName('createcounter') + .setDescription('[ADMIN] Create a counter in this channel') + .addStringOption(option => + option + .setName('key') + .setDescription('The codename. Best to leave descriptive for later') + .setRequired(true) + ) + .addStringOption(option => + option + .setName('name') + .setDescription('The name, anything goes') + .setRequired(true) + ) + .addStringOption(option => + option + .setName('emoji') + .setDescription('An emoji or symbol or something to represent the counter') + .setMaxLength(10) + .setRequired(true) + ) + .addNumberOption(option => + option + .setName('value') + .setDescription('Initial value to start with') + ) + .setDefaultMemberPermissions('0') + .setDMPermission(false), + + execute: async (interaction: Interaction) => { + if (!interaction.isChatInputCommand()) return; + + await interaction.deferReply({ephemeral: true}); + + const key = interaction.options.getString('key')!; + const name = interaction.options.getString('name')!; + const emoji = interaction.options.getString('emoji')!; + const value = interaction.options.getNumber('value') || 0; + const channel = interaction.channelId; + const guild = interaction.guildId!; + + await db('counters') + .insert({ + 'key': key, + 'name': name, + 'emoji': emoji, + 'value': value, + 'channel': channel, + 'guild': guild + }); + + const counter = await db('counters') + .where('key', key) + .first(); + + await updateCounter(interaction.client, counter!); + + await interaction.followUp({ + content: `<#${interaction.channelId}> has been **enriched** with your new counter. Congratulations!` + }); + } +}; \ No newline at end of file diff --git a/src/commands/decrease.ts b/src/commands/decrease.ts index 2fdd2cb..8b80a92 100644 --- a/src/commands/decrease.ts +++ b/src/commands/decrease.ts @@ -1,26 +1,36 @@ -import { Interaction, GuildMember, SlashCommandBuilder } from 'discord.js'; -import { changeCounterInteraction } from '../lib/counter'; -import { knownServers } from '../lib/knownServers'; +import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { changeCounterInteraction, counterAutocomplete } from '../lib/counter'; module.exports = { data: new SlashCommandBuilder() .setName('decrease') - .setDescription('Decrease the counter') + .setDescription('Decrease a counter') + .addStringOption(option => + option + .setName('type') + .setAutocomplete(true) + .setDescription('The name of the counter') + .setRequired(true) + ) .addIntegerOption((option) => option .setName('amount') .setRequired(false) .setDescription('Amount to decrease the counter by') .setMinValue(1) - ), - - serverWhitelist: [...knownServers.firepit], + ) + .setDMPermission(false), execute: async (interaction: Interaction, member: GuildMember) => { if (!interaction.isChatInputCommand()) return; - await interaction.deferReply({ephemeral: true}); const amount = Math.trunc(interaction.options.getInteger('amount') || 1); - changeCounterInteraction(interaction, member, -amount, false); - } + const type = interaction.options.getString('type')!; + + await interaction.deferReply({ephemeral: true}); + + changeCounterInteraction(interaction, member, -amount, type); + }, + + autocomplete: counterAutocomplete }; \ No newline at end of file diff --git a/src/commands/decrease2.ts b/src/commands/decrease2.ts deleted file mode 100644 index cebe55f..0000000 --- a/src/commands/decrease2.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Interaction, GuildMember, SlashCommandBuilder } from 'discord.js'; -import { changeCounterInteraction, getCounter } from '../lib/counter'; -import { knownServers } from '../lib/knownServers'; - -module.exports = { - data: new SlashCommandBuilder() - .setName('decrease2') - .setDescription('Decrease the cream counter') - .addIntegerOption((option) => - option - .setName('amount') - .setRequired(false) - .setDescription('Amount to decrease the counter by') - .setMinValue(1) - ), - - serverWhitelist: [...knownServers.firepit], - - execute: async (interaction: Interaction, member: GuildMember) => { - if (!interaction.isChatInputCommand()) return; - - await interaction.deferReply({ephemeral: true}); - const amount = Math.min(Math.trunc(interaction.options.getInteger('amount') || 1), getCounter(true)); - changeCounterInteraction(interaction, member, -amount, true); - } -}; \ No newline at end of file diff --git a/src/commands/increase.ts b/src/commands/increase.ts index b178b66..54be345 100644 --- a/src/commands/increase.ts +++ b/src/commands/increase.ts @@ -1,26 +1,36 @@ import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; -import { changeCounterInteraction } from '../lib/counter'; -import { knownServers } from '../lib/knownServers'; +import { changeCounterInteraction, counterAutocomplete } from '../lib/counter'; module.exports = { data: new SlashCommandBuilder() .setName('increase') - .setDescription('Increase the counter') + .setDescription('Increase a counter') + .addStringOption(option => + option + .setName('type') + .setAutocomplete(true) + .setDescription('The name of the counter') + .setRequired(true) + ) .addIntegerOption((option) => option .setName('amount') .setRequired(false) .setDescription('Amount to increase the counter by') .setMinValue(1) - ), - - serverWhitelist: [...knownServers.firepit], + ) + .setDMPermission(false), execute: async (interaction: Interaction, member: GuildMember) => { if (!interaction.isChatInputCommand()) return; + + const amount = Math.trunc(interaction.options.getInteger('amount') || 1); + const type = interaction.options.getString('type')!; await interaction.deferReply({ephemeral: true}); - const amount = Math.trunc(interaction.options.getInteger('amount') || 1); - changeCounterInteraction(interaction, member, amount, false); - } + + changeCounterInteraction(interaction, member, amount, type); + }, + + autocomplete: counterAutocomplete }; \ No newline at end of file diff --git a/src/commands/increase2.ts b/src/commands/increase2.ts deleted file mode 100644 index b6e9353..0000000 --- a/src/commands/increase2.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; -import { changeCounterInteraction } from '../lib/counter'; -import { knownServers } from '../lib/knownServers'; - -module.exports = { - data: new SlashCommandBuilder() - .setName('increase2') - .setDescription('Increase the cream counter') - .addIntegerOption((option) => - option - .setName('amount') - .setRequired(false) - .setDescription('Amount to increase the counter by') - .setMinValue(1) - ), - - serverWhitelist: [...knownServers.firepit], - - execute: async (interaction: Interaction, member: GuildMember) => { - if (!interaction.isChatInputCommand()) return; - - if (member.id !== '212481359589933056' && member.id !== '321126371189587968') - return await interaction.reply({ - ephemeral: true, - content: 'you are not Dragon.' - }); - await interaction.deferReply({ephemeral: true}); - const amount = Math.trunc(interaction.options.getInteger('amount') || 1); - changeCounterInteraction(interaction, member, amount, true); - } -}; \ No newline at end of file diff --git a/src/commands/subscribe.ts b/src/commands/subscribe.ts index 8e2129e..9749945 100644 --- a/src/commands/subscribe.ts +++ b/src/commands/subscribe.ts @@ -4,12 +4,13 @@ import { isSubscribed, subscribe, timeAnnouncements, unsubscribe } from '../lib/ module.exports = { data: new SlashCommandBuilder() .setName('subscribe') - .setDescription('Subscribe/unsubscribe to a time announcement') + .setDescription('[ADMIN] Subscribe/unsubscribe to a time announcement') .addStringOption(option => option .setName('type') .setChoices(...Object.keys(timeAnnouncements).map(l => ({name: l, value: l}))) .setDescription('The name of the time announcement') + .setRequired(true) ) .setDefaultMemberPermissions('0'), diff --git a/src/index.ts b/src/index.ts index 47dceda..18feb4f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,6 @@ import { Client, GatewayIntentBits, Events, Collection, TextChannel } from 'disc import * as fs from 'fs'; const { token } = JSON.parse(fs.readFileSync('./config.json', 'utf8')); import * as path from 'path'; -import { initializeCounter } from './lib/counter'; import { initializeAnnouncements } from './lib/subscriptions'; import { initTables } from './lib/db'; @@ -19,9 +18,6 @@ const bot = new Client({ }); bot.on(Events.ClientReady, async () => { - initializeCounter(false); - initializeCounter(true); - initializeAnnouncements(bot); bot.commands = new Collection(); @@ -36,17 +32,26 @@ bot.on(Events.ClientReady, async () => { }); bot.on(Events.InteractionCreate, async (interaction) => { - if (!interaction.isCommand()) return; + if (interaction.isCommand()) { + const command = interaction.client.commands.get(interaction.commandName); + if (!command) return; + + try { + await command.execute(interaction, interaction.member); + } catch (error) { + if (interaction.isRepliable() && !interaction.replied && !interaction.deferred) interaction.reply({ content: '`ERROR`', ephemeral: true }); + if (interaction.deferred) interaction.followUp('`ERROR`'); + console.error(error); + } + } else if (interaction.isAutocomplete()) { + const command = interaction.client.commands.get(interaction.commandName); + if (!command) return; - const command = interaction.client.commands.get(interaction.commandName); - if (!command) return; - - try { - await command.execute(interaction, interaction.member); - } catch (error) { - if (interaction.isRepliable() && !interaction.replied && !interaction.deferred) interaction.reply({ content: '`ERROR`', ephemeral: true }); - if (interaction.deferred) interaction.followUp('`ERROR`'); - console.error(error); + try { + await command.autocomplete(interaction); + } catch (error) { + console.error(error); + } } }); diff --git a/src/lib/counter.ts b/src/lib/counter.ts index fe7e692..9b39ef8 100644 --- a/src/lib/counter.ts +++ b/src/lib/counter.ts @@ -1,94 +1,49 @@ -import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel } from 'discord.js'; -import * as fsp from 'fs/promises'; -import { exists, getSign } from './util'; +import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, AutocompleteInteraction } from 'discord.js'; +import { getSign } from './util'; +import { Counter, db } from './db'; -const counterFile = './counter.json'; -const counterFileCream = './counterCream.json'; -const counterMessageFile = './counterMessageID.txt'; -const counterCreamMessageFile = './counterCreamMessageID.txt'; +export async function getCounter(type: string) { + const counter = await db('counters') + .where('key', type) + .first(); -const PISS_CHANNEL = '975802147126018150'; -const CREAM_CHANNEL = '1098319707741900910'; -export const PISS_EMOJI = '🪣'; -export const CREAM_EMOJI = '🥛'; + if (!counter) throw 'No such counter'; -function getCounterFile(cream: boolean) { - return cream ? counterFileCream : counterFile; + return counter.value; } -let pissCounter = 0; -let creamCounter = 0; +export async function changeCounter(delta: number, type: string) { + const value = await getCounter(type); + const newValue = value + delta; -export async function initializeCounter(cream: boolean) { - const filename = getCounterFile(cream); - if (await exists(filename)) { - const count = parseInt(await fsp.readFile(filename, 'utf8')); - if (cream) { - creamCounter = count; - } else { - pissCounter = count; - } - } else { - if (cream) { - creamCounter = 0; - } else { - pissCounter = 0; - } - await saveCounter(cream); - } + await db('counters') + .where('key', type) + .update({ + 'value': newValue + }); + + return newValue; } -export function getCounter(cream: boolean) { - return cream ? creamCounter : pissCounter; +export async function getCounterData(type: string) { + const counter = await db('counters') + .select('*') + .where('key', type) + .first(); + + if (!counter) throw 'No such counter'; + + return counter; } -async function saveCounter(cream: boolean) { - fsp.writeFile(getCounterFile(cream), Math.trunc(getCounter(cream)).toString()); -} +export async function updateCounter(bot: Client, counter: Counter) { + const channel = await bot.channels.fetch(counter.channel) as TextChannel; + const messageID = counter.message; -export async function changeCounter(delta: number, cream: boolean) { - if (cream) { - creamCounter += delta; - } else { - pissCounter += delta; - } - await saveCounter(cream); - return getCounter(cream); -} - -function getCounterMessageFile(cream: boolean) { - return cream ? counterMessageFile : counterCreamMessageFile; -} - -async function getCounterMessageID(cream: boolean) { - const filename = getCounterMessageFile(cream); - if (await exists(filename)) { - const str = await fsp.readFile(filename, 'utf8'); - return str; - } else { - return null; - } -} - -function saveCounterMessageID(id: string, cream: boolean) { - return fsp.writeFile(getCounterMessageFile(cream), id); -} - -function getEmoji(cream: boolean) { - return cream ? CREAM_EMOJI : PISS_EMOJI; -} - -function getChannel(cream: boolean) { - return cream ? CREAM_CHANNEL : PISS_CHANNEL; -} - -export async function updateCounter(bot: Client, cream: boolean) { - const channel = await bot.channels.fetch(getChannel(cream)) as TextChannel; - const messageID = await getCounterMessageID(cream); - - const content = `[${getEmoji(cream)}] x${getCounter(cream)}`; + const content = `[${counter.emoji}] x${counter.value}`; // bit janky + // yeah you don't say try { if (messageID) { const message = await channel.messages.fetch(messageID); @@ -100,12 +55,17 @@ export async function updateCounter(bot: Client, cream: boolean) { } catch(err) { const message = await channel.send(content); message.pin(); - await saveCounterMessageID(message.id, cream); + + await db('counters') + .where('key', counter.key) + .update({ + 'message': message.id + }); } } -export async function announceCounterUpdate(bot: Client, member: GuildMember, delta: number, cream: boolean) { - const channel = await bot.channels.fetch(getChannel(cream)) as TextChannel; +export async function announceCounterUpdate(bot: Client, member: GuildMember, delta: number, counter: Counter) { + const channel = await bot.channels.fetch(counter.channel) as TextChannel; const embed = new EmbedBuilder() .setAuthor({ @@ -116,7 +76,7 @@ export async function announceCounterUpdate(bot: Client, member: GuildMember, de .setColor(member.displayColor) .setTimestamp() .setFooter({ - text: `[${getEmoji(cream)}] x${getCounter(cream)}` + text: `[${counter.emoji}] x${counter.value}` }); await channel.send({ @@ -124,11 +84,33 @@ export async function announceCounterUpdate(bot: Client, member: GuildMember, de }); } -export async function changeCounterInteraction(interaction: CommandInteraction, member: GuildMember, amount: number, cream: boolean) { - const newCount = await changeCounter(amount, cream); - await updateCounter(interaction.client, cream); - await announceCounterUpdate(interaction.client, member, amount, cream); +export async function changeCounterInteraction(interaction: CommandInteraction, member: GuildMember, amount: number, type: string) { + const counter = await getCounterData(type); + + const newCount = await changeCounter(amount, type); + await updateCounter(interaction.client, counter); + await announceCounterUpdate(interaction.client, member, amount, counter); interaction.followUp({ - content: `${getEmoji(cream)} **You have ${amount > 0 ? 'increased' : 'decreased'} the counter.**\n\`\`\`diff\n ${newCount - amount}\n${getSign(amount)}${Math.abs(amount)}\n ${newCount}\`\`\`` + content: `${counter.emoji} **You have ${amount > 0 ? 'increased' : 'decreased'} the counter.**\n\`\`\`diff\n ${newCount - amount}\n${getSign(amount)}${Math.abs(amount)}\n ${newCount}\`\`\`` }); +} + +export async function counterAutocomplete(interaction: AutocompleteInteraction) { + const focusedValue = interaction.options.getFocused(); + const guild = interaction.guildId; + + const query = db('counters') + .select('name', 'key') + .whereLike('name', `%${focusedValue.toLowerCase()}%`) + .limit(25); + + if (guild) { + query.where('guild', guild); + } + + const foundCounters = await query; + + await interaction.respond( + foundCounters.map(choice => ({ name: choice.name, value: choice.key })) + ); } \ No newline at end of file diff --git a/src/lib/db.ts b/src/lib/db.ts index 885acfa..4347298 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -17,6 +17,16 @@ export interface Subscription { channel: string, guild?: string } +export interface Counter { + key: string, + name: string, + emoji: string, + value: number, + channel: string, + guild: string, + message?: string, + allowlist: boolean +} export async function initTables() { await db.schema.createTableIfNotExists('scheduledSubscriptions', table => { @@ -29,17 +39,18 @@ export async function initTables() { table.string('channel'); table.string('guild').nullable(); }); - /*await db.schema.createTableIfNotExists('counters', table => { - table.string('key').primary(); + await db.schema.createTableIfNotExists('counters', table => { + table.string('key').notNullable(); table.string('name').notNullable(); table.string('emoji').notNullable(); table.integer('value').defaultTo(0); table.string('channel').notNullable(); + table.string('guild').notNullable(); table.string('message'); table.boolean('allowlist'); }); await db.schema.createTableIfNotExists('counterUserLink', table => { table.string('key').references('key').inTable('counters'); table.string('user').notNullable(); - });*/ + }); } \ No newline at end of file