its called we do a little abstraction
This commit is contained in:
		
							
								
								
									
										75
									
								
								src/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/api.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 }; | ||||||
							
								
								
									
										187
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										187
									
								
								src/main.ts
									
									
									
									
									
								
							| @ -4,14 +4,26 @@ import { | |||||||
|   NewStatusBody, |   NewStatusBody, | ||||||
|   Notification, |   Notification, | ||||||
|   OllamaConfigOptions, |   OllamaConfigOptions, | ||||||
|   PleromaEmoji, |  | ||||||
| } from "../types.js"; | } from "../types.js"; | ||||||
| import striptags from "striptags"; | import striptags from "striptags"; | ||||||
| import { PrismaClient } from "../generated/prisma/client.js"; | 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 || "", |   pleromaInstanceUrl: process.env.PLEROMA_INSTANCE_URL || "", | ||||||
|   pleromaInstanceDomain: process.env.PLEROMA_INSTANCE_DOMAIN || "", |   pleromaInstanceDomain: process.env.PLEROMA_INSTANCE_DOMAIN || "", | ||||||
|   whitelistOnly: process.env.ONLY_WHITELIST === "true" ? true : false || "true", |   whitelistOnly: process.env.ONLY_WHITELIST === "true" ? true : false || "true", | ||||||
| @ -33,143 +45,6 @@ const ollamaConfig: OllamaConfigOptions = { | |||||||
|   temperature: 1.2, |   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<void> => { |  | ||||||
|   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<boolean> => { |  | ||||||
|   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<boolean> => { |  | ||||||
|   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 ( | const generateOllamaRequest = async ( | ||||||
|   notification: Notification |   notification: Notification | ||||||
| ): Promise<OllamaResponse | undefined> => { | ): Promise<OllamaResponse | undefined> => { | ||||||
| @ -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 = []; | let notifications = []; | ||||||
| const beginFetchCycle = async () => { | const beginFetchCycle = async () => { | ||||||
|   setInterval(async () => { |   setInterval(async () => { | ||||||
| @ -315,6 +160,6 @@ console.log( | |||||||
|   } seconds.` |   } seconds.` | ||||||
| ); | ); | ||||||
| console.log( | console.log( | ||||||
|   `Accepting prompts from domains: ${envConfig.whitelistedDomains.join(", ")}` |   `Accepting prompts from: ${envConfig.whitelistedDomains.join(", ")}` | ||||||
| ); | ); | ||||||
| await beginFetchCycle(); | await beginFetchCycle(); | ||||||
|  | |||||||
							
								
								
									
										41
									
								
								src/prisma.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/prisma.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<void> => { | ||||||
|  |   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 }; | ||||||
							
								
								
									
										73
									
								
								src/util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/util.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<boolean> => { | ||||||
|  |   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<boolean> => { | ||||||
|  |   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, | ||||||
|  | }; | ||||||
		Reference in New Issue
	
	Block a user