diff --git a/discordBot/addons/gptInteraction.mjs b/discordBot/addons/gptInteraction.mjs new file mode 100644 index 0000000..f145703 --- /dev/null +++ b/discordBot/addons/gptInteraction.mjs @@ -0,0 +1,53 @@ +import { DebugBuilder } from "../../modules/debugger.mjs"; +const log = new DebugBuilder("server", "discordBot.addons.gptInteraction"); +import { gptHandler } from "../modules/gptHandler.mjs"; + +export const gptInteraction = async (nodeIo, message) => { + let conversation = []; + + let prevMessages = await message.channel.messages.fetch({ limit: 10 }); + prevMessages.reverse(); + + prevMessages.forEach((msg) => { + // Check if the message was sent within the last 24 hours + if (new Date().getTime() - msg.createdTimestamp > (24 * 60 * 60 * 1000)) { + return; + } + + // Check if it's from a bot other than the server + if (msg.author.bot && msg.author.id !== nodeIo.serverClient.user.id) return; + + const username = msg.author.username.replace(/\s+/g, '_').replace(/[^\w\s]/gi, ''); + + if (msg.author.id === nodeIo.serverClient.user.id) { + conversation.push({ + role: 'assistant', + name: msg.author.id, + content: msg.content, + }); + + return; + } + + conversation.push({ + role: 'user', + name: msg.author.id, + content: msg.content.replace(`<@${nodeIo.serverClient.user.id}>`, ''), + }); + }); + const response = await gptHandler(conversation); + if (response) { + const responseMessage = response.choices[0].message.content; + const chunkSize = 2500; + + for (let i = 0; i < responseMessage.length; i += chunkSize) { + const chunk = responseMessage.substring(i, i + chunkSize); + + log.DEBUG("Sending message chunk:", chunk); + + await message.reply(chunk); + } + } else { + message.channel.send('Sorry, I encountered an error while processing your request.'); + } +} \ No newline at end of file diff --git a/discordBot/addons/linkCop.mjs b/discordBot/addons/linkCop.mjs new file mode 100644 index 0000000..aae558e --- /dev/null +++ b/discordBot/addons/linkCop.mjs @@ -0,0 +1,83 @@ +import { DebugBuilder } from "../../modules/debugger.mjs"; +const log = new DebugBuilder("server", "discordBot.addons.linkCop"); +import { gptHandler } from "../modules/gptHandler.mjs"; +import dotenv from 'dotenv'; +dotenv.config(); + +const approvedLinksChannel = "767303243285790721"; +const restrictedChannelIds = process.env.LINKCOP_RESTRICTED_CHANNELS.split(','); + +const linkRegExp = /(?:http[s]?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g + +export const linkCop = async (nodeIo, message) => { + if (message.channel.id == approvedLinksChannel || !restrictedChannelIds.includes(message.channel.id)) return false; + + const urls = String(message.content).matchAll(linkRegExp); + if (!urls || urls.length === 0) return false; + log.DEBUG("Found URLs: ", urls); + + let conversation = []; + + let prevMessages = await message.channel.messages.fetch({ limit: 2 }); + prevMessages.reverse(); + + prevMessages.forEach((msg) => { + // Check if the message was sent within the last 5 minutes + if (new Date().getTime() - msg.createdTimestamp > (5 * 60 * 1000)) { + return; + } + + // Check if it's from a bot other than the server + if (msg.author.bot && msg.author.id !== nodeIo.serverClient.user.id) return; + + const username = msg.author.username.replace(/\s+/g, '_').replace(/[^\w\s]/gi, ''); + + if (msg.author.id === nodeIo.serverClient.user.id) { + conversation.push({ + role: 'assistant', + name: msg.author.id, + content: msg.content, + }); + + return; + } + + conversation.push({ + role: 'user', + name: msg.author.id, + content: msg.content.replace(`<@${nodeIo.serverClient.user.id}>`, ''), + }); + }); + + conversation.push({ + role: 'system', + content: `There has been a link posted to a channel that links are not allowed in. The above messages are from the channel that links are not allowed including the message with the link. The message with the link is going to be deleted and moved to the '#links' channels. You should let the user know.` + }); + + const response = await gptHandler(conversation); + + if (response) { + const responseMessage = response.choices[0].message.content; + const chunkSize = 2000; + + for (let i = 0; i < responseMessage.length; i += chunkSize) { + const chunk = responseMessage.substring(i, i + chunkSize); + + log.DEBUG("Sending message chunk:", chunk); + + await message.reply(chunk); + } + + const messageContent = { + 'author': message.author, + 'content': `<@${message.author.id}> - ${String(message.content)}`, + 'channelId': message.channelId, + 'links': urls + } + + await message.delete(); + log.DEBUG("Message content: ", messageContent); + + message.client.channels.cache.get(approvedLinksChannel).send(messageContent); + } +} \ No newline at end of file diff --git a/discordBot/discordBot.mjs b/discordBot/discordBot.mjs index aea3d4e..6de8500 100644 --- a/discordBot/discordBot.mjs +++ b/discordBot/discordBot.mjs @@ -79,7 +79,7 @@ export function addEnabledEventListeners(serverClient, _eventsPath = "./events") } // The discord client -export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] }); +export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] }); // Run when the bot is ready serverClient.on('ready', async () => { diff --git a/discordBot/events/messageCreate.mjs b/discordBot/events/messageCreate.mjs new file mode 100644 index 0000000..f829b72 --- /dev/null +++ b/discordBot/events/messageCreate.mjs @@ -0,0 +1,29 @@ +import { DebugBuilder } from "../../modules/debugger.mjs"; +const log = new DebugBuilder("server", "discordBot.events.messageCreate"); +import dotenv from 'dotenv'; +dotenv.config(); +import { Events } from 'discord.js'; +import { gptInteraction } from '../addons/gptInteraction.mjs'; +import { linkCop } from '../addons/linkCop.mjs'; + +const IGNORED_CHANNELS = process.env.IGNORED_CHANNELS.split(','); + +export const name = Events.MessageCreate; + +export async function execute(nodeIo, message) { + // Ignore ignored channels + if (IGNORED_CHANNELS.includes(message.channel.id)) return; + + // Ignore messages from a bot + if (message.author.bot) return; + + log.INFO("Message create", message); + + // Check if the message mentions the bot + if (message.mentions.users.has(nodeIo.serverClient.user.id)) { + return await gptInteraction(nodeIo, message); + } + + // Check if the message contains a link in a channel it shouldn't + if (await linkCop(nodeIo, message)) return; +} \ No newline at end of file diff --git a/discordBot/modules/gptHandler.mjs b/discordBot/modules/gptHandler.mjs new file mode 100644 index 0000000..ad2bf09 --- /dev/null +++ b/discordBot/modules/gptHandler.mjs @@ -0,0 +1,39 @@ +import { DebugBuilder } from "../../modules/debugger.mjs"; +const log = new DebugBuilder("server", "discordBot.modules.gptHandler"); +import dotenv from 'dotenv'; +dotenv.config(); + +import { OpenAI } from 'openai'; + +const openai = new OpenAI(process.env.OPENAI_API_KEY); + +let conversation = []; + +conversation.push({ + role: 'system', + content: process.env.DRB_SERVER_INITIAL_PROMPT +}); + +export const gptHandler = async (additionalMessages) => { + // Add the additional messages to the conversation + conversation = conversation.concat(additionalMessages); + log.DEBUG("AI Conversation:", conversation); + try { + const response = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo', + messages: conversation, + }).catch((error) => log.ERROR("OpenAI Error: ", error)); + + log.DEBUG("AI Response:", response); + + if (!response) { + return false; + } + + return response + + } catch (error) { + console.error('Error generating response:', error); + return false; + } +} \ No newline at end of file