diff --git a/.vscode/settings.json b/.vscode/settings.json index c947c79..1cd8dcf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,9 @@ "deno.enable": true, "deno.unstable": true, "deno.importMap": "import_map.json", - "editor.tabSize": 4, - "editor.detectIndentation": false + "[typescript]": { + "editor.tabSize": 4, + "editor.detectIndentation": false, + "editor.defaultFormatter": "denoland.vscode-deno" + } } diff --git a/config.template.json b/config.template.json index 36c1a7e..e70b090 100644 --- a/config.template.json +++ b/config.template.json @@ -11,6 +11,11 @@ "database": "DB_HERE" } }, + "websocket": { + "enabled": true, + "debug": false, + "port": 3012 + }, "discord": { "channels": [ "CHANNEL_ID_HERE" diff --git a/src/ai/BaseAI.d.ts b/src/ai/BaseAI.d.ts index 995d147..5c73d78 100644 --- a/src/ai/BaseAI.d.ts +++ b/src/ai/BaseAI.d.ts @@ -1,5 +1,5 @@ import { configType } from "../config.ts"; -import { Channel } from "../database.ts" +import { Channel } from "../database.ts"; export default class BaseAI { name: string; diff --git a/src/ai/huggingface.ts b/src/ai/huggingface.ts index 9acc09d..930b8ef 100644 --- a/src/ai/huggingface.ts +++ b/src/ai/huggingface.ts @@ -1,5 +1,5 @@ import { configType } from "../config.ts"; -import { Channel } from "../database.ts" +import { Channel } from "../database.ts"; class HuggingFaceAI { name: string; @@ -48,18 +48,18 @@ class HuggingFaceAI { }, method: "POST", }); - this.tokenNum++ + this.tokenNum++; if (this.tokenNum > this.tokens.length - 1) { - this.tokenNum = 0 + this.tokenNum = 0; } const jsonRes = await res.json(); if (!jsonRes[0].generated_text) { - console.log(jsonRes) - console.warn("Retrying generation with new token...") - return await this.#query(prompt) // bound to go well + console.log(jsonRes); + console.warn("Retrying generation with new token..."); + return await this.#query(prompt); // bound to go well } - return jsonRes + return jsonRes; } reset() { diff --git a/src/ai/index.ts b/src/ai/index.ts index a89989a..7533a46 100644 --- a/src/ai/index.ts +++ b/src/ai/index.ts @@ -1,10 +1,8 @@ import { Bot, Interaction } from "@wackford/discordeno.ts"; import { sendInteractionResponse } from "@wackford/mod.ts"; -import { GoofyAhhException } from "../bot.ts"; import config from "../config.ts"; -import { db, Channel } from "../database.ts"; +import { Channel, db } from "../database.ts"; import BaseAI from "./BaseAI.d.ts"; -import HuggingFaceAI from "./huggingface.ts"; const dirname = new URL(".", import.meta.url).pathname; @@ -26,14 +24,14 @@ export default function initAI() { moduleName: "huggingface", name: config.general.name, description: config.general.desc, - history: [] - }) + history: [], + }); channel = (await db.select(channelId))[0] as Channel; } - console.log(channel) - changeAI(channel.moduleName, channelId, channel.name, channel.description) + console.log(channel); + changeAI(channel.moduleName, channelId, channel.name, channel.description); }); } diff --git a/src/bot.ts b/src/bot.ts index df517b7..fa86baf 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,10 +1,11 @@ import { BotEmitter, initCommands } from "@wackford/mod.ts"; -import { Intents, sendMessage } from "@wackford/discordeno.ts"; +import { Intents } from "@wackford/discordeno.ts"; import initLocalCommands from "./commands/index.ts"; import initOnMessage from "./events/onMessage.ts"; import initAIs from "./ai/index.ts"; import initDB from "./database.ts"; import config from "./config.ts"; +import initSocket from "./socket.ts"; export class GoofyAhhException extends Error { constructor(message: string) { @@ -14,6 +15,7 @@ export class GoofyAhhException extends Error { } await initDB(); +if (config.websocket.enabled) initSocket(config.websocket.port); initOnMessage(); initLocalCommands(); initCommands(); diff --git a/src/commands/change.ts b/src/commands/change.ts index 36105c6..e863f94 100644 --- a/src/commands/change.ts +++ b/src/commands/change.ts @@ -30,12 +30,12 @@ export default { name: newName, description: newDesc, }); - + db.change(channelId, { - name: newName, - description: newDesc, - history: [] - }) + name: newName, + description: newDesc, + history: [], + }); await sendInteractionResponse(bot, interaction, { content: `Name: ${newName ?? "[unchanged]"}\nDescription: ${newDesc ?? "[unchanged]"}`, diff --git a/src/commands/changeAI.ts b/src/commands/changeAI.ts index cc430d6..c624f62 100644 --- a/src/commands/changeAI.ts +++ b/src/commands/changeAI.ts @@ -30,8 +30,8 @@ export default { if (name) { await changeAI(name, interaction.channelId?.toString() as string); db.change(interaction?.channelId?.toString() as string, { - history: [] - }) + history: [], + }); } await sendInteractionResponse(bot, interaction, { content: `Changed AI to ${name}!`, diff --git a/src/commands/debug.ts b/src/commands/debug.ts index f75a2ba..532c989 100644 --- a/src/commands/debug.ts +++ b/src/commands/debug.ts @@ -10,8 +10,10 @@ export default { if (!checkAI(bot, interaction, AI)) return; await sendInteractionResponse(bot, interaction, { - content: `Name: ${AI.name}\nDescription: ${AI.description}\nAI: ${AI.constructor.name}\nHistory: ${JSON.stringify(AI.memory ?? AI.history ?? "[unused]")}`, - private: true + content: `Name: ${AI.name}\nDescription: ${AI.description}\nAI: ${AI.constructor.name}\nHistory: ${ + JSON.stringify(AI.memory ?? AI.history ?? "[unused]") + }`, + private: true, }); }, } as SlashCommandOptions; diff --git a/src/commands/disclaimer.ts b/src/commands/disclaimer.ts index f5b933c..9d66fe1 100644 --- a/src/commands/disclaimer.ts +++ b/src/commands/disclaimer.ts @@ -5,7 +5,8 @@ export default { description: "proves that we are not at fault when the bot says something stupid again", execute: async (bot, interaction) => { await sendInteractionResponse(bot, interaction, { - content: `DISCLAIMER: We (Wackyware) do not take any responsibility for the things said by this bot, if they offend you, get the administrator of your server to remove the message. + content: + `DISCLAIMER: We (Wackyware) do not take any responsibility for the things said by this bot, if they offend you, get the administrator of your server to remove the message. For more details see https://discord.gg/Pxt9dVDSRn and `, }); }, diff --git a/src/commands/index.ts b/src/commands/index.ts index a9be361..5a3baaf 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,6 +1,6 @@ import { createSlashCommand, sendInteractionResponse } from "@wackford/mod.ts"; -const dirname = new URL(".", import.meta.url).pathname; +const dirname = new URL(".", import.meta.url); const helpMessage: string[] = [ "/help - helps your idiot ass", // we do this manually because fuck you too diff --git a/src/commands/reset.ts b/src/commands/reset.ts index 79c8464..9057358 100644 --- a/src/commands/reset.ts +++ b/src/commands/reset.ts @@ -14,8 +14,8 @@ export default { AI.reset(); db.change(channelId, { - history: [] - }) + history: [], + }); await sendInteractionResponse(bot, interaction, { content: `Done!`, diff --git a/src/database.ts b/src/database.ts index 0d9913d..088e5e9 100644 --- a/src/database.ts +++ b/src/database.ts @@ -9,8 +9,8 @@ export interface Channel { name: string; description: string; history: { - name: string, - content: string + name: string; + content: string; }[]; } @@ -18,7 +18,7 @@ export default async function init() { try { await db.signin({ user: config.general.db.user, - pass: config.general.db.pass + pass: config.general.db.pass, }); await db.use(config.general.db.namespace, config.general.db.database); diff --git a/src/events/onMessage.ts b/src/events/onMessage.ts index e253d02..1d5dcbb 100644 --- a/src/events/onMessage.ts +++ b/src/events/onMessage.ts @@ -1,8 +1,9 @@ -import { db, Channel } from "../database.ts"; +import { Channel, db } from "../database.ts"; import { BotEmitter } from "@wackford/mod.ts"; import { AIs } from "../ai/index.ts"; import config from "../config.ts"; import blacklist from "../../blacklist.json" assert { type: "json" }; +import { sendSocketMessage } from "../socket.ts"; //const dirname = new URL(".", import.meta.url).pathname; @@ -15,30 +16,37 @@ export default function init() { !message.isFromBot && message.member ) { - const channel = (await db.select(message.channelId.toString()))[0] as Channel + const channel = (await db.select(message.channelId.toString()))[0] as Channel; const user = await bot.helpers.getUser(message.member?.id); - + sendSocketMessage(user.username, message.content, message.channelId.toString()); await bot.helpers.startTyping(message.channelId); - let res = await AIs[message.channelId.toString()].complete(user.username, message.content) + let res = await AIs[message.channelId.toString()].complete(user.username, message.content); - const history = channel.history ?? [] + const history = channel.history ?? []; history.push({ name: user.username, - content: message.content + content: message.content, }, { name: AIs[message.channelId.toString()].name, - content: res - }) + content: res, + }); db.change(message.channelId.toString(), { - history - }) + history, + }); + + sendSocketMessage( + AIs[message.channelId.toString()].name, + res, + message.channelId.toString(), + AIs[message.channelId.toString()].description, + ); blacklist.forEach((val: string) => { - res = res.replaceAll(new RegExp(val, "gmi"), `${val[0]}${"\\*".repeat(val.length - 1)}`) - }) + res = res.replaceAll(new RegExp(val, "gmi"), `${val[0]}${"\\*".repeat(val.length - 1)}`); + }); await bot.helpers.sendMessage(message.channelId, { content: `[${AIs[message.channelId.toString()].name}] ${res}`, diff --git a/src/socket.ts b/src/socket.ts new file mode 100644 index 0000000..0001962 --- /dev/null +++ b/src/socket.ts @@ -0,0 +1,84 @@ +import { serve } from "https://deno.land/std@0.160.0/http/server.ts"; +import config from "./config.ts"; + +interface SockClient { + ws: WebSocket; + pinger: number; +} + +// TODO: Read this from config +const debugMode = config.websocket.debug; +export const connected: Record = {}; + +function debug(msg: string) { + if (debugMode) console.log(`debug: ${msg}`); +} + +function handleConnected(_event: Event, ws: WebSocket) { + debug("Client connected!"); + + const pinger = setInterval(() => { + // i'm not sorry for this + debug(`Pinging client ${Object.keys(connected).length - 1}...`); + ws.send("__PING__"); + }, 10000); + + connected[Object.keys(connected).length] = { + ws: ws, + pinger: pinger, + }; +} + +function handleDisconnected(_event: CloseEvent, ws: WebSocket) { + Object.keys(connected).forEach((val) => { + if (connected[val].ws == ws) { + debug(`Client ${val} disconnected!`); + clearInterval(connected[val].pinger); + delete connected[val]; + } + }); +} + +function handleMessage(event: MessageEvent) { + if (event.data == "__PONG__") { + debug("Recieved pong!"); + } +} + +function reqHandler(req: Request) { + if (req.headers.get("upgrade") != "websocket") { + return new Response(null, { status: 501 }); + } + + const { socket: ws, response } = Deno.upgradeWebSocket(req); + + ws.onopen = (event) => handleConnected(event, ws); + ws.onclose = (event) => handleDisconnected(event, ws); + ws.onmessage = (event) => handleMessage(event); + + return response; +} + +export default function initialize(port: number) { + serve(reqHandler, { port: port }); +} + +export function sendSocketMessage( + name: string, + message: string, + channel: string, + description = "", +) { + Object.keys(connected).forEach((val) => { + if (connected[val]) { + connected[val].ws.send( + JSON.stringify({ + name, + description, + message, + channel, + }), + ); + } + }); +}