diff --git a/.gitignore b/.gitignore index ce66808..ab4680a 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ config.json # error output file error.json markovDB.json + +config diff --git a/src/deploy-commands.ts b/src/deploy-commands.ts index 94b1b93..8f5de61 100644 --- a/src/deploy-commands.ts +++ b/src/deploy-commands.ts @@ -1,14 +1,53 @@ -import { SlashCommandBuilder } from '@discordjs/builders'; +import { SlashCommandBuilder, SlashCommandChannelOption } from '@discordjs/builders'; import { REST } from '@discordjs/rest'; -import { Routes } from 'discord-api-types/v9'; +import { ChannelType, Routes } from 'discord-api-types/v9'; import { config } from './config'; import { packageJson } from './util'; +const CHANNEL_OPTIONS_MAX = 25; + const helpSlashCommand = new SlashCommandBuilder() .setName('help') .setDescription(`How to use ${packageJson().name}`); -const commands = [helpSlashCommand.toJSON()]; +/** + * Helps generate a list of parameters for channel options + */ +const channelOptionsGenerator = (builder: SlashCommandChannelOption, index: number) => + builder + .setName(`channel-${index + 1}`) + .setDescription('A text channel') + .setRequired(index === 0) + .addChannelType(ChannelType.GuildText as any); + +const listenChannelCommand = new SlashCommandBuilder() + .setName('listen') + .addSubcommand((sub) => { + sub + .setName('add') + .setDescription( + `Add channels to learn from. Doesn't add the channel's past messages; re-train to do that.` + ); + + Array.from(Array(CHANNEL_OPTIONS_MAX).keys()).forEach((index) => + sub.addChannelOption((opt) => channelOptionsGenerator(opt, index)) + ); + return sub; + }) + .addSubcommand((sub) => { + sub + .setName('remove') + .setDescription( + `Remove channels from being learned from. Doesn't remove the channel's data; re-train to do that.` + ); + Array.from(Array(CHANNEL_OPTIONS_MAX).keys()).forEach((index) => + sub.addChannelOption((opt) => channelOptionsGenerator(opt, index)) + ); + return sub; + }) + .setDescription(`How to use ${packageJson().name}`); + +const commands = [helpSlashCommand.toJSON(), listenChannelCommand.toJSON()]; export async function deployCommands(clientId: string) { const rest = new REST({ version: '9' }).setToken(config.token); diff --git a/src/entity/Channel.ts b/src/entity/Channel.ts index 7255678..080469a 100644 --- a/src/entity/Channel.ts +++ b/src/entity/Channel.ts @@ -5,10 +5,10 @@ import { Guild } from './Guild'; @Entity() export class Channel extends BaseEntity { @PrimaryColumn() - id: string; + id: number; @Column({ - default: true, + default: false, }) listen: boolean; diff --git a/src/entity/Guild.ts b/src/entity/Guild.ts index 8ddb36b..1bd8f41 100644 --- a/src/entity/Guild.ts +++ b/src/entity/Guild.ts @@ -5,7 +5,7 @@ import { Channel } from './Channel'; @Entity() export class Guild extends BaseEntity { @PrimaryColumn() - id: string; + id: number; @OneToMany(() => Channel, (channel) => channel.guild, { onDelete: 'CASCADE', cascade: true }) channels: Channel[]; diff --git a/src/index.ts b/src/index.ts index 3914e5b..485efe4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,6 +40,13 @@ const markovOpts: MarkovConstructorOptions = { stateSize: config.stateSize, }; +const markovGenerateOptions: MarkovGenerateOptions = { + filter: (result): boolean => { + return result.score >= config.minScore; + }, + maxTries: config.maxTries, +}; + /** * #v3-complete */ @@ -50,6 +57,20 @@ async function getMarkovByGuildId(guildId: string): Promise { return markov; } +/** + * #v3-complete + */ +async function isValidChannel(channelId: string): Promise { + const id = parseInt(channelId, 10); + const channel = await Channel.findOne(id); + if (!channel) { + L.warn({ channelId }, 'Channel does not exist, setting to valid'); + await Channel.create({ id }).save(); + return false; + } + return channel.listen; +} + /** * Checks if the author of a message as moderator-like permissions. * @param {GuildMember} member Sender of the message @@ -174,20 +195,25 @@ async function generateResponse( ): Promise { L.debug('Responding...'); if (!interaction.guildId) { - L.info('Received a message without a guildId'); + L.debug('Received an interaction without a guildId'); + return; + } + if (!interaction.channelId) { + L.debug('Received an interaction without a channelId'); + return; + } + const isValid = await isValidChannel(interaction.channelId); + if (!isValid) { + L.debug( + { channelId: interaction.channelId }, + 'Channel is not enabled for listening. Ignoring...' + ); return; } - const options: MarkovGenerateOptions = { - filter: (result): boolean => { - return result.score >= config.minScore; - }, - maxTries: config.maxTries, - }; - const markov = await getMarkovByGuildId(interaction.guildId); try { - const response = await markov.generate(options); + const response = await markov.generate(markovGenerateOptions); L.info({ response }, 'Generated response'); const messageOpts: Discord.MessageOptions = { tts }; const attachmentUrls = response.refs @@ -197,7 +223,6 @@ async function generateResponse( const randomRefAttachment = getRandomElement(attachmentUrls); messageOpts.files = [randomRefAttachment]; } else { - // TODO: This might not even work const randomMessage = await MarkovInputData.createQueryBuilder< MarkovInputData >('input') @@ -297,19 +322,10 @@ client.on('ready', async (readyClient) => { await deployCommands(readyClient.user.id); - const guildsToSave: Guild[] = []; - const channelsToSave: Channel[] = []; - readyClient.guilds.valueOf().forEach((guild) => { - const dbGuild = Guild.create({ id: guild.id }); - const textChannels = guild.channels.valueOf().filter((channel) => channel.isText()); - const dbChannels = textChannels.map((channel) => - Channel.create({ id: channel.id, guild: dbGuild }) - ); - guildsToSave.push(dbGuild); - channelsToSave.push(...dbChannels); - }); + const guildsToSave = readyClient.guilds + .valueOf() + .map((guild) => Guild.create({ id: parseInt(guild.id, 10) })); await Guild.upsert(guildsToSave, ['id']); - await Channel.upsert(channelsToSave, ['id']); // TODO: ensure this doesn't overwrite the existing `listen` }); client.on('error', (err) => {