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(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 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 )`); 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.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); 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: removeMd(title), $msg_id: msg.id, $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); } }); client.on(Events.MessageUpdate, async (msg, msgnew) => { if (!msg.author) return; if (!msg.content) return; if (msg.author.bot) 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); 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: removeMd(title), $author: removeMd(author), $message: await marked(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 }) 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); } }); client.login(process.env.TOKEN);