From d99deee20918e5391dc14c98f4d9ec16e9cf41d0 Mon Sep 17 00:00:00 2001 From: Ashley Lamont Date: Wed, 16 Oct 2024 11:18:59 +1100 Subject: [PATCH] Quotes API --- src/index.ts | 2 + src/quotes-server.ts | 105 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/quotes-server.ts diff --git a/src/index.ts b/src/index.ts index 9066bc4..7905f4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import { attachNocoDBWebhookListener } from "./nocodb-integration"; import { startServerIcon } from "./server-icon"; import registerCommands from "./command-registry"; import addStageOne from "./secret"; +import { attachQuotesServer } from "./quotes-server"; const PORT = 8080; globalThis.appMaintainers = []; @@ -32,6 +33,7 @@ async function main(): Promise { // Initialise the express app and attach the door server const expressApp = express(); await attachDoorServer(expressApp); + await attachQuotesServer(expressApp); await attachNocoDBWebhookListener(expressApp); // Start the server icon and add reaction events diff --git a/src/quotes-server.ts b/src/quotes-server.ts new file mode 100644 index 0000000..6f9a268 --- /dev/null +++ b/src/quotes-server.ts @@ -0,0 +1,105 @@ +import type { Express } from "express"; +import { Collection, Message } from "discord.js"; +import { Quote } from "./commands/quote"; + +const quotes: Collection = new Collection(); + +export function convertMessageToQuote( + message: Message, +): Quote | undefined { + // Check if the message is an embed + if (message.embeds.length === 1) { + const embed = message.embeds[0]; + let author: Quote["quotee"] = embed.title ?? undefined; + if (author == "Anonymous Quote") { + author = undefined; + } + author = author?.replace("Quote of ", ""); + return { + message: embed.description ?? "", + quotee: author, + timestamp: new Date(embed.timestamp || message.createdTimestamp), + }; + } else { + const messageContent = message.content; + if (!messageContent.startsWith("> ")) { + return undefined; + } + const quoteBody = messageContent; + if (message.mentions.members.size > 0) { + return { + message: quoteBody, + quotee: message.mentions.members + .map((member) => member.displayName) + .join(", "), + timestamp: message.createdAt, + }; + } else { + // Guess the quotee + const quotee = messageContent + .split("\n") + .find((line) => !line.startsWith("> ")); + if (quotee === undefined) { + return undefined; + } + return { + message: quoteBody, + quotee, + timestamp: message.createdAt, + }; + } + } +} + +export async function refreshQuotes() { + const quotesChannel = await globalThis.cssaGuild.channels.fetch( + "1169166790094491678", + ); + if (quotesChannel === null) { + throw new Error("Quotes channel is null"); + } else if (!quotesChannel.isTextBased()) { + throw new Error("Quotes channel is not text based"); + } + // Start fetching recent top-level messages + let hitCachedMessage = false; + let messages = await quotesChannel.messages.fetch({ limit: 100 }); + while (!hitCachedMessage && messages.size > 0) { + for (const message of messages.values()) { + const quote = convertMessageToQuote(message); + if (quote !== undefined) { + if (quotes.has(message.id)) { + hitCachedMessage = true; + } + quotes.set(message.id, quote); + } + } + if (!hitCachedMessage) { + const lastMessage = messages.last(); + if (lastMessage === undefined) { + break; + } + // eslint-disable-next-line no-await-in-loop + messages = await quotesChannel.messages.fetch({ + limit: 100, + before: lastMessage.id, + }); + } + } + return; +} + +export async function attachQuotesServer(app: Express) { + await refreshQuotes(); + app.get("/quotes", (_, response) => { + refreshQuotes().then(() => { + response.set("Content-Type", "application/json"); + response.send( + JSON.stringify({ + quotes: [...quotes.values()].sort( + (a, b) => b.timestamp.getTime() - a.timestamp.getTime(), + ), + }), + ); + }); + }); +}