Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Channel/Thread Chat Toggle #75

Merged
merged 8 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ MODEL = MODEL_NAME
# discord bot user id for mentions
CLIENT_UID = BOT_USER_ID

# ip/port address of docker container, I use 172.18.X.X for docker, 127.0.0.1 for local
# ip/port address of docker container, I use 172.18.0.3 for docker, 127.0.0.1 for local
OLLAMA_IP = IP_ADDRESS
OLLAMA_PORT = PORT

# ip address for discord bot container, I use 172.18.X.X, use different IP than ollama_ip
# ip address for discord bot container, I use 172.18.0.2, use different IP than ollama_ip
DISCORD_IP = IP_ADDRESS

# subnet address, ex. 172.18.0.0 as we use /16.
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
name: Builds
run-name: Validate Node and Docker Builds
on:
push:
pull_request:
branches:
- master
paths:
- '/'
- '!docs/**'
- '!imgs/**'
- '!.github/**'
- '.github/workflows/**'
- '!.gitignore'
- '!LICENSE'
- '!README'

jobs:
Discord-Node-Build: # test if the node install and run
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
paths:
- '/'
- '!docs/**'
- '!imgs/**'
- '!.github/**'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ Ollama is an AI model management tool that allows users to install and use custo
The project aims to:
* [x] Create a Discord bot that will utilize Ollama and chat to chat with users!
* [ ] User Preferences on Chat
* [ ] Message Persistance on Channels and Threads
* [x] Message Persistance on Channels and Threads
* [x] Threads
* [ ] Channels
* [x] Channels
* [x] Containerization with Docker
* [x] Slash Commands Compatible
* [x] Generated Token Length Handling for >2000
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
build: ./ # find docker file in designated path
container_name: discord
restart: always # rebuild container always
image: discord/bot:0.5.1
image: discord/bot:0.5.2
environment:
CLIENT_TOKEN: ${CLIENT_TOKEN}
GUILD_ID: ${GUILD_ID}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "discord-ollama",
"version": "0.5.1",
"version": "0.5.2",
"description": "Ollama Integration into discord",
"main": "build/index.js",
"exports": "./build/index.js",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/capacity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const Capacity: SlashCommand = {
run: async (client: Client, interaction: CommandInteraction) => {
// fetch channel and message
const channel = await client.channels.fetch(interaction.channelId)
if (!channel || channel.type !== ChannelType.PublicThread) return
if (!channel || channel.type !== (ChannelType.PublicThread && ChannelType.GuildText)) return

// set state of bot chat features
openConfig('config.json', interaction.commandName, interaction.options.get('context-capacity')?.value)
Expand Down
34 changes: 34 additions & 0 deletions src/commands/channelToggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ApplicationCommandOptionType, ChannelType, Client, CommandInteraction } from 'discord.js'
import { SlashCommand } from '../utils/commands.js'
import { openConfig } from '../utils/jsonHandler.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.PublicThread && ChannelType.GuildText)) return


// set state of bot channel preferences
openConfig('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
})
}
}
4 changes: 3 additions & 1 deletion src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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'

