aboutsummaryrefslogtreecommitdiff
path: root/bot
diff options
context:
space:
mode:
authorAndrew Lee <andrew@alee14.me>2026-04-09 01:22:38 -0400
committerAndrew Lee <andrew@alee14.me>2026-04-09 01:22:38 -0400
commitf8b93cf133126b57d85d8a5fc424fc87ad2f4459 (patch)
tree53f16e8c0a2d65b1df1981292bdaa736ec1e1e76 /bot
downloadbnbmc-announcement-api-f8b93cf133126b57d85d8a5fc424fc87ad2f4459.tar.gz
bnbmc-announcement-api-f8b93cf133126b57d85d8a5fc424fc87ad2f4459.tar.bz2
bnbmc-announcement-api-f8b93cf133126b57d85d8a5fc424fc87ad2f4459.zip
Initial commit
Diffstat (limited to 'bot')
-rwxr-xr-xbot/bun.lockbbin0 -> 11472 bytes
-rw-r--r--bot/index.ts106
-rw-r--r--bot/package.json13
-rw-r--r--bot/tsconfig.json12
4 files changed, 131 insertions, 0 deletions
diff --git a/bot/bun.lockb b/bot/bun.lockb
new file mode 100755
index 0000000..28b13e5
--- /dev/null
+++ b/bot/bun.lockb
Binary files differ
diff --git a/bot/index.ts b/bot/index.ts
new file mode 100644
index 0000000..e3a2c6a
--- /dev/null
+++ b/bot/index.ts
@@ -0,0 +1,106 @@
+import { Client, Events, GatewayIntentBits } from 'discord.js';
+import { Database } from "bun:sqlite";
+
+const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
+const db = new Database("../database.sqlite", { create: true });
+
+function parseMessage(content: string) {
+ const lines = content.split('\n');
+ const authorMatch = content.match(/\*from(?: the)? ([^*]+)\*/i);
+ const titleLine = lines.slice(0, 2).find(l => /^#+\s+.+|^.+\*\*[^*]+\*\*$/.test(l)) ?? null;
+
+ const author = authorMatch ? authorMatch[1].trim() : null;
+ const title = titleLine ? titleLine.replace(/^#+\s+/, '').replace(/\*\*/g, '').trim() : null;
+ const message = lines
+ .filter(l => l !== titleLine)
+ .join('\n')
+ .replace(/\*from(?: the)? [^*]+\*\n?/i, '')
+ .trimStart();
+
+ return { author, title, message };
+}
+
+client.on(Events.ClientReady, bot => {
+ console.log(`Logged in as ${bot.user.tag}!`);
+ client.user?.setStatus('invisible');
+
+ try {
+ db.run(`CREATE TABLE IF NOT EXISTS announcements (
+ id INTEGER PRIMARY KEY,
+ msg_id INTERGER,
+ title TEXT,
+ author TEXT,
+ message TEXT,
+ created_at INTEGER
+ )`);
+ } catch (e) {
+ console.error(e);
+ }
+});
+
+client.on(Events.MessageCreate, async (msg) => {
+ if (msg.author.bot) return;
+ if (msg.author.id === client.user!.id) return;
+ if (msg.channel.id !== process.env.ANNOUNCEMENT_CHANNEL) return;
+ if (!(msg.content.startsWith('# ') || msg.content.startsWith('*') || msg.content.match(/^:[a-z0-9_]+:/i) || msg.content.match(/^\p{Emoji}/u))) return;
+
+ const { title, author, message } = parseMessage(msg.content);
+
+ try {
+ const insert = db.prepare(`INSERT INTO announcements (title, msg_id, author, message, created_at) VALUES (($title), ($msg_id), ($author), ($message), ($created_at))`);
+ insert.run({
+ $title: title,
+ $msg_id: msg.id,
+ $author: author,
+ $message: message,
+ $created_at: msg.createdTimestamp.toString()
+ })
+ } catch (e) {
+ console.error(e);
+ }
+
+});
+
+client.on(Events.MessageUpdate, async (msg, msgnew) => {
+ if (!msg.author) return;
+ if (!msg.content) return;
+ if (msg.author.bot) return;
+ if (msg.author.id === client.user!.id) return;
+ if (msg.channel.id !== process.env.ANNOUNCEMENT_CHANNEL) return;
+ if (!(msgnew.content.startsWith('# ') || msgnew.content.startsWith('*') || msgnew.content.match(/^:[a-z0-9_]+:/i) || msgnew.content.match(/^\p{Emoji}/u))) return;
+
+ const { title, author, message } = parseMessage(msgnew.content);
+
+ try {
+ const update = db.prepare(`UPDATE announcements SET title = ($title), author = ($author), message = ($message) WHERE msg_id = ($msg_id)`);
+ update.run({
+ $title: title,
+ $author: author,
+ $message: message,
+ $msg_id: msg.id
+ })
+ } catch (e) {
+ console.error(e);
+ }
+
+});
+
+client.on(Events.MessageDelete, async (msg) => {
+ if (!msg.author) return;
+ if (!msg.content) return;
+ if (msg.author.bot) return;
+ if (msg.author.id === client.user!.id) return;
+ if (msg.channel.id !== process.env.ANNOUNCEMENT_CHANNEL) return;
+
+ try {
+ const deleteAnnouncement = db.prepare(`DELETE FROM announcements WHERE msg_id = ($msg_id)`);
+ deleteAnnouncement.run({
+ $msg_id: msg.id
+ })
+ } catch (e) {
+ console.error(e);
+ }
+
+});
+
+client.login(process.env.TOKEN);
diff --git a/bot/package.json b/bot/package.json
new file mode 100644
index 0000000..4ada057
--- /dev/null
+++ b/bot/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "bot",
+ "version": "1.0.0",
+ "main": "index.js",
+ "license": "MIT",
+ "dependencies": {
+ "discord.js": "^14.26.2"
+ },
+ "devDependencies": {
+ "@types/bun": "^1.3.11",
+ "@types/node": "^25.5.2"
+ }
+}
diff --git a/bot/tsconfig.json b/bot/tsconfig.json
new file mode 100644
index 0000000..f737fec
--- /dev/null
+++ b/bot/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ "module": "NodeNext", /* Specify what module code is generated. */
+ "moduleResolution": "NodeNext",
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+ "strict": true, /* Enable all strict type-checking options. */
+ "skipLibCheck": true, /* Skip type checking all .d.ts files. */
+ "types": ["node", "bun"]
+ }
+}