From cbf6b1d3ebfc918954b64bc4960c6d01036be271 Mon Sep 17 00:00:00 2001 From: matty Date: Sun, 6 Jul 2025 14:53:48 +0000 Subject: [PATCH] its called we do a little abstraction --- src/api.ts | 75 ++++++++++++++++++++ src/main.ts | 187 +++++--------------------------------------------- src/prisma.ts | 41 +++++++++++ src/util.ts | 73 ++++++++++++++++++++ 4 files changed, 205 insertions(+), 171 deletions(-) create mode 100644 src/api.ts create mode 100644 src/prisma.ts create mode 100644 src/util.ts diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..78359b3 --- /dev/null +++ b/src/api.ts @@ -0,0 +1,75 @@ +import { envConfig, prisma } from "./main.js"; +import { PleromaEmoji, Notification } from "../types.js"; + +const getNotifications = async () => { + const { bearerToken, pleromaInstanceUrl } = envConfig; + try { + const request = await fetch( + `${pleromaInstanceUrl}/api/v1/notifications?types[]=mention`, + { + method: "GET", + headers: { + Authorization: `Bearer ${bearerToken}`, + }, + } + ); + + const notifications: Notification[] = await request.json(); + + return notifications; + } catch (error: any) { + throw new Error(error.message); + } +}; + +const getInstanceEmojis = async () => { + const { bearerToken, pleromaInstanceUrl } = envConfig; + try { + const request = await fetch(`${pleromaInstanceUrl}/api/v1/pleroma/emoji`, { + method: "GET", + headers: { + Authorization: `Bearer ${bearerToken}`, + }, + }); + if (!request.ok) { + console.error(`Emoji GET failed: ${request.status}`); + return; + } + const emojis: PleromaEmoji[] = await request.json(); + return Object.keys(emojis); + } catch (error: any) { + console.error(`Could not fetch emojis: ${error.message}`); + } +}; + +const deleteNotification = async (notification: Notification) => { + const { pleromaInstanceUrl, bearerToken } = envConfig; + try { + if (!notification.id) { + return; + } + await prisma.response.updateMany({ + // this is probably not the best way to do this, but since we may have duplicate notifications, we have to update all of them - probably won't scale (lmao) + where: { pleromaNotificationId: notification.id }, + data: { isProcessing: false }, + }); + const response = await fetch( + `${pleromaInstanceUrl}/api/v1/notifications/${notification.id}/dismiss`, + { + method: "POST", + headers: { + Authorization: `Bearer ${bearerToken}`, + }, + } + ); + if (!response.ok) { + console.error( + `Could not delete notification ID: ${notification.id}\nReason: ${response.status} - ${response.statusText}` + ); + } + } catch (error: any) { + throw new Error(error.message); + } +}; + +export { deleteNotification, getInstanceEmojis, getNotifications }; diff --git a/src/main.ts b/src/main.ts index c87eb92..04ffda8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,14 +4,26 @@ import { NewStatusBody, Notification, OllamaConfigOptions, - PleromaEmoji, } from "../types.js"; import striptags from "striptags"; import { PrismaClient } from "../generated/prisma/client.js"; +import { + getInstanceEmojis, + deleteNotification, + getNotifications, +} from "./api.js"; +import { storeUserData, storePromptData } from "./prisma.js"; +import { + isFromWhitelistedDomain, + alreadyRespondedTo, + recordPendingResponse, + trimInputData, + selectRandomEmoji, +} from "./util.js"; -const prisma = new PrismaClient(); +export const prisma = new PrismaClient(); -const envConfig = { +export const envConfig = { pleromaInstanceUrl: process.env.PLEROMA_INSTANCE_URL || "", pleromaInstanceDomain: process.env.PLEROMA_INSTANCE_DOMAIN || "", whitelistOnly: process.env.ONLY_WHITELIST === "true" ? true : false || "true", @@ -33,143 +45,6 @@ const ollamaConfig: OllamaConfigOptions = { temperature: 1.2, }; -const getNotifications = async () => { - const { bearerToken, pleromaInstanceUrl } = envConfig; - try { - const request = await fetch( - `${pleromaInstanceUrl}/api/v1/notifications?types[]=mention`, - { - method: "GET", - headers: { - Authorization: `Bearer ${bearerToken}`, - }, - } - ); - - const notifications: Notification[] = await request.json(); - - return notifications; - } catch (error: any) { - throw new Error(error.message); - } -}; - -const getInstanceEmojis = async () => { - const { bearerToken, pleromaInstanceUrl } = envConfig; - try { - const request = await fetch(`${pleromaInstanceUrl}/api/v1/pleroma/emoji`, { - method: "GET", - headers: { - Authorization: `Bearer ${bearerToken}`, - }, - }); - if (!request.ok) { - console.error(`Emoji GET failed: ${request.status}`); - return; - } - const emojis: PleromaEmoji[] = await request.json(); - return Object.keys(emojis); - } catch (error: any) { - console.error(`Could not fetch emojis: ${error.message}`); - } -}; - -const selectRandomEmoji = (emojiList: string[]) => { - return emojiList[Math.floor(Math.random() * emojiList.length)]; -}; - -const storeUserData = async (notification: Notification): Promise => { - try { - await prisma.user.upsert({ - where: { userFqn: notification.status.account.fqn }, - update: { - lastRespondedTo: new Date(Date.now()), - }, - create: { - userFqn: notification.status.account.fqn, - lastRespondedTo: new Date(Date.now()), - }, - }); - } catch (error: any) { - throw new Error(error.message); - } -}; - -const alreadyRespondedTo = async ( - notification: Notification -): Promise => { - try { - const duplicate = await prisma.response.findFirst({ - where: { pleromaNotificationId: notification.id, isProcessing: true }, - }); - if (duplicate) { - return true; - } - return false; - } catch (error: any) { - throw new Error(error.message); - } -}; - -const storePromptData = async ( - notification: Notification, - ollamaResponseBody: OllamaResponse -) => { - try { - await prisma.response.updateMany({ - where: { pleromaNotificationId: notification.id }, - data: { - response: ollamaResponseBody.response, - request: trimInputData(notification.status.content), - to: notification.account.fqn, - isProcessing: false, - }, - }); - } catch (error: any) { - throw new Error(error.message); - } -}; - -const trimInputData = (input: string): string => { - const strippedInput = striptags(input); - const split = strippedInput.split(" "); - const promptStringIndex = split.indexOf("!prompt"); - split.splice(promptStringIndex, 1); - return split.join(" "); // returns everything after the !prompt -}; - -const recordPendingResponse = async (notification: Notification) => { - try { - await prisma.response.create({ - data: { - pleromaNotificationId: notification.id, - }, - }); - } catch (error: any) { - throw new Error(error.message); - } -}; - -const isFromWhitelistedDomain = async ( - notification: Notification -): Promise => { - try { - const domain = notification.status.account.fqn.split("@")[1]; - if (envConfig.whitelistedDomains.includes(domain)) { - return true; - } - console.log( - `Rejecting prompt request from non-whitelisted domain: ${domain}` - ); - // delete the notification so we don't keep trying to fetch it - await deleteNotification(notification); - return false; - } catch (error: any) { - console.error(`Error with domain check: ${error.message}`); - return false; - } -}; - const generateOllamaRequest = async ( notification: Notification ): Promise => { @@ -258,36 +133,6 @@ const postReplyToStatus = async ( } }; -const deleteNotification = async (notification: Notification) => { - const { pleromaInstanceUrl, bearerToken } = envConfig; - try { - if (!notification.id) { - return; - } - await prisma.response.updateMany({ - // this is probably not the best way to do this, but since we may have duplicate notifications, we have to update all of them - probably won't scale (lmao) - where: { pleromaNotificationId: notification.id }, - data: { isProcessing: false }, - }); - const response = await fetch( - `${pleromaInstanceUrl}/api/v1/notifications/${notification.id}/dismiss`, - { - method: "POST", - headers: { - Authorization: `Bearer ${bearerToken}`, - }, - } - ); - if (!response.ok) { - console.error( - `Could not delete notification ID: ${notification.id}\nReason: ${response.status} - ${response.statusText}` - ); - } - } catch (error: any) { - throw new Error(error.message); - } -}; - let notifications = []; const beginFetchCycle = async () => { setInterval(async () => { @@ -315,6 +160,6 @@ console.log( } seconds.` ); console.log( - `Accepting prompts from domains: ${envConfig.whitelistedDomains.join(", ")}` + `Accepting prompts from: ${envConfig.whitelistedDomains.join(", ")}` ); await beginFetchCycle(); diff --git a/src/prisma.ts b/src/prisma.ts new file mode 100644 index 0000000..3045205 --- /dev/null +++ b/src/prisma.ts @@ -0,0 +1,41 @@ +import { Notification, OllamaResponse } from "../types.js"; +import { trimInputData } from "./util.js"; +import { prisma } from "./main.js"; + +const storePromptData = async ( + notification: Notification, + ollamaResponseBody: OllamaResponse +) => { + try { + await prisma.response.updateMany({ + where: { pleromaNotificationId: notification.id }, + data: { + response: ollamaResponseBody.response, + request: trimInputData(notification.status.content), + to: notification.account.fqn, + isProcessing: false, + }, + }); + } catch (error: any) { + throw new Error(error.message); + } +}; + +const storeUserData = async (notification: Notification): Promise => { + try { + await prisma.user.upsert({ + where: { userFqn: notification.status.account.fqn }, + update: { + lastRespondedTo: new Date(Date.now()), + }, + create: { + userFqn: notification.status.account.fqn, + lastRespondedTo: new Date(Date.now()), + }, + }); + } catch (error: any) { + throw new Error(error.message); + } +}; + +export { storeUserData, storePromptData }; diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..b5bcb9b --- /dev/null +++ b/src/util.ts @@ -0,0 +1,73 @@ +import striptags from "striptags"; +import { prisma } from "./main.js"; +import { envConfig } from "./main.js"; +import { Notification } from "../types.js"; +import { deleteNotification } from "./api.js"; + +const trimInputData = (input: string): string => { + const strippedInput = striptags(input); + const split = strippedInput.split(" "); + const promptStringIndex = split.indexOf("!prompt"); + split.splice(promptStringIndex, 1); + return split.join(" "); // returns everything after the !prompt +}; + +const recordPendingResponse = async (notification: Notification) => { + try { + await prisma.response.create({ + data: { + pleromaNotificationId: notification.id, + }, + }); + } catch (error: any) { + throw new Error(error.message); + } +}; + +const isFromWhitelistedDomain = async ( + notification: Notification +): Promise => { + try { + const domain = notification.status.account.fqn.split("@")[1]; + if (envConfig.whitelistedDomains.includes(domain)) { + return true; + } + console.log( + `Rejecting prompt request from non-whitelisted domain: ${domain}` + ); + // delete the notification so we don't keep trying to fetch it + await deleteNotification(notification); + return false; + } catch (error: any) { + console.error(`Error with domain check: ${error.message}`); + return false; + } +}; + +const alreadyRespondedTo = async ( + notification: Notification +): Promise => { + try { + const duplicate = await prisma.response.findFirst({ + where: { pleromaNotificationId: notification.id, isProcessing: true }, + }); + if (duplicate) { + return true; + } + return false; + } catch (error: any) { + throw new Error(error.message); + } +}; + +const selectRandomEmoji = (emojiList: string[]) => { + return emojiList[Math.floor(Math.random() * emojiList.length)]; +}; + +export { + alreadyRespondedTo, + selectRandomEmoji, + trimInputData, + recordPendingResponse, + isFromWhitelistedDomain, +};