aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacob janzen <53062115+JacobJanzen@users.noreply.github.com>2024-12-11 18:13:16 -0600
committerGitHub <noreply@github.com>2024-12-11 18:13:16 -0600
commit6007a8bbfac47cfe9318541eede484c890489c6e (patch)
treedd607ea596d64e1921f6b9f9f02c2dce2f0120d9
parentda5c9f9f0ba5c43873827f0d882e73610571fae0 (diff)
Complete refactor (#1)
-rw-r--r--app.js211
-rw-r--r--command_impls.js37
-rw-r--r--message-scheduler.js92
3 files changed, 215 insertions, 125 deletions
diff --git a/app.js b/app.js
index 705ad80..53fe461 100644
--- a/app.js
+++ b/app.js
@@ -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;
+ }
+}