Compare commits
	
		
			2 Commits
		
	
	
		
			d4ee457d74
			...
			ff5c7506ff
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ff5c7506ff | |||
| 5c51acc8d1 | 
							
								
								
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,21 +1,21 @@ | ||||
| { | ||||
|   "name": "pleroma-ollama-bot", | ||||
|   "version": "1.0.0", | ||||
|   "version": "1.0.5", | ||||
|   "lockfileVersion": 3, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "pleroma-ollama-bot", | ||||
|       "version": "1.0.0", | ||||
|       "version": "1.0.5", | ||||
|       "dependencies": { | ||||
|         "@prisma/client": "^6.10.1", | ||||
|         "@types/node": "^24.0.5", | ||||
|         "dotenv": "^17.0.0", | ||||
|         "striptags": "^3.2.0", | ||||
|         "ts-node": "^10.9.2", | ||||
|         "typescript": "^5.8.3" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
|         "@types/node": "^24.0.10", | ||||
|         "@types/ws": "^8.18.1", | ||||
|         "prisma": "^6.10.1" | ||||
|       } | ||||
| @ -164,10 +164,9 @@ | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@types/node": { | ||||
|       "version": "24.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.5.tgz", | ||||
|       "integrity": "sha512-CXEG9E7GCTOZIre0WdDznmnhvF7xi7AmnP/zF496trmLiqlfdtxp9nPRgLVqfmJ8jgtcKcs0EcvOu2yDZSuvTg==", | ||||
|       "license": "MIT", | ||||
|       "version": "24.0.10", | ||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", | ||||
|       "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", | ||||
|       "dependencies": { | ||||
|         "undici-types": "~7.8.0" | ||||
|       } | ||||
|  | ||||
| @ -9,16 +9,16 @@ | ||||
|   "type": "module", | ||||
|   "keywords": [], | ||||
|   "author": "NiceCrew", | ||||
|   "description": "A simple bot that responds to activities from Pleroma instances using Ollama's API.", | ||||
|   "description": "A simple bot that responds to activities from Pleroma instances using Ollama's API at a configurable interval.", | ||||
|   "dependencies": { | ||||
|     "@prisma/client": "^6.10.1", | ||||
|     "@types/node": "^24.0.5", | ||||
|     "dotenv": "^17.0.0", | ||||
|     "striptags": "^3.2.0", | ||||
|     "ts-node": "^10.9.2", | ||||
|     "typescript": "^5.8.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/node": "^24.0.10", | ||||
|     "@types/ws": "^8.18.1", | ||||
|     "prisma": "^6.10.1" | ||||
|   } | ||||
|  | ||||
							
								
								
									
										88
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								src/main.ts
									
									
									
									
									
								
							| @ -10,14 +10,35 @@ import { PrismaClient } from "../generated/prisma/client.js"; | ||||
|  | ||||
| const prisma = new PrismaClient(); | ||||
|  | ||||
| const envConfig = { | ||||
|   pleromaInstanceUrl: process.env.PLEROMA_INSTANCE_URL || "", | ||||
|   pleromaInstanceDomain: process.env.PLEROMA_INSTANCE_DOMAIN || "", | ||||
|   onlyLocalReplies: process.env.ONLY_LOCAL_REPLIES === "true" ? true : false, | ||||
|   ollamaUrl: process.env.OLLAMA_URL || "", | ||||
|   ollamaSystemPrompt: | ||||
|     process.env.OLLAMA_SYSTEM_PROMPT || | ||||
|     "You are a helpful AI assistant. Answer all questions concisely.", | ||||
|   ollamaModel: process.env.OLLAMA_MODEL || "", | ||||
|   fetchInterval: process.env.FETCH_INTERVAL | ||||
|     ? parseInt(process.env.FETCH_INTERVAL) | ||||
|     : 15000, | ||||
|   bearerToken: process.env.INSTANCE_BEARER_TOKEN || "", | ||||
| }; | ||||
|  | ||||
| const ollamaConfig: OllamaConfigOptions = { | ||||
|   temperature: 0.3, | ||||
|   num_predict: 400, | ||||
| }; | ||||
|  | ||||
| const getNotifications = async () => { | ||||
|   const { bearerToken, pleromaInstanceUrl } = envConfig; | ||||
|   try { | ||||
|     const request = await fetch( | ||||
|       `${process.env.PLEROMA_INSTANCE_URL}/api/v1/notifications?types[]=mention`, | ||||
|       `${pleromaInstanceUrl}/api/v1/notifications?types[]=mention`, | ||||
|       { | ||||
|         method: "GET", | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${process.env.INSTANCE_BEARER_TOKEN}`, | ||||
|           Authorization: `Bearer ${bearerToken}`, | ||||
|         }, | ||||
|       } | ||||
|     ); | ||||
| @ -82,11 +103,12 @@ const storePromptData = async ( | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const trimInputData = (input: string) => { | ||||
| const trimInputData = (input: string): string => { | ||||
|   const strippedInput = striptags(input); | ||||
|   const split = strippedInput.split(" "); | ||||
|   const promptStringIndex = split.indexOf("!prompt"); | ||||
|   return split.slice(promptStringIndex + 1).join(" "); // returns everything after the !prompt | ||||
|   split.splice(promptStringIndex, 1); | ||||
|   return split.join(" "); // returns everything after the !prompt | ||||
| }; | ||||
|  | ||||
| const recordPendingResponse = async (notification: Notification) => { | ||||
| @ -104,6 +126,13 @@ const recordPendingResponse = async (notification: Notification) => { | ||||
| const generateOllamaRequest = async ( | ||||
|   notification: Notification | ||||
| ): Promise<OllamaResponse | undefined> => { | ||||
|   const { | ||||
|     onlyLocalReplies, | ||||
|     pleromaInstanceDomain, | ||||
|     ollamaModel, | ||||
|     ollamaSystemPrompt, | ||||
|     ollamaUrl, | ||||
|   } = envConfig; | ||||
|   try { | ||||
|     if ( | ||||
|       striptags(notification.status.content).includes("!prompt") && | ||||
| @ -111,10 +140,8 @@ const generateOllamaRequest = async ( | ||||
|       notification.type === "mention" | ||||
|     ) { | ||||
|       if ( | ||||
|         process.env.ONLY_LOCAL_REPLIES === "true" && | ||||
|         !notification.status.account.fqn.includes( | ||||
|           `@${process.env.PLEROMA_INSTANCE_DOMAIN}` | ||||
|         ) | ||||
|         onlyLocalReplies && | ||||
|         !notification.status.account.fqn.includes(`@${pleromaInstanceDomain}`) | ||||
|       ) { | ||||
|         return; | ||||
|       } | ||||
| @ -123,20 +150,16 @@ const generateOllamaRequest = async ( | ||||
|       } | ||||
|       await recordPendingResponse(notification); | ||||
|       await storeUserData(notification); | ||||
|       const ollamaConfig: OllamaConfigOptions = { | ||||
|         temperature: 1.2, | ||||
|         num_predict: 400, | ||||
|       }; | ||||
|       const ollamaRequestBody: OllamaRequest = { | ||||
|         model: process.env.OLLAMA_MODEL as string, | ||||
|         system: process.env.OLLAMA_SYSTEM_PROMPT as string, | ||||
|         model: ollamaModel, | ||||
|         system: ollamaSystemPrompt, | ||||
|         prompt: `@${notification.status.account.fqn} says: ${trimInputData( | ||||
|           notification.status.content | ||||
|         )}`, | ||||
|         stream: false, | ||||
|         options: ollamaConfig, | ||||
|       }; | ||||
|       const response = await fetch(`${process.env.OLLAMA_URL}/api/generate`, { | ||||
|       const response = await fetch(`${ollamaUrl}/api/generate`, { | ||||
|         method: "POST", | ||||
|         body: JSON.stringify(ollamaRequestBody), | ||||
|       }); | ||||
| @ -153,6 +176,7 @@ const postReplyToStatus = async ( | ||||
|   notification: Notification, | ||||
|   ollamaResponseBody: OllamaResponse | ||||
| ) => { | ||||
|   const { pleromaInstanceUrl, bearerToken } = envConfig; | ||||
|   try { | ||||
|     let mentions: string[]; | ||||
|     const statusBody: NewStatusBody = { | ||||
| @ -170,17 +194,14 @@ const postReplyToStatus = async ( | ||||
|       statusBody.to = mentions; | ||||
|     } | ||||
|  | ||||
|     const response = await fetch( | ||||
|       `${process.env.PLEROMA_INSTANCE_URL}/api/v1/statuses`, | ||||
|       { | ||||
|         method: "POST", | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${process.env.INSTANCE_BEARER_TOKEN}`, | ||||
|           "Content-Type": "application/json", | ||||
|         }, | ||||
|         body: JSON.stringify(statusBody), | ||||
|       } | ||||
|     ); | ||||
|     const response = await fetch(`${pleromaInstanceUrl}/api/v1/statuses`, { | ||||
|       method: "POST", | ||||
|       headers: { | ||||
|         Authorization: `Bearer ${bearerToken}`, | ||||
|         "Content-Type": "application/json", | ||||
|       }, | ||||
|       body: JSON.stringify(statusBody), | ||||
|     }); | ||||
|  | ||||
|     if (!response.ok) { | ||||
|       throw new Error(`New status request failed: ${response.statusText}`); | ||||
| @ -193,6 +214,7 @@ const postReplyToStatus = async ( | ||||
| }; | ||||
|  | ||||
| const deleteNotification = async (notification: Notification) => { | ||||
|   const { pleromaInstanceUrl, bearerToken } = envConfig; | ||||
|   try { | ||||
|     if (!notification.id) { | ||||
|       return; | ||||
| @ -203,11 +225,11 @@ const deleteNotification = async (notification: Notification) => { | ||||
|       data: { isProcessing: false }, | ||||
|     }); | ||||
|     const response = await fetch( | ||||
|       `${process.env.PLEROMA_INSTANCE_URL}/api/v1/notifications/${notification.id}/dismiss`, | ||||
|       `${pleromaInstanceUrl}/api/v1/notifications/${notification.id}/dismiss`, | ||||
|       { | ||||
|         method: "POST", | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${process.env.INSTANCE_BEARER_TOKEN}`, | ||||
|           Authorization: `Bearer ${bearerToken}`, | ||||
|         }, | ||||
|       } | ||||
|     ); | ||||
| @ -221,10 +243,6 @@ const deleteNotification = async (notification: Notification) => { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const fetchInterval = process.env.FETCH_INTERVAL | ||||
|   ? parseInt(process.env.FETCH_INTERVAL) | ||||
|   : 15000; | ||||
|  | ||||
| let notifications = []; | ||||
| const beginFetchCycle = async () => { | ||||
|   setInterval(async () => { | ||||
| @ -243,12 +261,12 @@ const beginFetchCycle = async () => { | ||||
|         }) | ||||
|       ); | ||||
|     } | ||||
|   }, 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 | ||||
|   }, envConfig.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 | ||||
|   `Fetching notifications from ${envConfig.pleromaInstanceDomain}, every ${ | ||||
|     envConfig.fetchInterval / 1000 | ||||
|   } seconds.` | ||||
| ); | ||||
| await beginFetchCycle(); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user