From 36a0cd309b1c3c1c50eb81527cc12da6030e3cee Mon Sep 17 00:00:00 2001 From: Kevin Dang <77701718+kevinthedang@users.noreply.github.com> Date: Sat, 14 Sep 2024 13:34:40 -0700 Subject: [PATCH] Removed Channel Toggle Command (#115) * Remove: channel-toggle as command and server config * Remove: Thread interface * Fix: Users Thread files will now delete * Fix: Any user can chat in threads now * Fix: Thread history files are now deleted with multiple users * Update: version increment --- docker-compose.yml | 2 +- package-lock.json | 4 +- package.json | 2 +- src/commands/channelToggle.ts | 33 ---------- src/commands/index.ts | 2 - src/commands/threadCreate.ts | 11 ++-- src/commands/threadPrivateCreate.ts | 9 ++- src/events/messageCreate.ts | 74 ++++++++++------------ src/events/threadDelete.ts | 42 +++++++++---- src/utils/configInterfaces.ts | 9 +-- src/utils/handlers/chatHistoryHandler.ts | 78 ++---------------------- tests/commands.test.ts | 2 +- 12 files changed, 86 insertions(+), 182 deletions(-) delete mode 100644 src/commands/channelToggle.ts diff --git a/docker-compose.yml b/docker-compose.yml index 3b1b989..f404cd6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: build: ./ # find docker file in designated path container_name: discord restart: always # rebuild container always - image: kevinthedang/discord-ollama:0.5.10 + image: kevinthedang/discord-ollama:0.5.11 environment: CLIENT_TOKEN: ${CLIENT_TOKEN} MODEL: ${MODEL} diff --git a/package-lock.json b/package-lock.json index 28275ea..aa8707f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "discord-ollama", - "version": "0.5.10", + "version": "0.5.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "discord-ollama", - "version": "0.5.10", + "version": "0.5.11", "license": "ISC", "dependencies": { "discord.js": "^14.15.3", diff --git a/package.json b/package.json index 07c7705..f31766f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-ollama", - "version": "0.5.10", + "version": "0.5.11", "description": "Ollama Integration into discord", "main": "build/index.js", "exports": "./build/index.js", diff --git a/src/commands/channelToggle.ts b/src/commands/channelToggle.ts deleted file mode 100644 index bd50f7e..0000000 --- a/src/commands/channelToggle.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ApplicationCommandOptionType, ChannelType, Client, CommandInteraction } from 'discord.js' -import { SlashCommand } from '../utils/commands.js' -import { openConfig } from '../utils/index.js' - -export const ChannelToggle: SlashCommand = { - name: 'channel-toggle', - description: 'toggles channel or thread usage.', - - // set user option for toggling - options: [ - { - name: 'toggle-channel', - description: 'toggle channel usage, otherwise threads', - type: ApplicationCommandOptionType.Boolean, - required: true - } - ], - - // Query for chatting preference - run: async (client: Client, interaction: CommandInteraction) => { - // fetch channel location - const channel = await client.channels.fetch(interaction.channelId) - if (!channel || channel.type !== (ChannelType.PrivateThread && ChannelType.PublicThread && ChannelType.GuildText)) return - - // set state of bot channel preferences - openConfig(`${interaction.guildId}-config.json`, interaction.commandName, interaction.options.get('toggle-channel')?.value) - - interaction.reply({ - content: `Channel Preferences have for Regular Channels set to \`${interaction.options.get('toggle-channel')?.value}\``, - ephemeral: true - }) - } -} diff --git a/src/commands/index.ts b/src/commands/index.ts index aad3865..e93a108 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -6,7 +6,6 @@ import { Disable } from './disable.js' import { Shutoff } from './shutoff.js' import { Capacity } from './capacity.js' import { PrivateThreadCreate } from './threadPrivateCreate.js' -import { ChannelToggle } from './channelToggle.js' import { ClearUserChannelHistory } from './cleanUserChannelHistory.js' export default [ @@ -17,6 +16,5 @@ export default [ Disable, Shutoff, Capacity, - ChannelToggle, ClearUserChannelHistory ] as SlashCommand[] \ No newline at end of file diff --git a/src/commands/threadCreate.ts b/src/commands/threadCreate.ts index 97b4a0c..5b676ba 100644 --- a/src/commands/threadCreate.ts +++ b/src/commands/threadCreate.ts @@ -1,6 +1,6 @@ -import { ChannelType, Client, CommandInteraction, TextChannel } from 'discord.js' +import { ChannelType, Client, CommandInteraction, TextChannel, ThreadChannel } from 'discord.js' import { SlashCommand } from '../utils/commands.js' -import { openThreadInfo } from '../utils/index.js' +import { openChannelInfo } from '../utils/index.js' export const ThreadCreate: SlashCommand = { name: 'thread', @@ -19,11 +19,12 @@ export const ThreadCreate: SlashCommand = { }) // Send a message in the thread - thread.send(`Hello ${interaction.user} and others! \n\nIt's nice to meet you. Please talk to me by typing **@${client.user?.username}** with your prompt.\n\nIf I do not respond, ensure \`channel-toggle\` is set to \`false\``) + thread.send(`Hello ${interaction.user} and others! \n\nIt's nice to meet you. Please talk to me by typing **@${client.user?.username}** with your prompt.`) // handle storing this chat channel - // store: thread.id, thread.name - openThreadInfo(`${thread.id}.json`, thread) + openChannelInfo(thread.id, + thread as ThreadChannel, + interaction.user.tag) // user only reply return interaction.reply({ diff --git a/src/commands/threadPrivateCreate.ts b/src/commands/threadPrivateCreate.ts index 49bda35..cc941a2 100644 --- a/src/commands/threadPrivateCreate.ts +++ b/src/commands/threadPrivateCreate.ts @@ -1,6 +1,6 @@ -import { ChannelType, Client, CommandInteraction, TextChannel } from 'discord.js' +import { ChannelType, Client, CommandInteraction, TextChannel, ThreadChannel } from 'discord.js' import { SlashCommand } from '../utils/commands.js' -import { openThreadInfo } from '../utils/index.js' +import { openChannelInfo } from '../utils/index.js' export const PrivateThreadCreate: SlashCommand = { name: 'private-thread', @@ -23,7 +23,10 @@ export const PrivateThreadCreate: SlashCommand = { // handle storing this chat channel // store: thread.id, thread.name - openThreadInfo(`${thread.id}.json`, thread) + openChannelInfo(thread.id, + thread as ThreadChannel, + interaction.user.tag + ) // user only reply return interaction.reply({ diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 812e74b..00d3ff2 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -1,7 +1,7 @@ import { embedMessage, event, Events, normalMessage, UserMessage } from '../utils/index.js' -import { getChannelInfo, getServerConfig, getThread, getUserConfig, openChannelInfo, openConfig, openThreadInfo, ServerConfig, UserConfig } from '../utils/index.js' +import { getChannelInfo, getServerConfig, getUserConfig, openChannelInfo, openConfig, ServerConfig, UserConfig } from '../utils/index.js' import { clean } from '../utils/mentionClean.js' -import { TextChannel, ThreadChannel } from 'discord.js' +import { TextChannel } from 'discord.js' /** * Max Message length for free users is 2000 characters (bot or not). @@ -9,7 +9,7 @@ import { TextChannel, ThreadChannel } from 'discord.js' * * @param message the message received from the channel */ -export default event(Events.MessageCreate, async ({ log, msgHist, tokens, ollama, client }, message) => { +export default event(Events.MessageCreate, async ({ log, msgHist, tokens, ollama }, message) => { log(`Message \"${clean(message.content)}\" from ${message.author.tag} in channel/thread ${message.channelId}.`) // Do not respond if bot talks in the chat @@ -23,15 +23,12 @@ export default event(Events.MessageCreate, async ({ log, msgHist, tokens, ollama try { // Retrieve Server/Guild Preferences - const serverConfig: ServerConfig = await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { getServerConfig(`${message.guildId}-config.json`, (config) => { // check if config.json exists if (config === undefined) { // Allowing chat options to be available openConfig(`${message.guildId}-config.json`, 'toggle-chat', true) - - // default to channel scope chats - openConfig(`${message.guildId}-config.json`, 'channel-toggle', true) reject(new Error('No Server Preferences is set up.\n\nCreating default server preferences file...\nPlease try chatting again.')) return } @@ -42,14 +39,6 @@ export default event(Events.MessageCreate, async ({ log, msgHist, tokens, ollama return } - // ensure channel json exists, if not create it - if (config.options['channel-toggle']) { - openChannelInfo(message.channelId, - message.channel as TextChannel, - message.author.username - ) - } - resolve(config) }) }) @@ -80,27 +69,35 @@ export default event(Events.MessageCreate, async ({ log, msgHist, tokens, ollama }) }) - - // need new check for "open/active" threads/channels here! - const chatMessages: UserMessage[] = await new Promise((resolve) => { + let chatMessages: UserMessage[] = await new Promise((resolve) => { // set new queue to modify - if (serverConfig.options['channel-toggle']) { + getChannelInfo(`${message.channelId}-${message.author.username}.json`, (channelInfo) => { + if (channelInfo?.messages) + resolve(channelInfo.messages) + else { + log(`Channel/Thread ${message.channel}-${message.author.username} does not exist. File will be created shortly...`) + resolve([]) + } + }) + }) + + if (chatMessages.length === 0) { + chatMessages = await new Promise((resolve, reject) => { + openChannelInfo(message.channelId, + message.channel as TextChannel, + message.author.tag + ) getChannelInfo(`${message.channelId}-${message.author.username}.json`, (channelInfo) => { if (channelInfo?.messages) resolve(channelInfo.messages) - else - log(`Channel ${message.channel}-${message.author.username} does not exist.`) + else { + log(`Channel/Thread ${message.channel}-${message.author.username} does not exist. File will be created shortly...`) + reject(new Error(`Failed to find ${message.author.username}'s history. Try chatting again.`)) + } }) - } else { - getThread(`${message.channelId}.json`, (threadInfo) => { - if (threadInfo?.messages) - resolve(threadInfo.messages) - else - log(`Thread ${message.channelId} does not exist.`) - }) - } - }) + }) + } // response string for ollama to put its response let response: string @@ -136,18 +133,11 @@ export default event(Events.MessageCreate, async ({ log, msgHist, tokens, ollama }) // only update the json on success - if (serverConfig.options['channel-toggle']) { - openChannelInfo(message.channelId, - message.channel as TextChannel, - message.author.tag, - msgHist.getItems() - ) - } else { - openThreadInfo(`${message.channelId}.json`, - client.channels.fetch(message.channelId) as unknown as ThreadChannel, - msgHist.getItems() - ) - } + openChannelInfo(message.channelId, + message.channel as TextChannel, + message.author.tag, + msgHist.getItems() + ) } catch (error: any) { msgHist.pop() // remove message because of failure message.reply(`**Error Occurred:**\n\n**Reason:** *${error.message}*`) diff --git a/src/events/threadDelete.ts b/src/events/threadDelete.ts index ee9017d..619231d 100644 --- a/src/events/threadDelete.ts +++ b/src/events/threadDelete.ts @@ -5,16 +5,36 @@ import fs from 'fs' /** * Event to remove the associated .json file for a thread once deleted */ -export default event(Events.ThreadDelete, ({ log }, thread: ThreadChannel) => { - const filePath = `data/${thread.id}.json` - if (fs.existsSync(filePath)) { - fs.unlink(filePath, (error) => { - if (error) - log(`Error deleting file ${filePath}`, error) - else - log(`Successfully deleted ${filePath} thread info`) +export default event(Events.ThreadDelete, async ({ log }, thread: ThreadChannel) => { + // iterate through every guild member in the thread and delete their history, except the bot + try { + log(`Number of User Guild Members in Thread being deleted: ${thread.memberCount!! - 1}`) + const dirPath = 'data/' + + // read all files in data/ + fs.readdir(dirPath, (error, files) => { + if (error) { + log(`Error reading directory ${dirPath}`, error) + return + } + + // filter files by thread id being deleted + const filesToDiscard = files.filter( + file => file.startsWith(`${thread.id}-`) && + file.endsWith('.json')) + + // remove files by unlinking + filesToDiscard.forEach(file => { + const filePath = dirPath + file + fs.unlink(filePath, error => { + if (error) + log(`Error deleting file ${filePath}`, error) + else + log(`Successfully deleted ${filePath} thread information`) + }) + }) }) - } else { - log(`File ${filePath} does not exist.`) - } + } catch (error) { + log(`Issue deleting user history files from ${thread.id}`) + } }) \ No newline at end of file diff --git a/src/utils/configInterfaces.ts b/src/utils/configInterfaces.ts index 6ba23eb..ec16ba4 100644 --- a/src/utils/configInterfaces.ts +++ b/src/utils/configInterfaces.ts @@ -8,7 +8,6 @@ export interface UserConfiguration { export interface ServerConfiguration { 'toggle-chat'?: boolean, - 'channel-toggle'?: boolean } /** @@ -35,12 +34,6 @@ export interface ServerConfig { options: ServerConfiguration } -export interface Thread { - readonly id: string - readonly name: string - messages: UserMessage[] -} - export interface Channel { readonly id: string readonly name: string @@ -54,5 +47,5 @@ export interface Channel { * @returns true if command is from Server Config, false otherwise */ export function isServerConfigurationKey(key: string): key is keyof ServerConfiguration { - return ['toggle-chat', 'channel-toggle'].includes(key); + return ['toggle-chat'].includes(key); } \ No newline at end of file diff --git a/src/utils/handlers/chatHistoryHandler.ts b/src/utils/handlers/chatHistoryHandler.ts index d1f52f1..80bbb4e 100644 --- a/src/utils/handlers/chatHistoryHandler.ts +++ b/src/utils/handlers/chatHistoryHandler.ts @@ -1,63 +1,8 @@ import { TextChannel, ThreadChannel } from 'discord.js' -import { Configuration, Thread, Channel, UserMessage } from '../index.js' +import { Configuration, Channel, UserMessage } from '../index.js' import fs from 'fs' import path from 'path' -/** - * Method to open/create and modify a json file containing thread information - * - * @param filename name of the thread file - * @param thread the thread with all of the interactions - * @param message message contents and from who - */ -export function openThreadInfo(filename: string, thread: ThreadChannel, messages: UserMessage[] = []) { - // check if the file exists, if not then make the config file - const fullFileName = `data/${filename}` - if (fs.existsSync(fullFileName)) { - fs.readFile(fullFileName, 'utf8', (error, data) => { - if (error) - console.log(`[Error: openThreadInfo] Incorrect file format`) - else { - const object = JSON.parse(data) - object['messages'] = messages as [] - fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2)) - } - }) - } else { // file doesn't exist, create it - const object: Configuration = JSON.parse(`{ \"id\": \"${thread?.id}\", \"name\": \"${thread?.name}\", \"messages\": []}`) - - const directory = path.dirname(fullFileName) - if (!fs.existsSync(directory)) - fs.mkdirSync(directory, { recursive: true }) - - // only creating it, no need to add anything - fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2)) - console.log(`[Util: openThreadInfo] Created '${fullFileName}' in working directory`) - } -} - -/** - * Method to obtain the configurations of the message chat/thread - * - * @param filename name of the configuration file to get - * @param callback function to allow a promise from getting the config - */ -export async function getThread(filename: string, callback: (config: Thread | undefined) => void): Promise { - // attempt to read the file and get the configuration - const fullFileName = `data/${filename}` - if (fs.existsSync(fullFileName)) { - fs.readFile(fullFileName, 'utf8', (error, data) => { - if (error) { - callback(undefined) - return // something went wrong... stop - } - callback(JSON.parse(data)) - }) - } else { - callback(undefined) // file not found - } -} - /** * Method to check if a thread history file exists * @@ -65,16 +10,15 @@ export async function getThread(filename: string, callback: (config: Thread | un * @returns true if channel does not exist, false otherwise */ async function checkChannelInfoExists(channel: TextChannel, user: string) { - // thread exist handler - const isThread: boolean = await new Promise((resolve) => { - getThread(`${channel.id}-${user}.json`, (channelInfo) => { + const doesExists: boolean = await new Promise((resolve) => { + getChannelInfo(`${channel.id}-${user}.json`, (channelInfo) => { if (channelInfo?.messages) resolve(true) else resolve(false) }) }) - return isThread + return doesExists } /** @@ -121,19 +65,7 @@ export async function clearChannelInfo(filename: string, channel: TextChannel, u * @param user the user's name * @param messages their messages */ -export async function openChannelInfo(filename: string, channel: TextChannel, user: string, messages: UserMessage[] = []): Promise { - const isThread: boolean = await new Promise((resolve) => { - getThread(`${channel.id}.json`, (threadInfo) => { - if (threadInfo?.messages) - resolve(true) - else - resolve(false) - }) - }) - - // this is a thread channel, do not duplicate files - if (isThread) return - +export async function openChannelInfo(filename: string, channel: TextChannel | ThreadChannel, user: string, messages: UserMessage[] = []): Promise { const fullFileName = `data/${filename}-${user}.json` if (fs.existsSync(fullFileName)) { fs.readFile(fullFileName, 'utf8', (error, data) => { diff --git a/tests/commands.test.ts b/tests/commands.test.ts index faae0b1..2167406 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -22,6 +22,6 @@ describe('#commands', () => { // test specific commands in the object it('references specific commands', () => { const commandsString = commands.map(e => e.name).join(', ') - expect(commandsString).toBe('thread, private-thread, message-style, message-stream, toggle-chat, shutoff, modify-capacity, channel-toggle, clear-user-channel-history') + expect(commandsString).toBe('thread, private-thread, message-style, message-stream, toggle-chat, shutoff, modify-capacity, clear-user-channel-history') }) }) \ No newline at end of file