From 76e6ca9e38460eb792d3d6008a5a0bdb9cf5e553 Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Fri, 12 Sep 2025 08:34:14 -0400 Subject: [PATCH] Staff knowledgebase --- src/commands/cite.ts | 49 ++++++++++++++ src/commands/knowledgebase.ts | 120 ++++++++++++++++++++++++++++++++++ src/shared/Database.ts | 41 +++++++++++- src/types/database-types.d.ts | 7 ++ 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/commands/cite.ts create mode 100644 src/commands/knowledgebase.ts diff --git a/src/commands/cite.ts b/src/commands/cite.ts new file mode 100644 index 0000000..2c15d2b --- /dev/null +++ b/src/commands/cite.ts @@ -0,0 +1,49 @@ +import { ApplicationIntegrationType, AutocompleteInteraction, BitFieldResolvable, ChatInputCommandInteraction, Client, GuildBasedChannel, GuildMember, InteractionContextType, MessageFlags, MessageMentions, PermissionFlagsBits, SlashCommandBuilder, time, TimestampStyles } from 'discord.js'; +import { deferInteraction } from '../utils'; +import { Database } from '../shared/Database'; +import { KnowledgebaseItem } from '../types'; + +export default { + name: 'cite', + data: new SlashCommandBuilder() + .setName('cite') + .setDescription('Cite content from the knowledgebase.') + .setIntegrationTypes(ApplicationIntegrationType.GuildInstall) + .setContexts(InteractionContextType.Guild) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) + .addIntegerOption(option => + option + .setName('name') + .setDescription('The name of the entry.') + .setRequired(true) + .setAutocomplete(true) + ), + handler: async function (client: Client, interaction: ChatInputCommandInteraction) { + if (!interaction.guild) return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Must be ran in guild.' }); + + await interaction.deferReply(); + + const id = interaction.options.getInteger('name', true); + + const item = await Database.getFromKnowledgebase(id); + + if (!item) return interaction.editReply('Knowledgebase item not found.'); + + return interaction.editReply(item.content); + }, + autoComplete: async function (client: Client, interaction: AutocompleteInteraction) { + if (!interaction.guild) return interaction.respond([]); + + const items: KnowledgebaseItem[] = await Database.getAllKnowledgebaseItems(interaction.guild.id); + + const value = interaction.options.getFocused(); + + const toRespond = items.filter(i => !value ? true : i.content.includes(value)); + if (toRespond.length > 25) toRespond.length = 25; + + interaction.respond(toRespond.map(p => ({ + name: p.name, + value: p.id + }))); + } +}; \ No newline at end of file diff --git a/src/commands/knowledgebase.ts b/src/commands/knowledgebase.ts new file mode 100644 index 0000000..7eac4a9 --- /dev/null +++ b/src/commands/knowledgebase.ts @@ -0,0 +1,120 @@ +import { ApplicationIntegrationType, AutocompleteInteraction, BitFieldResolvable, ChatInputCommandInteraction, Client, GuildBasedChannel, GuildMember, InteractionContextType, MessageFlags, MessageMentions, PermissionFlagsBits, SlashCommandBuilder, time, TimestampStyles } from 'discord.js'; +import { deferInteraction } from '../utils'; +import { Database } from '../shared/Database'; +import { KnowledgebaseItem } from '../types'; + +export default { + name: 'knowledgebase', + data: new SlashCommandBuilder() + .setName('knowledgebase') + .setDescription('Access the compendium of knowledge.') + .setIntegrationTypes(ApplicationIntegrationType.GuildInstall) + .setContexts(InteractionContextType.Guild) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) + .addSubcommand(subcommand => + subcommand + .setName('add') + .setDescription('Add to the knowledgebase.') + .addStringOption(option => + option + .setName('name') + .setDescription('The name of the entry.') + .setRequired(true) + ) + .addStringOption(option => + option + .setName('content') + .setDescription('The content of the entry.') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('remove') + .setDescription('Purge knowledge from the universe.') + .addIntegerOption(option => + option + .setName('name') + .setDescription('The name of the entry to remove.') + .setRequired(true) + .setAutocomplete(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('edit') + .setDescription('Edit a knowledgebase entry.') + .addIntegerOption(option => + option + .setName('name') + .setDescription('The name of the entry to edit.') + .setRequired(true) + .setAutocomplete(true) + ) + .addStringOption(option => + option + .setName('content') + .setDescription('The new content of the entry.') + .setRequired(true) + ) + ), + handler: async function (client: Client, interaction: ChatInputCommandInteraction) { + if (!interaction.guild) return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Must be ran in guild.' }); + + const subcommand = interaction.options.getSubcommand(true); + + if (subcommand == 'add') { + await deferInteraction(interaction); + + const name = interaction.options.getString('name', true); + const content = interaction.options.getString('content', true); + + const existingItem = await Database.getFromKnowledgebaseByName(interaction.guild.id, name); + + if (existingItem) return interaction.editReply(`Knowledgebase item ${name} already exists!`); + + await Database.addToKnowledgebase(interaction.guild.id, name, content); + + return interaction.editReply('Knowledge expanded.'); + } else if (subcommand == 'remove') { + await deferInteraction(interaction); + const id = interaction.options.getInteger('name', true); + + const item = await Database.getFromKnowledgebase(id); + + if (!item) return interaction.editReply('Knowledgebase item not found.'); + + await Database.removeFromKnowledgebase(id); + + return interaction.editReply('Knowledge removed.'); + } else if (subcommand == 'edit') { + await deferInteraction(interaction); + + const id = interaction.options.getInteger('name', true); + const content = interaction.options.getString('content', true); + + const existingItem = await Database.getFromKnowledgebase(id); + + if (!existingItem) return interaction.editReply('Knowledgebase item not found.'); + + await Database.editKnowledgebaseItem(id, content); + + return interaction.editReply('Information exchange completed.'); + } + }, + autoComplete: async function (client: Client, interaction: AutocompleteInteraction) { + if (!interaction.guild) return interaction.respond([]); + + const items: KnowledgebaseItem[] = await Database.getAllKnowledgebaseItems(interaction.guild.id); + + const value = interaction.options.getFocused(); + + const toRespond = items.filter(i => !value ? true : i.content.includes(value)); + if (toRespond.length > 25) toRespond.length = 25; + + interaction.respond(toRespond.map(p => ({ + name: p.name, + value: p.id + }))); + } +}; \ No newline at end of file diff --git a/src/shared/Database.ts b/src/shared/Database.ts index e9855ba..06eca7c 100644 --- a/src/shared/Database.ts +++ b/src/shared/Database.ts @@ -3,7 +3,7 @@ import { open, Database as SqliteDatabase } from 'sqlite'; import { config } from '../config'; import DiscordOAuth2 from 'discord-oauth2'; import { serializeMessage, wait } from '../utils'; -import { GuildSettings, LoggedMessage, TicketMessage, TicketPhrase, Note, Ban, GuildArraySetting, GithubUserMapping } from '../types'; +import { GuildSettings, LoggedMessage, TicketMessage, TicketPhrase, Note, Ban, GuildArraySetting, GithubUserMapping, KnowledgebaseItem } from '../types'; import { Message } from '../events'; const DB_SCHEMA = ` @@ -87,6 +87,13 @@ const DB_SCHEMA = ` discord_id TEXT, github_username TEXT ); + + CREATE TABLE IF NOT EXISTS knowledgebase ( + id INTEGER PRIMARY KEY, + guild_id TEXT NOT NULL, + name TEXT NOT NULL, + content TEXT NOT NULL + ) `; export class Database { @@ -395,4 +402,36 @@ export class Database { } // -- END GITHUB USER MAPPING -- + + // -- START KNOWLEDGEBASE -- + + static async addToKnowledgebase(guildId: string, name: string, content: string) { + if (content.length > 2000) return; + + await Database.db.run('INSERT INTO knowledgebase(guild_id, name, content) VALUES (?, ?, ?)', guildId, name, content); + } + + static async removeFromKnowledgebase(id: number) { + await Database.db.run('DELETE from knowledgebase WHERE id = ?', id); + } + + static async editKnowledgebaseItem(id: number, content: string) { + if (content.length > 2000) return; + + await Database.db.run('UPDATE knowledgebase SET content = ? WHERE id = ?', content, id); + } + + static async getFromKnowledgebaseByName(guildId: string, name: string): Promise { + return await Database.db.get('SELECT * from knowledgebase WHERE guild_id = ? AND name = ?', guildId, name); + } + + static async getFromKnowledgebase(id: number): Promise { + return await Database.db.get('SELECT * from knowledgebase WHERE id = ?', id); + } + + static async getAllKnowledgebaseItems(guildId: string): Promise { + return await Database.db.all('SELECT * from knowledgebase WHERE guild_id = ?', guildId); + } + + // -- END KNOWLEDGEBASE -- } \ No newline at end of file diff --git a/src/types/database-types.d.ts b/src/types/database-types.d.ts index 7bd4855..dfd0ee4 100644 --- a/src/types/database-types.d.ts +++ b/src/types/database-types.d.ts @@ -58,4 +58,11 @@ export type GithubUserMapping = { id: number discord_id: string github_username: string +} + +export type KnowledgebaseItem = { + id: number + guild_id: string + name: string + content: string } \ No newline at end of file