Compare commits

...

7 Commits

Author SHA1 Message Date
Kevin Dang
02ffb6a196 Remove Unnecessary Docker Test Pipeline (#93)
* Remove: container test pipeline

* Update: build pipelines rely on test pipeline

* Fix: typo in build file

* Fix: naming conventions for workflows in yml
2024-07-31 06:20:00 -07:00
Kevin Dang
060494e883 Adjusted Slash Command Scope (#91)
* Update: Slash Command Scope

* Update: version increment
2024-07-31 06:19:23 -07:00
Kevin Dang
352d88ee9d Clear User Channel History Command (#88)
* Add: Clear user channel message history command

* Update: Checks if messages are empty and has clearer replies

* Fix: Issue where duplication happens on channel-toggle true in threads

* Update: version increment

* Fix: Missing test case for commands.test.ts

* Readability fix

---------

Co-authored-by: Jonathan Smoley <67881240+JT2M0L3Y@users.noreply.github.com>
2024-07-25 14:26:50 -07:00
Kevin Dang
e60c2f88b8 Handlers Directory and Universal Import Fix (#86)
* Update: split jsonHandler.ts to different files

* Add: handlers folder and moved some files there

* Update: interface file name
2024-07-23 16:59:54 -07:00
Kevin Dang
b498276978 Dependency Upgrade (#85)
* Update: dependencies upgrade

* Fix: Run tests at root scope
2024-07-23 15:41:16 -07:00
Kevin Dang
ae9cac65a9 PR Template Update (#84)
* Update: version increment and reminder on template

* Update: comment on incrementing as necessary
2024-07-11 17:08:34 -07:00
Kevin Dang
61d3dc4312 User Preferences Fix (#83)
* Fix: incorrect user preferences saving
2024-07-10 20:41:23 -07:00
23 changed files with 937 additions and 686 deletions

View File

@@ -15,3 +15,5 @@
## After the Pull Request is Opened ## After the Pull Request is Opened
* One the Pull Request has been created, please add any Issue(s) that are being addressed by this change (if any). * One the Pull Request has been created, please add any Issue(s) that are being addressed by this change (if any).
* If the reviewer(s) mention any changes or open threads for questions, please resolve those as soon as you can. * If the reviewer(s) mention any changes or open threads for questions, please resolve those as soon as you can.
# Ensure you version increment as necessary!!!

View File

@@ -1,20 +1,10 @@
name: Builds name: Builds
run-name: Validate Node and Docker Builds run-name: Validate Node and Docker Builds
on: on:
pull_request: workflow_run:
branches: workflows: [Tests]
- master types:
paths: - completed
- '/'
- 'src/**'
- 'tests/**'
- '!docs/**'
- '!imgs/**'
- '!.github/**'
- '.github/workflows/**'
- '!.gitignore'
- '!LICENSE'
- '!README'
jobs: jobs:
Discord-Node-Build: # test if the node install and run Discord-Node-Build: # test if the node install and run

View File

@@ -5,7 +5,8 @@ on:
branches: branches:
- master - master
paths: paths:
- '/' - '*'
- 'package*.json'
- 'src/**' - 'src/**'
- 'tests/**' - 'tests/**'
- '!docs/**' - '!docs/**'
@@ -47,34 +48,3 @@ jobs:
- name: Test Application - name: Test Application
run: | run: |
npm run test:run npm run test:run
Discord-Ollama-Container-Test:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Node Environment lts/hydrogen
uses: actions/setup-node@v4
with:
node-version: lts/hydrogen
cache: "npm"
- name: Create Environment Variables
run: |
touch .env
echo CLIENT_TOKEN = ${{ secrets.BOT_TOKEN }} >> .env
echo GUILD_ID = ${{ secrets.GUILD_ID }} >> .env
echo MODEL = ${{ secrets.MODEL }} >> .env
echo CLIENT_UID = ${{ secrets.CLIENT_UID }} >> .env
echo OLLAMA_IP = ${{ secrets.OLLAMA_IP }} >> .env
echo OLLAMA_PORT = ${{ secrets.OLLAMA_PORT }} >> .env
- name: Setup Docker Network and Images
run: |
npm run docker:start-cpu
- name: Test Docker Container
run: |
npm run docker:test

View File

@@ -12,7 +12,7 @@
Ollama is an AI model management tool that allows users to install and use custom large language models locally. Ollama is an AI model management tool that allows users to install and use custom large language models locally.
The project aims to: The project aims to:
* [x] Create a Discord bot that will utilize Ollama and chat to chat with users! * [x] Create a Discord bot that will utilize Ollama and chat to chat with users!
* [ ] User Preferences on Chat * [x] User Preferences on Chat
* [x] Message Persistance on Channels and Threads * [x] Message Persistance on Channels and Threads
* [x] Threads * [x] Threads
* [x] Channels * [x] Channels
@@ -20,10 +20,10 @@ The project aims to:
* [x] Slash Commands Compatible * [x] Slash Commands Compatible
* [x] Generated Token Length Handling for >2000 * [x] Generated Token Length Handling for >2000
* [x] Token Length Handling of any message size * [x] Token Length Handling of any message size
* [ ] User vs. Server Preferences * [x] User vs. Server Preferences
* [ ] Redis Caching * [ ] Redis Caching
* [x] Administrator Role Compatible * [x] Administrator Role Compatible
* [ ] Multi-User Chat Generation (Multiple users chatting at the same time) * [x] Multi-User Chat Generation (Multiple users chatting at the same time) - This was built into from Ollama `v0.2.1+`
* [ ] Automatic and Manual model pulling through the Discord client * [ ] Automatic and Manual model pulling through the Discord client
* [ ] Allow others to create their own models personalized for their own servers! * [ ] Allow others to create their own models personalized for their own servers!
* [ ] Documentation on creating your own LLM * [ ] Documentation on creating your own LLM

View File

@@ -8,7 +8,7 @@ services:
build: ./ # find docker file in designated path build: ./ # find docker file in designated path
container_name: discord container_name: discord
restart: always # rebuild container always restart: always # rebuild container always
image: discord/bot:0.5.4 image: discord/bot:0.5.7
environment: environment:
CLIENT_TOKEN: ${CLIENT_TOKEN} CLIENT_TOKEN: ${CLIENT_TOKEN}
GUILD_ID: ${GUILD_ID} GUILD_ID: ${GUILD_ID}

1114
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "discord-ollama", "name": "discord-ollama",
"version": "0.5.4", "version": "0.5.7",
"description": "Ollama Integration into discord", "description": "Ollama Integration into discord",
"main": "build/index.js", "main": "build/index.js",
"exports": "./build/index.js", "exports": "./build/index.js",
@@ -19,7 +19,6 @@
"docker:clean": "docker rm -f discord && docker rm -f ollama && docker rmi $(docker images --filter \"dangling=true\" -q --no-trunc)", "docker:clean": "docker rm -f discord && docker rm -f ollama && docker rmi $(docker images --filter \"dangling=true\" -q --no-trunc)",
"docker:network": "docker network create --subnet=172.18.0.0/16 ollama-net", "docker:network": "docker network create --subnet=172.18.0.0/16 ollama-net",
"docker:build": "docker build --no-cache -t discord/bot:$(node -p \"require('./package.json').version\") .", "docker:build": "docker build --no-cache -t discord/bot:$(node -p \"require('./package.json').version\") .",
"docker:test": "docker run -d --rm -v discord:/src/app --name test discord/bot:$(node -p \"require('./package.json').version\") npm run test:run",
"docker:client": "docker run -d -v discord:/src/app --name discord --network ollama-net --ip 172.18.0.3 discord/bot:$(node -p \"require('./package.json').version\")", "docker:client": "docker run -d -v discord:/src/app --name discord --network ollama-net --ip 172.18.0.3 discord/bot:$(node -p \"require('./package.json').version\")",
"docker:ollama": "docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama --network ollama-net --ip 172.18.0.2 ollama/ollama:latest", "docker:ollama": "docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama --network ollama-net --ip 172.18.0.2 ollama/ollama:latest",
"docker:ollama-cpu": "docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama --network ollama-net --ip 172.18.0.2 ollama/ollama:latest" "docker:ollama-cpu": "docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama --network ollama-net --ip 172.18.0.2 ollama/ollama:latest"
@@ -29,16 +28,16 @@
"dependencies": { "dependencies": {
"discord.js": "^14.15.3", "discord.js": "^14.15.3",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"ollama": "^0.5.2" "ollama": "^0.5.6"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.14.2", "@types/node": "^20.14.12",
"@vitest/coverage-v8": "^1.6.0", "@vitest/coverage-v8": "^2.0.4",
"nodemon": "^3.1.3", "nodemon": "^3.1.4",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsx": "^4.15.5", "tsx": "^4.16.2",
"typescript": "^5.4.5", "typescript": "^5.5.4",
"vitest": "^1.6.0" "vitest": "^2.0.4"
}, },
"type": "module", "type": "module",
"engines": { "engines": {

View File

@@ -1,6 +1,6 @@
import { ChannelType, Client, CommandInteraction, ApplicationCommandOptionType } from 'discord.js' import { ChannelType, Client, CommandInteraction, ApplicationCommandOptionType } from 'discord.js'
import { SlashCommand } from '../utils/commands.js' import { SlashCommand } from '../utils/commands.js'
import { openConfig } from '../utils/jsonHandler.js' import { openConfig } from '../utils/index.js'
export const Capacity: SlashCommand = { export const Capacity: SlashCommand = {
name: 'modify-capacity', name: 'modify-capacity',
@@ -20,10 +20,10 @@ export const Capacity: SlashCommand = {
run: async (client: Client, interaction: CommandInteraction) => { run: async (client: Client, interaction: CommandInteraction) => {
// fetch channel and message // fetch channel and message
const channel = await client.channels.fetch(interaction.channelId) const channel = await client.channels.fetch(interaction.channelId)
if (!channel || channel.type !== (ChannelType.PublicThread && ChannelType.GuildText)) return if (!channel || channel.type !== (ChannelType.PrivateThread && ChannelType.PublicThread && ChannelType.GuildText)) return
// set state of bot chat features // set state of bot chat features
openConfig(`${interaction.client.user.username}-config.json`, interaction.commandName, interaction.options.get('context-capacity')?.value) openConfig(`${interaction.user.username}-config.json`, interaction.commandName, interaction.options.get('context-capacity')?.value)
interaction.reply({ interaction.reply({
content: `Message History Capacity has been set to \`${interaction.options.get('context-capacity')?.value}\``, content: `Message History Capacity has been set to \`${interaction.options.get('context-capacity')?.value}\``,

View File

@@ -1,6 +1,6 @@
import { ApplicationCommandOptionType, ChannelType, Client, CommandInteraction } from 'discord.js' import { ApplicationCommandOptionType, ChannelType, Client, CommandInteraction } from 'discord.js'
import { SlashCommand } from '../utils/commands.js' import { SlashCommand } from '../utils/commands.js'
import { openConfig } from '../utils/jsonHandler.js' import { openConfig } from '../utils/index.js'
export const ChannelToggle: SlashCommand = { export const ChannelToggle: SlashCommand = {
name: 'channel-toggle', name: 'channel-toggle',
@@ -20,8 +20,7 @@ export const ChannelToggle: SlashCommand = {
run: async (client: Client, interaction: CommandInteraction) => { run: async (client: Client, interaction: CommandInteraction) => {
// fetch channel location // fetch channel location
const channel = await client.channels.fetch(interaction.channelId) const channel = await client.channels.fetch(interaction.channelId)
if (!channel || channel.type !== (ChannelType.PublicThread && ChannelType.GuildText)) return if (!channel || channel.type !== (ChannelType.PrivateThread && ChannelType.PublicThread && ChannelType.GuildText)) return
// set state of bot channel preferences // set state of bot channel preferences
openConfig(`${interaction.guildId}-config.json`, interaction.commandName, interaction.options.get('toggle-channel')?.value) openConfig(`${interaction.guildId}-config.json`, interaction.commandName, interaction.options.get('toggle-channel')?.value)

View File

@@ -0,0 +1,34 @@
import { ChannelType, Client, CommandInteraction, TextChannel } from 'discord.js'
import { SlashCommand } from '../utils/commands.js'
import { clearChannelInfo } from '../utils/index.js'
export const ClearUserChannelHistory: SlashCommand = {
name: 'clear-user-channel-history',
description: 'clears history for user running this command in current channel',
// Clear channel history for intended user
run: async (client: Client, interaction: CommandInteraction) => {
// fetch current channel
const channel = await client.channels.fetch(interaction.channelId)
// if not an existing channel or a GuildText, fail command
if (!channel || channel.type !== ChannelType.GuildText) return
// clear channel info for user
const successfulWipe = await clearChannelInfo(interaction.channelId,
interaction.channel as TextChannel,
interaction.user.username)
// check result of clearing history
if (successfulWipe)
interaction.reply({
content: `Channel history in **${channel.name}** cleared for **${interaction.user.username}**.`,
ephemeral: true
})
else
interaction.reply({
content: `Channel history could not be found for **${interaction.user.username}** in **${channel.name}**.\n\nPlease chat with **${client.user?.username}** to start a chat history.`,
ephemeral: true
})
}
}

View File

@@ -1,6 +1,6 @@
import { ChannelType, Client, CommandInteraction, ApplicationCommandOptionType } from 'discord.js' import { ChannelType, Client, CommandInteraction, ApplicationCommandOptionType } from 'discord.js'
import { SlashCommand } from '../utils/commands.js' import { SlashCommand } from '../utils/commands.js'
import { openConfig } from '../utils/jsonHandler.js' import { openConfig } from '../utils/index.js'
export const Disable: SlashCommand = { export const Disable: SlashCommand = {
name: 'toggle-chat', name: 'toggle-chat',

View File

@@ -7,6 +7,7 @@ import { Shutoff } from './shutoff.js'
import { Capacity } from './capacity.js' import { Capacity } from './capacity.js'
import { PrivateThreadCreate } from './threadPrivateCreate.js' import { PrivateThreadCreate } from './threadPrivateCreate.js'
import { ChannelToggle } from './channelToggle.js' import { ChannelToggle } from './channelToggle.js'
import { ClearUserChannelHistory } from './cleanUserChannelHistory.js'
export default [ export default [
ThreadCreate, ThreadCreate,
@@ -16,5 +17,6 @@ export default [
Disable, Disable,
Shutoff, Shutoff,
Capacity, Capacity,
ChannelToggle ChannelToggle,
ClearUserChannelHistory
] as SlashCommand[] ] as SlashCommand[]

View File

@@ -1,6 +1,6 @@
import { ApplicationCommandOptionType, ChannelType, Client, CommandInteraction } from 'discord.js' import { ApplicationCommandOptionType, ChannelType, Client, CommandInteraction } from 'discord.js'
import { SlashCommand } from '../utils/commands.js' import { SlashCommand } from '../utils/commands.js'
import { openConfig } from '../utils/jsonHandler.js' import { openConfig } from '../utils/index.js'
export const MessageStream: SlashCommand = { export const MessageStream: SlashCommand = {
name: 'message-stream', name: 'message-stream',
@@ -20,10 +20,10 @@ export const MessageStream: SlashCommand = {
run: async (client: Client, interaction: CommandInteraction) => { run: async (client: Client, interaction: CommandInteraction) => {
// verify channel // verify channel
const channel = await client.channels.fetch(interaction.channelId) const channel = await client.channels.fetch(interaction.channelId)
if (!channel || channel.type !== (ChannelType.PublicThread && ChannelType.GuildText)) return if (!channel || channel.type !== (ChannelType.PrivateThread && ChannelType.PublicThread && ChannelType.GuildText)) return
// save value to json and write to it // save value to json and write to it
openConfig(`${interaction.client.user.username}-config.json`, interaction.commandName, interaction.options.get('stream')?.value) openConfig(`${interaction.user.username}-config.json`, interaction.commandName, interaction.options.get('stream')?.value)
interaction.reply({ interaction.reply({
content: `Message streaming preferences set to: \`${interaction.options.get('stream')?.value}\``, content: `Message streaming preferences set to: \`${interaction.options.get('stream')?.value}\``,

View File

@@ -1,6 +1,6 @@
import { ChannelType, Client, CommandInteraction, ApplicationCommandOptionType } from 'discord.js' import { ChannelType, Client, CommandInteraction, ApplicationCommandOptionType } from 'discord.js'
import { SlashCommand } from '../utils/commands.js' import { SlashCommand } from '../utils/commands.js'
import { openConfig } from '../utils/jsonHandler.js' import { openConfig } from '../utils/index.js'
export const MessageStyle: SlashCommand = { export const MessageStyle: SlashCommand = {
name: 'message-style', name: 'message-style',
@@ -20,10 +20,10 @@ export const MessageStyle: SlashCommand = {
run: async (client: Client, interaction: CommandInteraction) => { run: async (client: Client, interaction: CommandInteraction) => {
// fetch channel and message // fetch channel and message
const channel = await client.channels.fetch(interaction.channelId) const channel = await client.channels.fetch(interaction.channelId)
if (!channel || channel.type !== (ChannelType.PublicThread && ChannelType.GuildText)) return if (!channel || channel.type !== (ChannelType.PrivateThread && ChannelType.PublicThread && ChannelType.GuildText)) return
// set the message style // set the message style
openConfig(`${interaction.client.user.username}-config.json`, interaction.commandName, interaction.options.get('embed')?.value) openConfig(`${interaction.user.username}-config.json`, interaction.commandName, interaction.options.get('embed')?.value)
interaction.reply({ interaction.reply({
content: `Message style preferences for embed set to: \`${interaction.options.get('embed')?.value}\``, content: `Message style preferences for embed set to: \`${interaction.options.get('embed')?.value}\``,

View File

@@ -1,6 +1,6 @@
import { ChannelType, Client, CommandInteraction, TextChannel } from 'discord.js' import { ChannelType, Client, CommandInteraction, TextChannel } from 'discord.js'
import { SlashCommand } from '../utils/commands.js' import { SlashCommand } from '../utils/commands.js'
import { openThreadInfo } from '../utils/jsonHandler.js' import { openThreadInfo } from '../utils/index.js'
export const ThreadCreate: SlashCommand = { export const ThreadCreate: SlashCommand = {
name: 'thread', name: 'thread',
@@ -19,7 +19,7 @@ export const ThreadCreate: SlashCommand = {
}) })
// Send a message in the thread // 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.`) 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\``)
// handle storing this chat channel // handle storing this chat channel
// store: thread.id, thread.name // store: thread.id, thread.name

View File

@@ -1,6 +1,6 @@
import { ChannelType, Client, CommandInteraction, TextChannel } from 'discord.js' import { ChannelType, Client, CommandInteraction, TextChannel } from 'discord.js'
import { SlashCommand } from '../utils/commands.js' import { SlashCommand } from '../utils/commands.js'
import { openThreadInfo } from '../utils/jsonHandler.js' import { openThreadInfo } from '../utils/index.js'
export const PrivateThreadCreate: SlashCommand = { export const PrivateThreadCreate: SlashCommand = {
name: 'private-thread', name: 'private-thread',

View File

@@ -1,5 +1,5 @@
import { embedMessage, event, Events, normalMessage, UserMessage } from '../utils/index.js' import { embedMessage, event, Events, normalMessage, UserMessage } from '../utils/index.js'
import { getChannelInfo, getServerConfig, getThread, getUserConfig, openChannelInfo, openConfig, openThreadInfo, ServerConfig, UserConfig } from '../utils/jsonHandler.js' import { getChannelInfo, getServerConfig, getThread, getUserConfig, openChannelInfo, openConfig, openThreadInfo, ServerConfig, UserConfig } from '../utils/index.js'
import { clean } from '../utils/mentionClean.js' import { clean } from '../utils/mentionClean.js'
import { TextChannel, ThreadChannel } from 'discord.js' import { TextChannel, ThreadChannel } from 'discord.js'

View File

@@ -0,0 +1,58 @@
import { UserMessage } from './events.js'
export interface UserConfiguration {
'message-stream'?: boolean,
'message-style'?: boolean,
'modify-capacity': number
}
export interface ServerConfiguration {
'toggle-chat'?: boolean,
'channel-toggle'?: boolean
}
/**
* Parent Configuration interface
*
* @see ServerConfiguration server settings per guild
* @see UserConfiguration user configurations (only for the user for any server)
*/
export interface Configuration {
readonly name: string
options: UserConfiguration | ServerConfiguration
}
/**
* User config to use outside of this file
*/
export interface UserConfig {
readonly name: string
options: UserConfiguration
}
export interface ServerConfig {
readonly name: string
options: ServerConfiguration
}
export interface Thread {
readonly id: string
readonly name: string
messages: UserMessage[]
}
export interface Channel {
readonly id: string
readonly name: string
readonly user: string
messages: UserMessage[]
}
/**
* Check if the configuration we are editing/taking from is a Server Config
* @param key name of command we ran
* @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);
}

View File

@@ -1,149 +1,8 @@
import { TextChannel, ThreadChannel } from 'discord.js' import { TextChannel, ThreadChannel } from 'discord.js'
import { UserMessage } from './events.js' import { Configuration, Thread, Channel, UserMessage } from '../index.js'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
export interface UserConfiguration {
'message-stream'?: boolean,
'message-style'?: boolean,
'modify-capacity': number
}
export interface ServerConfiguration {
'toggle-chat'?: boolean,
'channel-toggle'?: boolean
}
/**
* Parent Configuration interface
*
* @see ServerConfiguration server settings per guild
* @see UserConfiguration user configurations (only for the user for any server)
*/
export interface Configuration {
readonly name: string
options: UserConfiguration | ServerConfiguration
}
/**
* User config to use outside of this file
*/
export interface UserConfig {
readonly name: string
options: UserConfiguration
}
export interface ServerConfig {
readonly name: string
options: ServerConfiguration
}
export interface Thread {
readonly id: string
readonly name: string
messages: UserMessage[]
}
export interface Channel {
readonly id: string
readonly name: string
readonly user: string
messages: UserMessage[]
}
function isUserConfigurationKey(key: string): key is keyof UserConfiguration {
return ['message-stream', 'message-style', 'modify-capacity'].includes(key);
}
function isServerConfigurationKey(key: string): key is keyof ServerConfiguration {
return ['toggle-chat', 'channel-toggle'].includes(key);
}
/**
* Method to open a file in the working directory and modify/create it
*
* @param filename name of the file
* @param key key value to access
* @param value new value to assign
*/
// add type of change (server, user)
export function openConfig(filename: string, key: string, value: any) {
const fullFileName = `data/${filename}`
// check if the file exists, if not then make the config file
if (fs.existsSync(fullFileName)) {
fs.readFile(fullFileName, 'utf8', (error, data) => {
if (error)
console.log(`[Error: openConfig] Incorrect file format`)
else {
const object = JSON.parse(data)
object['options'][key] = value
fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2))
}
})
} else { // work on dynamic file creation
let object: Configuration
if (isServerConfigurationKey(key))
object = JSON.parse('{ \"name\": \"Server Confirgurations\" }')
else
object = JSON.parse('{ \"name\": \"User Confirgurations\" }')
// set standard information for config file and options
object['options'] = {
[key]: value
}
fs.writeFileSync(`data/${filename}`, JSON.stringify(object, null, 2))
console.log(`[Util: openConfig] Created '${filename}' 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 getServerConfig(filename: string, callback: (config: ServerConfig | undefined) => void): Promise<void> {
const fullFileName = `data/${filename}`
// attempt to read the file and get the configuration
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 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 getUserConfig(filename: string, callback: (config: UserConfig | undefined) => void): Promise<void> {
const fullFileName = `data/${filename}`
// attempt to read the file and get the configuration
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 open/create and modify a json file containing thread information * Method to open/create and modify a json file containing thread information
* *
@@ -199,6 +58,61 @@ export async function getThread(filename: string, callback: (config: Thread | un
} }
} }
/**
* Method to check if a thread history file exists
*
* @param channel parent thread of the requested thread (can be GuildText)
* @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) => {
if (channelInfo?.messages)
resolve(true)
else
resolve(false)
})
})
return isThread
}
/**
* Method to clear channel history for requesting user
*
* @param filename guild id string
* @param channel the TextChannel in the Guild
* @param user username of user
* @returns nothing
*/
export async function clearChannelInfo(filename: string, channel: TextChannel, user: string): Promise<boolean> {
const channelInfoExists: boolean = await checkChannelInfoExists(channel, user)
// If thread does not exist, file can't be found
if (!channelInfoExists) return false
// Attempt to clear user channel history
const fullFileName = `data/${filename}-${user}.json`
const cleanedHistory: boolean = await new Promise((resolve) => {
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) // already empty, let user know
resolve(false)
else {
object['messages'] = [] // cleared history
fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2))
resolve(true)
}
}
})
})
console.log(cleanedHistory)
return cleanedHistory
}
/** /**
* Method to open the channel history * Method to open the channel history
* *
@@ -208,7 +122,6 @@ export async function getThread(filename: string, callback: (config: Thread | un
* @param messages their messages * @param messages their messages
*/ */
export async function openChannelInfo(filename: string, channel: TextChannel, user: string, messages: UserMessage[] = []): Promise<void> { export async function openChannelInfo(filename: string, channel: TextChannel, user: string, messages: UserMessage[] = []): Promise<void> {
// thread exist handler
const isThread: boolean = await new Promise((resolve) => { const isThread: boolean = await new Promise((resolve) => {
getThread(`${channel.id}.json`, (threadInfo) => { getThread(`${channel.id}.json`, (threadInfo) => {
if (threadInfo?.messages) if (threadInfo?.messages)
@@ -218,7 +131,7 @@ export async function openChannelInfo(filename: string, channel: TextChannel, us
}) })
}) })
// This is an existing thread, don't create another json // this is a thread channel, do not duplicate files
if (isThread) return if (isThread) return
const fullFileName = `data/${filename}-${user}.json` const fullFileName = `data/${filename}-${user}.json`

View File

@@ -0,0 +1,87 @@
import { Configuration, ServerConfig, UserConfig, isServerConfigurationKey } from '../index.js'
import fs from 'fs'
/**
* Method to open a file in the working directory and modify/create it
*
* @param filename name of the file
* @param key key value to access
* @param value new value to assign
*/
// add type of change (server, user)
export function openConfig(filename: string, key: string, value: any) {
const fullFileName = `data/${filename}`
// check if the file exists, if not then make the config file
if (fs.existsSync(fullFileName)) {
fs.readFile(fullFileName, 'utf8', (error, data) => {
if (error)
console.log(`[Error: openConfig] Incorrect file format`)
else {
const object = JSON.parse(data)
object['options'][key] = value
fs.writeFileSync(fullFileName, JSON.stringify(object, null, 2))
}
})
} else { // work on dynamic file creation
let object: Configuration
if (isServerConfigurationKey(key))
object = JSON.parse('{ \"name\": \"Server Confirgurations\" }')
else
object = JSON.parse('{ \"name\": \"User Confirgurations\" }')
// set standard information for config file and options
object['options'] = {
[key]: value
}
fs.writeFileSync(`data/${filename}`, JSON.stringify(object, null, 2))
console.log(`[Util: openConfig] Created '${filename}' 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 getServerConfig(filename: string, callback: (config: ServerConfig | undefined) => void): Promise<void> {
const fullFileName = `data/${filename}`
// attempt to read the file and get the configuration
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 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 getUserConfig(filename: string, callback: (config: UserConfig | undefined) => void): Promise<void> {
const fullFileName = `data/${filename}`
// attempt to read the file and get the configuration
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
}
}

View File

@@ -1,5 +1,5 @@
import { ChatResponse } from "ollama" import { ChatResponse } from "ollama"
import { ChatParams } from "./index.js" import { ChatParams } from "../index.js"
import { AbortableAsyncIterator } from "ollama/src/utils.js" import { AbortableAsyncIterator } from "ollama/src/utils.js"
/** /**

View File

@@ -4,4 +4,9 @@ export * from './events.js'
export * from './messageEmbed.js' export * from './messageEmbed.js'
export * from './messageNormal.js' export * from './messageNormal.js'
export * from './commands.js' export * from './commands.js'
export * from './streamHandler.js' export * from './configInterfaces.js'
// handler imports
export * from './handlers/chatHistoryHandler.js'
export * from './handlers/configHandler.js'
export * from './handlers/streamHandler.js'

View File

@@ -22,6 +22,6 @@ describe('#commands', () => {
// test specific commands in the object // test specific commands in the object
it('references specific commands', () => { it('references specific commands', () => {
const commandsString = commands.map(e => e.name).join(', ') 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') expect(commandsString).toBe('thread, private-thread, message-style, message-stream, toggle-chat, shutoff, modify-capacity, channel-toggle, clear-user-channel-history')
}) })
}) })