diff options
author | jacob janzen <53062115+JacobJanzen@users.noreply.github.com> | 2024-12-11 18:13:16 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-11 18:13:16 -0600 |
commit | 6007a8bbfac47cfe9318541eede484c890489c6e (patch) | |
tree | dd607ea596d64e1921f6b9f9f02c2dce2f0120d9 | |
parent | da5c9f9f0ba5c43873827f0d882e73610571fae0 (diff) |
Complete refactor (#1)
-rw-r--r-- | app.js | 211 | ||||
-rw-r--r-- | command_impls.js | 37 | ||||
-rw-r--r-- | message-scheduler.js | 92 |
3 files changed, 215 insertions, 125 deletions
@@ -1,130 +1,91 @@ -import 'dotenv/config'; -import express from 'express'; -import cron from 'node-cron'; +import "dotenv/config"; +import express from "express"; import { - InteractionType, - InteractionResponseType, - verifyKeyMiddleware, -} from 'discord-interactions'; -import { Client, GatewayIntentBits, ActivityType } from 'discord.js'; -import fs from 'node:fs'; -import { v4 as uuidv4 } from 'uuid'; - -const client = new Client ({ intents: [GatewayIntentBits.Guilds] }); -var jobs = {}; -const job_crons = {}; -client.on('ready', () => { - console.log(`Logged in as ${client.user.tag}!`); - fs.readFile('jobs.json', 'utf8', (err,data) => { - jobs = err ? {} : JSON.parse(data); - - Object.keys(jobs).forEach((job) => { - job_crons[job] = cron.schedule(jobs[job].crontab, () => { - client.channels.cache.get(jobs[job].channel_id).send(jobs[job].message); - }, { - scheduled: true, - timezone: process.env.TIMEZONE - }); + InteractionType, + InteractionResponseType, + verifyKeyMiddleware, +} from "discord-interactions"; +import { Client, GatewayIntentBits, ActivityType } from "discord.js"; +import { pet, schedule_message, unschedule_message } from "./command_impls.js"; +import { MessageSchedule } from "./message-scheduler.js"; + +class State { + constructor() { + this.job_db = "jobs.json"; + this.client = new Client({ intents: [GatewayIntentBits.Guilds] }); + } +} + +function handle_application_command(state, data, channel_id) { + const { name, options } = data; + + switch (name) { + case "schedule_message": + return schedule_message( + state, + options[0].value, + channel_id, + options[1].value, + ); + + case "unschedule_message": + return unschedule_message(state, options[0].value); + + case "pet": + return pet(state); + + default: + console.error(`unknown command: ${name}`); + return res.status(400).json({ error: "unknown command" }); + } +} + +function main() { + const state = new State(); + + state.client.on("ready", () => { + console.log(`Logged in as ${state.client.user.tag}!`); + state.schedule = new MessageSchedule(state); + state.client.user.setPresence({ + activities: [ + { + name: "sily", + type: ActivityType.Playing, + }, + ], + status: "idle", }); }); - client.user.setPresence({ - activities: [{ - name: 'sily', - type: ActivityType.Playing, - }], - status: "idle" - }); -}); - -client.login(process.env.DISCORD_TOKEN); - -const app = express(); -const PORT = process.env.PORT || 3000; - -app.post('/', verifyKeyMiddleware(process.env.PUBLIC_KEY), async function (req, res) { - const { type, data } = req.body; - - if (type === InteractionType.PING) { - return res.send({ type: InteractionResponseType.PONG }); - } - - if (type === InteractionType.APPLICATION_COMMAND) { - const { name, options, guild_id } = data; - - if (name === 'schedule_message') { - const message = options[0].value; - const crontab = options[1].value; - const valid = cron.validate(crontab); - const id = uuidv4(); - const content = valid ? `registered message: "${message}" with cron: "${crontab}" and id: "${id}"` : 'invalid cron'; - - if (valid) { - jobs[id] = { - message: message, - crontab: crontab, - channel_id: req.body.channel_id, - }; - job_crons[id] = cron.schedule(crontab, () => { - client.channels.cache.get(req.body.channel_id).send(message); - }, { - scheduled: true, - timezone: process.env.TIMEZONE - }); - - const json = JSON.stringify(jobs); - fs.writeFile('jobs.json', json, 'utf8', err => { - if (err) { - console.error(err); - } - }); - } - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: content, - }, - }); - } else if (name === 'unschedule_message') { - const id = options[0].value; - const content = id in jobs ? `stopped job ${id}` : `no such job ${id}`; - if (id in jobs) { - job_crons[id].stop(); - delete jobs[id] - delete job_crons[id]; - - const json = JSON.stringify(jobs); - fs.writeFile('jobs.json', json, 'utf8', err => { - if (err) { - console.error(err); - } - }); - } - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: content, - }, - }); - } else if (name === 'pet') { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: '[purring noises]', - }, - }); - } - - console.error(`unknown command: ${name}`); - return res.status(400).json({ error: 'unknown command' }); - } - - console.error('unknown interaction type', type); - return res.status(400).json({ error: 'unknown interaction type' }); -}); + state.client.login(process.env.DISCORD_TOKEN); + + const app = express(); + const PORT = process.env.PORT || 3000; + + app.post( + "/", + verifyKeyMiddleware(process.env.PUBLIC_KEY), + async (req, res) => { + const { type, data, channel_id } = req.body; + + state.res = res; + switch (type) { + case InteractionType.PING: + return res.send({ type: InteractionResponseType.PONG }); + case InteractionType.APPLICATION_COMMAND: + return handle_application_command(state, data, channel_id); + default: + console.error("unknown interaction type", type); + return res + .status(400) + .json({ error: "unknown interaction type" }); + } + }, + ); + + app.listen(PORT, () => { + console.log("Listening on port", PORT); + }); +} -app.listen(PORT, () => { - console.log('Listening on port', PORT); -}); +main(); diff --git a/command_impls.js b/command_impls.js new file mode 100644 index 0000000..8e2cf45 --- /dev/null +++ b/command_impls.js @@ -0,0 +1,37 @@ +import { InteractionResponseType } from "discord-interactions"; +import { Message } from "./message-scheduler.js"; + +function send(state, content) { + return state.res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: content, + }, + }); +} + +export function schedule_message(state, msg, channel, crontab) { + const message = new Message(msg, channel, crontab); + + const schedule_valid = state.schedule.schedule(message); + + return send( + state, + schedule_valid + ? `registered message: "${msg}" with cron: "${crontab}" and id: "${message.id}"` + : "invalid cron", + ); +} + +export function unschedule_message(state, id) { + const unschedule_valid = state.schedule.unschedule(id); + + return send( + state, + unschedule_valid ? `stopped job ${id}` : `no such job ${id}`, + ); +} + +export function pet(state) { + return send(state, "[purring noises]"); +} diff --git a/message-scheduler.js b/message-scheduler.js new file mode 100644 index 0000000..12f868b --- /dev/null +++ b/message-scheduler.js @@ -0,0 +1,92 @@ +import "dotenv/config"; +import cron from "node-cron"; +import { v4 as uuidv4 } from "uuid"; +import fs from "node:fs"; + +export class Message { + constructor(message, channel_id, crontab, repeat = true, id = null) { + this.id = id ? id : uuidv4(); + this.repeat = repeat; + + this.channel_id = channel_id; + this.message = message; + this.crontab = crontab; + } +} + +export class MessageSchedule { + constructor(state) { + this.state = state; + this.crons = {}; + + // read db file if there is one and start up all jobs + fs.readFile(state.job_db, "utf8", (err, data) => { + this.jobs = err ? {} : JSON.parse(data); + + Object.keys(this.jobs).forEach((job) => { + // fix incompatibility with older storage version + const repeat = this.jobs[job].repeat + ? this.jobs[job].repeat + : true; + this.jobs[job].id = job; + this.jobs[job].repeat = repeat; + + this.schedule(this.jobs[job]); + }); + }); + } + + save() { + const json = JSON.stringify(this.jobs); + fs.writeFile(this.state.job_db, json, "utf8", (err) => { + if (err) console.log(err); + }); + } + + schedule(message) { + const valid = cron.validate(message.crontab); + + if (valid) { + this.jobs[message.id] = message; + + this.crons[message.id] = cron.schedule( + message.crontab, + () => { + this.state.client.channels.cache + .get(message.channel_id) + .send(message.message); + if (!message.repeat) { + this.unschedule(message.id); + } + }, + { + scheduled: true, + timezone: process.env.TIMEZONE, + }, + ); + + this.save(); + } + + return valid; + } + + unschedule(id) { + var valid = true; + if (id in this.jobs) { + delete this.jobs[id]; + } else { + valid = false; + } + if (id in this.crons) { + this.crons[id].stop(); + delete this.crons[id]; + } else { + valid = false; + } + + this.save(); + + return valid; + } +} |