refactor subscriptions to use sqlite w/ knex

This commit is contained in:
Jill 2023-11-11 00:41:42 +03:00
parent 57b96ce082
commit e57ebd47d2
Signed by: oat
GPG Key ID: 33489AA58A955108
10 changed files with 955 additions and 98 deletions

3
.gitignore vendored
View File

@ -8,4 +8,5 @@ dist/
/counter.json /counter.json
/counterMessageID.txt /counterMessageID.txt
/counterCream.json /counterCream.json
/counterCreamMessageID.txt /counterCreamMessageID.txt
/*.sqlite

View File

@ -1,4 +1,3 @@
{ {
"token": "token", "token": "token"
"disableDaytimeAnnouncements": false
} }

View File

@ -1,15 +0,0 @@
const fs = require("node:fs");
const { REST, Routes } = require("discord.js");
const {token} = require('./config.json');
const rest = new REST({ version: "9" }).setToken(token);
rest.get(Routes.applicationGuildCommands('898850107892596776', '587108210121506816')).then((data) => {
const promises = [];
for (const command of data) {
const deleteUrl = `${Routes.applicationGuildCommands('898850107892596776', '587108210121506816')}/${command.id}`;
promises.push(rest.delete(deleteUrl));
}
console.log(`Removing ${promises.length} commands...`);
Promise.all(promises).then(() => console.log('Done!'));
});

View File

@ -1,2 +0,0 @@
#!/bin/bash
pnpm run build && pm2 restart foggy-v2

View File

@ -7,8 +7,7 @@
"start": "tsc && node deploy-commands.cjs && node dist/index.js", "start": "tsc && node deploy-commands.cjs && node dist/index.js",
"dev": "tsc && node dist/index.js", "dev": "tsc && node dist/index.js",
"build": "tsc", "build": "tsc",
"deploy-commands": "tsc && node deploy-commands.cjs", "deploy-commands": "tsc && node deploy-commands.cjs"
"delete-commands": "tsc && node delete-commands.cjs"
}, },
"author": "oatmealine", "author": "oatmealine",
"license": "AGPL-3.0", "license": "AGPL-3.0",
@ -16,8 +15,10 @@
"d3-array": "^2.12.1", "d3-array": "^2.12.1",
"discord.js": "^14.13.0", "discord.js": "^14.13.0",
"got": "^11.8.6", "got": "^11.8.6",
"knex": "^3.0.1",
"parse-color": "^1.0.0", "parse-color": "^1.0.0",
"random-seed": "^0.3.0" "random-seed": "^0.3.0",
"sqlite3": "^5.1.6"
}, },
"devDependencies": { "devDependencies": {
"@types/d3-array": "^3.0.9", "@types/d3-array": "^3.0.9",

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { Interaction, SlashCommandBuilder } from 'discord.js'; import { Interaction, SlashCommandBuilder } from 'discord.js';
import { saveSubscriptions, subscriptions, timeAnnouncements } from '../lib/subscriptions'; import { isSubscribed, subscribe, timeAnnouncements, unsubscribe } from '../lib/subscriptions';
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
@ -19,19 +19,18 @@ module.exports = {
await interaction.deferReply({ephemeral: true}); await interaction.deferReply({ephemeral: true});
const announcementType = interaction.options.getString('type', true); const announcementType = interaction.options.getString('type', true);
const channel = interaction.channelId;
if (subscriptions[announcementType] && subscriptions[announcementType].includes(interaction.channelId)) { if (await isSubscribed(announcementType, channel)) {
subscriptions[announcementType] = subscriptions[announcementType].filter(id => id !== interaction.channelId); await unsubscribe(announcementType, channel);
await interaction.followUp({ await interaction.followUp({
content: `<#${interaction.channelId}> has been unsubscribed from \`${announcementType}\`` content: `<#${interaction.channelId}> has been unsubscribed from \`${announcementType}\``
}); });
} else { } else {
subscriptions[announcementType] = subscriptions[announcementType] || []; await subscribe(announcementType, interaction.guildId || undefined, channel);
subscriptions[announcementType].push(interaction.channelId);
await interaction.followUp({ await interaction.followUp({
content: `<#${interaction.channelId}> has been subscribed to \`${announcementType}\`` content: `<#${interaction.channelId}> has been subscribed to \`${announcementType}\``
}); });
} }
saveSubscriptions();
} }
}; };

View File

@ -1,12 +1,10 @@
import { Client, GatewayIntentBits, Events, Collection, TextChannel } from 'discord.js'; import { Client, GatewayIntentBits, Events, Collection, TextChannel } from 'discord.js';
import * as fs from 'fs'; import * as fs from 'fs';
const { token, disableDaytimeAnnouncements } = JSON.parse(fs.readFileSync('./config.json', 'utf8')); const { token } = JSON.parse(fs.readFileSync('./config.json', 'utf8'));
import * as path from 'path'; import * as path from 'path';
import { initializeCounter } from './lib/counter'; import { initializeCounter } from './lib/counter';
import { initializeAnnouncements, loadNext, loadSubscriptions } from './lib/subscriptions'; import { initializeAnnouncements } from './lib/subscriptions';
import { initTables } from './lib/db';
loadNext();
loadSubscriptions();
const bot = new Client({ const bot = new Client({
intents: [ intents: [
@ -24,9 +22,7 @@ bot.on(Events.ClientReady, async () => {
initializeCounter(false); initializeCounter(false);
initializeCounter(true); initializeCounter(true);
if (!disableDaytimeAnnouncements) { initializeAnnouncements(bot);
initializeAnnouncements(bot);
}
bot.commands = new Collection(); bot.commands = new Collection();
const cmdFiles = fs.readdirSync(path.join(__dirname, './commands')).filter((file) => file.endsWith('.js')); const cmdFiles = fs.readdirSync(path.join(__dirname, './commands')).filter((file) => file.endsWith('.js'));
@ -63,4 +59,4 @@ process.on('uncaughtException', err => {
console.error(err); console.error(err);
}); });
bot.login(token); initTables().then(() => bot.login(token));

45
src/lib/db.ts Normal file
View File

@ -0,0 +1,45 @@
import knex from 'knex';
export const db = knex({
client: 'sqlite3',
connection: {
filename: './jillo.sqlite'
},
useNullAsDefault: true
});
export interface ScheduledSubscription {
name: string;
next: number;
}
export interface Subscription {
key: string,
channel: string,
guild?: string
}
export async function initTables() {
await db.schema.createTableIfNotExists('scheduledSubscriptions', table => {
table.string('name').primary();
table.timestamp('next').defaultTo(db.fn.now());
});
await db.schema.createTableIfNotExists('subscriptions', table => {
table.string('key')
.references('name').inTable('scheduledSubscriptions');
table.string('channel');
table.string('guild').nullable();
});
/*await db.schema.createTableIfNotExists('counters', table => {
table.string('key').primary();
table.string('name').notNullable();
table.string('emoji').notNullable();
table.integer('value').defaultTo(0);
table.string('channel').notNullable();
table.string('message');
table.boolean('allowlist');
});
await db.schema.createTableIfNotExists('counterUserLink', table => {
table.string('key').references('key').inTable('counters');
table.string('user').notNullable();
});*/
}

