From b295777041c5df3747fcbbf11ba6972349b96f04 Mon Sep 17 00:00:00 2001 From: matty Date: Tue, 1 Jul 2025 15:25:00 -0400 Subject: [PATCH] add websocket functionality --- .env.example | 3 +- README.md | 2 +- package-lock.json | 11 +++++++ package.json | 3 +- src/main.ts | 78 +++++++++++++++++++++++++++++++---------------- src/websocket.ts | 22 +++++++++++++ types.d.ts | 6 ++++ 7 files changed, 95 insertions(+), 30 deletions(-) create mode 100644 src/websocket.ts diff --git a/.env.example b/.env.example index c03549d..98a4c14 100644 --- a/.env.example +++ b/.env.example @@ -5,4 +5,5 @@ ONLY_LOCAL_REPLIES="true" # reply to only users locally on your instance OLLAMA_URL="http://localhost:11434" # OLLAMA connection URL OLLAMA_SYSTEM_PROMPT="" # system prompt - used to help tune the responses from the AI OLLAMA_MODEL="" # Ollama model for responses e.g dolphin-mistral:latest -INSTANCE_BEARER_TOKEN="" # instance auth/bearer token (check the "verify_credentials" endpoint request headers in Chrome DevTools if on Soapbox) \ No newline at end of file +INSTANCE_BEARER_TOKEN="" # instance auth/bearer token (check the "verify_credentials" endpoint request headers in Chrome DevTools if on Soapbox) +SOAPBOX_WS_PROTOCOL="" # this is the header required to authenticate to the websocket. No idea why Soapbox does it like this. You can get it in the request headers for the socket in Chrome DevTools \ No newline at end of file diff --git a/README.md b/README.md index a54bad9..c72d558 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ 2. Install npm 22.11.0 if you don't have it already 3. `cd` into the project directory 4. Run `npm install` -5. Run `npx prisma init --datasource-provider sqlite --output ../generated/prisma` 6. Run `npx prisma migrate dev --name init` 7. To run the software on a cronjob, use `npm run once` +8. To run continuously, use `npm run ws` ### Database Migrations diff --git a/package-lock.json b/package-lock.json index 85fe4d4..5b85ba2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "ws": "^8.18.3" }, "devDependencies": { + "@types/ws": "^8.18.1", "prisma": "^6.10.1" } }, @@ -172,6 +173,16 @@ "undici-types": "~7.8.0" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", diff --git a/package.json b/package.json index 60a44bb..3817e56 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "main": "index.js", "scripts": { - "once": "tsc && node -r dotenv/config dist/main.js", + "start": "tsc && node -r dotenv/config dist/main.js", "build": "tsc" }, "type": "module", @@ -20,6 +20,7 @@ "ws": "^8.18.3" }, "devDependencies": { + "@types/ws": "^8.18.1", "prisma": "^6.10.1" } } diff --git a/src/main.ts b/src/main.ts index 86d3c0d..f6c5593 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,33 +3,35 @@ import { OllamaResponse, NewStatusBody, Notification, + WSEvent, } from "../types.js"; import striptags from "striptags"; import { PrismaClient } from "../generated/prisma/client.js"; +import { createWebsocket } from "./websocket.js"; const prisma = new PrismaClient(); -const getNotifications = async () => { - try { - const request = await fetch( - `${process.env.PLEROMA_INSTANCE_URL}/api/v1/notifications?types[]=mention`, - { - method: "GET", - headers: { - Authorization: `Bearer ${process.env.INSTANCE_BEARER_TOKEN}`, - }, - } - ); +// const getNotifications = async () => { +// try { +// const request = await fetch( +// `${process.env.PLEROMA_INSTANCE_URL}/api/v1/notifications?types[]=mention`, +// { +// method: "GET", +// headers: { +// Authorization: `Bearer ${process.env.INSTANCE_BEARER_TOKEN}`, +// }, +// } +// ); - const notifications: Notification[] = await request.json(); +// const notifications: Notification[] = await request.json(); - return notifications; - } catch (error: any) { - throw new Error(error.message); - } -}; +// return notifications; +// } catch (error: any) { +// throw new Error(error.message); +// } +// }; -const notifications = await getNotifications(); +// const notifications = await getNotifications(); const storeUserData = async (notification: Notification): Promise => { try { @@ -171,13 +173,35 @@ const postReplyToStatus = async ( } }; -if (notifications) { - await Promise.all( - notifications.map(async (notification) => { - const ollamaResponse = await generateOllamaRequest(notification); - if (ollamaResponse) { - postReplyToStatus(notification, ollamaResponse); - } - }) +const ws = createWebsocket(); + +ws.on("upgrade", () => { + console.log( + `Websocket connection to ${process.env.PLEROMA_INSTANCE_DOMAIN} successful.` ); -} +}); + +ws.on("message", async (data) => { + const message: WSEvent = JSON.parse(data.toString("utf-8")); + if (message.event !== "notification") { + // only watch for notification events + return; + } + console.log("Websocket message received."); + const payload = JSON.parse(message.payload) as Notification; + const ollamaResponse = await generateOllamaRequest(payload); + if (ollamaResponse) { + await postReplyToStatus(payload, ollamaResponse); + } +}); + +// if (notifications) { +// await Promise.all( +// notifications.map(async (notification) => { +// const ollamaResponse = await generateOllamaRequest(notification); +// if (ollamaResponse) { +// postReplyToStatus(notification, ollamaResponse); +// } +// }) +// ); +// } diff --git a/src/websocket.ts b/src/websocket.ts new file mode 100644 index 0000000..e3740d6 --- /dev/null +++ b/src/websocket.ts @@ -0,0 +1,22 @@ +import { WebSocket } from "ws"; + +const scheme = process.env.PLEROMA_INSTANCE_URL?.startsWith("https") + ? "wss" + : "ws"; // this is so nigger rigged +const host = process.env.PLEROMA_INSTANCE_DOMAIN; + +export const createWebsocket = (): WebSocket => { + try { + const ws = new WebSocket( // only connects to Soapbox frontends right now, but could pretty easily connect to Pleroma frontends with some tweaking + `${scheme}://${host}/api/v1/streaming?stream=user`, + [process.env.SOAPBOX_WS_PROTOCOL as string], + { + followRedirects: true, + } + ); + + return ws; + } catch (error: any) { + throw new Error(error); + } +}; diff --git a/types.d.ts b/types.d.ts index 68a906e..2a755e1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -64,3 +64,9 @@ export interface Mention { url: string; username: string; } + +export interface WSEvent { + event: "update" | "status.update" | "notification"; + payload: string; + stream: "user" | "direct"; +}