aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md1
-rw-r--r--api/Dockerfile12
-rwxr-xr-xapi/bun.lockbbin30656 -> 31778 bytes
-rw-r--r--api/index.ts13
-rw-r--r--api/package.json2
-rw-r--r--bot/Dockerfile12
-rwxr-xr-xbot/bun.lockbbin11472 -> 12570 bytes
-rw-r--r--bot/index.ts91
-rw-r--r--bot/package.json7
-rw-r--r--compose.yaml16
11 files changed, 141 insertions, 14 deletions
diff --git a/.gitignore b/.gitignore
index 102ad8f..8d615c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
node_modules/
database.sqlite
.env
+public/
diff --git a/README.md b/README.md
index 409b1c4..5cf67d5 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
Reads messages from the bits & Bytes Minecraft General Announcements, and displays it on the website.
# Configuration
+bot/.env
```
TOKEN=[token]
ANNOUNCEMENT_CHANNEL=[channel_id]
diff --git a/api/Dockerfile b/api/Dockerfile
new file mode 100644
index 0000000..a7a7d62
--- /dev/null
+++ b/api/Dockerfile
@@ -0,0 +1,12 @@
+FROM bun:latest
+
+WORKDIR /api
+
+COPY bun.lockd .
+COPY package.json .
+
+RUN bun install
+
+COPY . .
+
+ENTRYPOINT ["bun", "index.ts"]
diff --git a/api/bun.lockb b/api/bun.lockb
index 52c0b95..9d47e1d 100755
--- a/api/bun.lockb
+++ b/api/bun.lockb
Binary files differ
diff --git a/api/index.ts b/api/index.ts
index 2fb7a08..9435bb1 100644
--- a/api/index.ts
+++ b/api/index.ts
@@ -1,10 +1,13 @@
import express from 'express';
import { Database } from "bun:sqlite";
+import cors from 'cors';
const app = express();
const port = process.env.PORT || 3000;
const db = new Database("../database.sqlite", { readonly: true });
+app.use(express.static('../public'))
+app.use(cors());
app.get('/', (req, res) => {
const query = db.prepare(`SELECT * FROM announcements ORDER BY created_at DESC`);
@@ -12,6 +15,16 @@ app.get('/', (req, res) => {
res.send(result);
});
+app.get('/attachments/:slug', (req, res) => {
+ if (req.params.slug) {
+ const query = db.prepare(`SELECT * FROM announcements_attachments WHERE msg_id = (?)`);
+ const result = query.all(req.params.slug);
+ res.send(result);
+ } else {
+ res.send('Unknown message id.');
+ }
+});
+
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
diff --git a/api/package.json b/api/package.json
index c9b6a1e..8fcc3ed 100644
--- a/api/package.json
+++ b/api/package.json
@@ -4,10 +4,12 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
+ "cors": "^2.8.6",
"express": "^5.2.1"
},
"devDependencies": {
"@types/bun": "^1.3.11",
+ "@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/node": "^25.5.2"
}
diff --git a/bot/Dockerfile b/bot/Dockerfile
new file mode 100644
index 0000000..e37f276
--- /dev/null
+++ b/bot/Dockerfile
@@ -0,0 +1,12 @@
+FROM bun:latest
+
+WORKDIR /bot
+
+COPY bun.lockd .
+COPY package.json .
+
+RUN bun install
+
+COPY . .
+
+ENTRYPOINT ["bun", "index.ts"]
diff --git a/bot/bun.lockb b/bot/bun.lockb
index 28b13e5..3d6ad67 100755
--- a/bot/bun.lockb
+++ b/bot/bun.lockb
Binary files differ
diff --git a/bot/index.ts b/bot/index.ts
index e3a2c6a..2c8bfd2 100644
--- a/bot/index.ts
+++ b/bot/index.ts
@@ -1,13 +1,16 @@
import { Client, Events, GatewayIntentBits } from 'discord.js';
+import removeMd from 'remove-markdown';
+import { writeFileSync, mkdirSync, rmSync } from 'fs';
+import { marked } from 'marked';
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 });
+const db = new Database(process.env.DB_LOCATION, { 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 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;
@@ -33,28 +36,67 @@ client.on(Events.ClientReady, bot => {
message TEXT,
created_at INTEGER
)`);
+
+ db.run(`CREATE TABLE IF NOT EXISTS announcements_attachments (
+ id INTEGER PRIMARY KEY,
+ msg_id INTERGER,
+ file_name TEXT
+ )`);
+
+ db.run(`CREATE TABLE IF NOT EXISTS exclude_person (
+ id INTEGER PRIMARY KEY,
+ user_id INTERGER
+ )`);
} catch (e) {
console.error(e);
}
});
client.on(Events.MessageCreate, async (msg) => {
+ const query = db.prepare('SELECT * FROM exclude_person WHERE user_id = ?');
+ const result = query.get(msg.author.id);
+ if (result) return;
+
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;
+ 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);
+ if (!title || !author || !message) return;
+
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,
+ $title: removeMd(title),
$msg_id: msg.id,
- $author: author,
- $message: message,
+ $author: removeMd(author),
+ $message: await marked(message),
$created_at: msg.createdTimestamp.toString()
})
+
+ if (msg.attachments.size > 0) {
+ try {
+ mkdirSync(`../public/images/${msg.id}`, { recursive: true });
+
+ for (const a of msg.attachments.values()) {
+ const response = await fetch(a.url);
+ const buffer = Buffer.from(await response.arrayBuffer());
+ writeFileSync(`../public/images/${msg.id}/${a.name}`, buffer);
+
+ const insert = db.prepare(`INSERT INTO announcements_attachments (msg_id, file_name) VALUES ($msg_id, $file_name)`);
+ insert.run({
+ $msg_id: msg.id,
+ $file_name: a.name
+ });
+ }
+ } catch (e) {
+ console.error(e);
+ return;
+ }
+ }
+
} catch (e) {
console.error(e);
}
@@ -64,19 +106,34 @@ client.on(Events.MessageCreate, async (msg) => {
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;
+ 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);
+ if (!title || !author || !message) return;
+
+ try {
+ const removedAttachments = msg.attachments.filter(a => !msgnew.attachments.has(a.id));
+
+ for (const a of removedAttachments.values()) {
+ const deleteAttachment = db.prepare(`DELETE FROM announcements_attachments WHERE msg_id = ($msg_id) AND file_name = ($file_name)`);
+ deleteAttachment.run({
+ $msg_id: msg.id,
+ $file_name: a.name
+ });
+ }
+ } catch (e) {
+ console.error(e);
+ }
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,
+ $title: removeMd(title),
+ $author: removeMd(author),
+ $message: await marked(message),
$msg_id: msg.id
})
} catch (e) {
@@ -89,6 +146,7 @@ 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;
@@ -97,6 +155,15 @@ client.on(Events.MessageDelete, async (msg) => {
deleteAnnouncement.run({
$msg_id: msg.id
})
+
+ if (msg.attachments.size > 0) {
+ const deleteAttachment = db.prepare(`DELETE FROM announcements_attachments WHERE msg_id = ($msg_id)`);
+ deleteAttachment.run({
+ $msg_id: msg.id
+ });
+
+ rmSync(`../public/images/${msg.id}`, { recursive: true });
+ }
} catch (e) {
console.error(e);
}
diff --git a/bot/package.json b/bot/package.json
index 4ada057..546cb39 100644
--- a/bot/package.json
+++ b/bot/package.json
@@ -4,10 +4,13 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
- "discord.js": "^14.26.2"
+ "discord.js": "^14.26.2",
+ "marked": "^18.0.0",
+ "remove-markdown": "^0.6.3"
},
"devDependencies": {
"@types/bun": "^1.3.11",
- "@types/node": "^25.5.2"
+ "@types/node": "^25.5.2",
+ "@types/remove-markdown": "^0.3.4"
}
}
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..0952f11
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,16 @@
+services:
+ bot:
+ image: bnbmc-announcement-bot
+ hostname: bot
+ restart: unless-stopped
+ volumes:
+ - ./database.db:/database.db
+ - ./.env:/bot/.env
+ web:
+ image: bnbmc-announcement-bot-api
+ hostname: api
+ restart: unless-stopped
+ volumes:
+ - ./database.db:/database.db
+ ports:
+ - "3000:3000"