View File

@ -1,5 +1,5 @@
import { Client, TextChannel } from 'discord.js'; import { Client, TextChannel } from 'discord.js';
import * as fs from 'fs'; import { ScheduledSubscription, Subscription, db } from './db';
interface AnnouncementType { interface AnnouncementType {
hour: number; hour: number;
@ -159,55 +159,65 @@ function getNextTime(hour: number, randomMinute = true) {
return next.getTime(); return next.getTime();
} }
export let next: Record<string, number> = {}; export async function subscribe(type: string, guild: string | undefined, channel: string) {
export let subscriptions: Record<string, string[]> = {}; await db<Subscription>('subscriptions')
.insert({
export function saveNext() { key: type,
fs.writeFileSync('./next.json', JSON.stringify(next)); guild: guild,
channel: channel
});
} }
export function saveSubscriptions() { export async function unsubscribe(type: string, channel: string) {
fs.writeFileSync('./subscriptions.json', JSON.stringify(subscriptions)); await db<Subscription>('subscriptions')
.delete()
.where('type', type).where('channel', channel);
} }
export function loadNext() { export async function isSubscribed(type: string, channel: string): Promise<boolean> {
if (fs.existsSync('./next.json')) { return await db<Subscription>('subscriptions')
next = JSON.parse(fs.readFileSync('./next.json', 'utf8')); .where('key', type).where('channel', channel)
} .first() !== undefined;
for (const k of Object.keys(timeAnnouncements)) {
if (!next[k]) next[k] = getNextAnnouncementTime(timeAnnouncements[k]);
}
saveNext();
}
export function loadSubscriptions() {
if (fs.existsSync('./subscriptions.json')) {
subscriptions = JSON.parse(fs.readFileSync('./subscriptions.json', 'utf8'));
}
saveSubscriptions();
} }
export function initializeAnnouncements(bot: Client) { export function initializeAnnouncements(bot: Client) {
setInterval(() => { setInterval(async () => {
const current = new Date().getTime(); const current = new Date().getTime();
// console.log(current, next.morning, next.night);
for (const k of Object.keys(timeAnnouncements)) { for (const k of Object.keys(timeAnnouncements)) {
if (next[k] && current > next[k]) { const announcement = timeAnnouncements[k];
const announcement = timeAnnouncements[k];
next[k] = getNextAnnouncementTime(announcement);
saveNext();
if (subscriptions[k]) { const announcementObj = await db<ScheduledSubscription>('scheduledSubscriptions')
for (const channelID of subscriptions[k]) { .select('*')
bot.channels.fetch(channelID, {allowUnknownGuild: true}) .where('name', k)
.then(c => .first();
(c as TextChannel).send(
`${announcement.messagesPrefix ? announcement.messagesPrefix : ''} ${announcement.messages[Math.floor(Math.random() * announcement.messages.length)]}` if (!announcementObj) {
) await db<ScheduledSubscription>('scheduledSubscriptions')
) .insert({
.catch(err => `failed to send ${k} announcement to ${channelID}: ${err}`); name: k,
} next: getNextAnnouncementTime(announcement)
} });
continue;
}
if (current < announcementObj.next) continue;
await db<ScheduledSubscription>('scheduledSubscriptions')
.where('name', k)
.update('next', getNextAnnouncementTime(announcement));
const subscriptions = await db<Subscription>('subscriptions')
.select('*')
.where('key', k);
for (const { channel } of subscriptions) {
bot.channels.fetch(channel, {allowUnknownGuild: true})
.then(c =>
(c as TextChannel).send(
`${announcement.messagesPrefix ? announcement.messagesPrefix : ''} ${announcement.messages[Math.floor(Math.random() * announcement.messages.length)]}`
)
)
.catch(err => console.error(`failed to send ${k} announcement to ${channel}: ${err}`));
} }
} }
}, 1000); }, 1000);