Compare commits
15 Commits
add-websoc
...
d85acd2179
Author | SHA1 | Date | |
---|---|---|---|
d85acd2179 | |||
856cc84208 | |||
ca4643092f | |||
b4b656f808 | |||
92f1366574 | |||
a64afa7e7b | |||
d63aa365e7 | |||
3759c5aa23 | |||
1a151b197b | |||
70180c5d5f | |||
dac037809c | |||
6088a2cbd3 | |||
ed8d148d0a | |||
379099dc7a | |||
c0ed38ac1a |
@ -5,5 +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
|
||||
FETCH_INTERVAL="" # interval for fetching new notifications from the instance, in milliseconds, recommend at least 15000
|
||||
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
|
10
README.md
10
README.md
@ -1,15 +1,15 @@
|
||||
## Pleroma -> Ollama Bot Setup
|
||||
|
||||
1. Clone project
|
||||
2. Install npm 22.11.0 if you don't have it already
|
||||
2. Install Node `v22.11.0` if you don't have it already
|
||||
* If using `nvm`, just `nvm install 22.11.0` and then `nvm use 22.11.0` if necessary
|
||||
3. `cd` into the project directory
|
||||
4. Run `npm install`
|
||||
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`
|
||||
7. To start, run `npm run start`
|
||||
|
||||
I recommend using `screen` to run this in the background until a `systemd` service can be created. I just haven't bothered to do it yet.
|
||||
|
||||
### Database Migrations
|
||||
|
||||
If you add stuff to the schema, follow the [Prisma development workflow](https://www.prisma.io/docs/orm/prisma-migrate/workflows/development-and-production). This will apply the new schema to the database and generate a new Prisma client with type safety.
|
||||
|
||||
Setting as a system service will come at some point, or someone could contribute if they wanted.
|
24
package-lock.json
generated
24
package-lock.json
generated
@ -13,8 +13,7 @@
|
||||
"dotenv": "^17.0.0",
|
||||
"striptags": "^3.2.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3",
|
||||
"ws": "^8.18.3"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.18.1",
|
||||
@ -356,27 +355,6 @@
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
@ -16,8 +16,7 @@
|
||||
"dotenv": "^17.0.0",
|
||||
"striptags": "^3.2.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3",
|
||||
"ws": "^8.18.3"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.18.1",
|
||||
|
120
src/main.ts
120
src/main.ts
@ -3,35 +3,31 @@ 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);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const notifications = await getNotifications();
|
||||
return notifications;
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const storeUserData = async (notification: Notification): Promise<void> => {
|
||||
try {
|
||||
@ -97,7 +93,8 @@ const generateOllamaRequest = async (
|
||||
try {
|
||||
if (
|
||||
striptags(notification.status.content).includes("!prompt") &&
|
||||
!notification.status.account.bot
|
||||
!notification.status.account.bot && // sanity check, sort of
|
||||
notification.type === "mention"
|
||||
) {
|
||||
if (
|
||||
process.env.ONLY_LOCAL_REPLIES === "true" &&
|
||||
@ -168,40 +165,63 @@ const postReplyToStatus = async (
|
||||
if (!response.ok) {
|
||||
throw new Error(`New status request failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
await deleteNotification(notification);
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
const deleteNotification = async (notification: Notification) => {
|
||||
try {
|
||||
if (!notification.id) {
|
||||
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);
|
||||
const response = await fetch(
|
||||
`${process.env.PLEROMA_INSTANCE_URL}/api/v1/notifications/${notification.id}/dismiss`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.INSTANCE_BEARER_TOKEN}`,
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
if (!response.ok) {
|
||||
console.error(`Could not delete notification ID: ${notification.id}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// if (notifications) {
|
||||
// await Promise.all(
|
||||
// notifications.map(async (notification) => {
|
||||
// const ollamaResponse = await generateOllamaRequest(notification);
|
||||
// if (ollamaResponse) {
|
||||
// postReplyToStatus(notification, ollamaResponse);
|
||||
// }
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
const fetchInterval = process.env.FETCH_INTERVAL
|
||||
? parseInt(process.env.FETCH_INTERVAL)
|
||||
: 15000;
|
||||
|
||||
const beginFetchCycle = async () => {
|
||||
let notifications = [];
|
||||
setInterval(async () => {
|
||||
notifications = await getNotifications();
|
||||
if (notifications.length > 0) {
|
||||
await Promise.all(
|
||||
notifications.map(async (notification) => {
|
||||
try {
|
||||
const ollamaResponse = await generateOllamaRequest(notification);
|
||||
if (ollamaResponse) {
|
||||
postReplyToStatus(notification, ollamaResponse);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}, fetchInterval); // lower intervals may cause the bot to respond multiple times to the same message, but we try to mitigate this with the deleteNotification function
|
||||
};
|
||||
|
||||
console.log(
|
||||
`Fetching notifications from ${process.env.PLEROMA_INSTANCE_DOMAIN}, every ${
|
||||
fetchInterval / 1000
|
||||
} seconds.`
|
||||
);
|
||||
await beginFetchCycle();
|
||||
|
@ -1,22 +0,0 @@
|
||||
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);
|
||||
}
|
||||
};
|
3
types.d.ts
vendored
3
types.d.ts
vendored
@ -1,6 +1,9 @@
|
||||
export interface Notification {
|
||||
account: Account;
|
||||
status: Status;
|
||||
id: string;
|
||||
type: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface NewStatusBody {
|
||||
|
Reference in New Issue
Block a user