export default [
ThreadCreate,
Expand All @@ -14,5 +15,6 @@ export default [
MessageStream,
Disable,
Shutoff,
Capacity
Capacity,
ChannelToggle
] as SlashCommand[]
2 changes: 1 addition & 1 deletion src/commands/messageStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const MessageStream: SlashCommand = {
run: async (client: Client, interaction: CommandInteraction) => {
// verify channel
const channel = await client.channels.fetch(interaction.channelId)
if (!channel || channel.type !== ChannelType.PublicThread) return
if (!channel || channel.type !== (ChannelType.PublicThread && ChannelType.GuildText)) return

// save value to json and write to it
openConfig('config.json', interaction.commandName, interaction.options.get('stream')?.value)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/messageStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const MessageStyle: SlashCommand = {
run: async (client: Client, interaction: CommandInteraction) => {
// fetch channel and message
const channel = await client.channels.fetch(interaction.channelId)
if (!channel || channel.type !== ChannelType.PublicThread) return
if (!channel || channel.type !== (ChannelType.PublicThread && ChannelType.GuildText)) return

// set the message style
openConfig('config.json', interaction.commandName, interaction.options.get('embed')?.value)
Expand Down
61 changes: 43 additions & 18 deletions src/events/messageCreate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { embedMessage, event, Events, normalMessage, UserMessage } from '../utils/index.js'
import { Configuration, getConfig, getThread, openConfig, openThreadInfo } from '../utils/jsonHandler.js'
import { Configuration, getChannelInfo, getConfig, getThread, openChannelInfo, openConfig, openThreadInfo } from '../utils/jsonHandler.js'
import { clean } from '../utils/mentionClean.js'
import { ThreadChannel } from 'discord.js'
import { TextChannel, ThreadChannel } from 'discord.js'

/**
* Max Message length for free users is 2000 characters (bot or not).
Expand All @@ -10,17 +10,6 @@ import { ThreadChannel } from 'discord.js'
export default event(Events.MessageCreate, async ({ log, msgHist, tokens, ollama, client }, message) => {
log(`Message \"${clean(message.content)}\" from ${message.author.tag} in channel/thread ${message.channelId}.`)

// need new check for "open/active" threads here!
const threadMessages: UserMessage[] = await new Promise((resolve) => {
// set new queue to modify
getThread(`${message.channelId}.json`, (threadInfo) => {
if (threadInfo?.messages)
resolve(threadInfo.messages)
else
log(`Channel/Thread ${message.channelId} does not exist.`)
})
})

// Do not respond if bot talks in the chat
if (message.author.tag === message.client.user.tag) return

Expand All @@ -45,6 +34,14 @@ 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.tag
)
}

// check if there is a set capacity in config
if (typeof config.options['modify-capacity'] !== 'number')
log(`Capacity is undefined, using default capacity of ${msgHist.capacity}.`)
Expand All @@ -62,11 +59,31 @@ 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) => {
// set new queue to modify
if (config.options['channel-toggle']) {
getChannelInfo(`${message.channelId}-${message.author.tag}.json`, (channelInfo) => {
if (channelInfo?.messages)
resolve(channelInfo.messages)
else
log(`Channel ${message.channel}-${message.author.tag} does not exist.`)
})
} 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

// set up new queue
msgHist.setQueue(threadMessages)
msgHist.setQueue(chatMessages)

// check if we can push, if not, remove oldest
while (msgHist.size() >= msgHist.capacity) msgHist.dequeue()
Expand Down Expand Up @@ -96,10 +113,18 @@ export default event(Events.MessageCreate, async ({ log, msgHist, tokens, ollama
})

// only update the json on success
openThreadInfo(`${message.channelId}.json`,
client.channels.fetch(message.channelId) as unknown as ThreadChannel,
msgHist.getItems()
)
if (config.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()
)
}
} catch (error: any) {
msgHist.pop() // remove message because of failure
openConfig('config.json', 'message-style', false)
Expand Down
84 changes: 81 additions & 3 deletions src/utils/jsonHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ThreadChannel } from 'discord.js'
import { TextChannel, ThreadChannel } from 'discord.js'
import { UserMessage } from './events.js'
import fs from 'fs'
import path from 'path'
Expand All @@ -9,7 +9,8 @@ export interface Configuration {
'message-stream'?: boolean,
'message-style'?: boolean,
'toggle-chat'?: boolean,
'modify-capacity'?: number
'modify-capacity'?: number,
'channel-toggle'?: boolean
}
}

Expand All @@ -19,6 +20,13 @@ export interface Thread {
messages: UserMessage[]
}

export interface Channel {
readonly id: string
readonly name: string
readonly user: string
messages: UserMessage[]
}

/**
* Method to open a file in the working directory and modify/create it
*
Expand Down Expand Up @@ -85,7 +93,7 @@ export function openThreadInfo(filename: string, thread: ThreadChannel, messages
if (fs.existsSync(fullFileName)) {
fs.readFile(fullFileName, 'utf8', (error, data) => {
if (error)
console.log(`[Error: openConfig] Incorrect file format`)
console.log(`[Error: openThreadInfo] Incorrect file format`)
else {
const object = JSON.parse(data)
object['messages'] = messages as []
Expand Down Expand Up @@ -125,4 +133,74 @@ export async function getThread(filename: string, callback: (config: Thread | un
} else {
callback(undefined) // file not found
}
}

/**
* Method to open the channel history
*
* @param filename name of the json file for the channel by user
* @param channel the text channel info
* @param user the user's name
* @param messages their messages
*/
export async function openChannelInfo(filename: string, channel: TextChannel, user: string, messages: UserMessage[] = []): Promise<void> {
// thread exist handler
const isThread: boolean = await new Promise((resolve) => {
getThread(`${channel.id}.json`, (threadInfo) => {
if (threadInfo?.messages)
resolve(true)
else
resolve(false)
})
})

// This is an existing thread, don't create another json
if (isThread) return

const fullFileName = `data/${filename}-${user}.json`
if (fs.existsSync(fullFileName)) {
fs.readFile(fullFileName, 'utf8', (error, data) => {
if (error)
console.log(`[Error: openChannelInfo] Incorrect file format`)
else {
const object = JSON.parse(data)
if (object['messages'].length === 0)
object['messages'] = messages as []
else if (object['messages'].length !== 0 && messages.length !== 0)
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\": \"${channel?.id}\", \"name\": \"${channel?.name}\", \"user\": \"${user}\", \"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: openChannelInfo] Created '${fullFileName}' in working directory`)
}
}

/**
* Method to get the channel information/history
*
* @param filename name of the json file for the channel by user
* @param callback function to handle resolving message history
*/
export async function getChannelInfo(filename: string, callback: (config: Channel | undefined) => void): Promise<void> {
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
}
}
2 changes: 1 addition & 1 deletion tests/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
expect(commandsString).toBe('thread, private-thread, message-style, message-stream, toggle-chat, shutoff, modify-capacity, channel-toggle')
})
})