mirror of
https://github.com/pacnpal/markov-discord.git
synced 2025-12-20 11:01:04 -05:00
14
.github/workflows/build-and-push-image.yml
vendored
14
.github/workflows/build-and-push-image.yml
vendored
@@ -12,25 +12,25 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
install: true
|
install: true
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: charlocharlie
|
username: charlocharlie
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push master
|
- name: Build and push master
|
||||||
if: ${{ github.ref == 'refs/heads/master' }}
|
if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
target: deploy
|
target: deploy
|
||||||
push: true
|
push: true
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push dev
|
- name: Build and push dev
|
||||||
if: ${{ github.ref == 'refs/heads/develop' }}
|
if: ${{ github.ref == 'refs/heads/develop' }}
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
target: deploy
|
target: deploy
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
4
.github/workflows/dockerhub-description.yml
vendored
4
.github/workflows/dockerhub-description.yml
vendored
@@ -11,10 +11,10 @@ jobs:
|
|||||||
dockerHubDescription:
|
dockerHubDescription:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Docker Hub Description
|
- name: Docker Hub Description
|
||||||
uses: peter-evans/dockerhub-description@v2
|
uses: peter-evans/dockerhub-description@v4
|
||||||
with:
|
with:
|
||||||
username: charlocharlie
|
username: charlocharlie
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|||||||
8
.github/workflows/typedoc.yml
vendored
8
.github/workflows/typedoc.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
|||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Node.js for use with actions
|
- name: Setup Node.js for use with actions
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: NPM install
|
- name: NPM install
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
run: npm run docs
|
run: npm run docs
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_dir: ./docs
|
publish_dir: ./docs
|
||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -4,7 +4,7 @@
|
|||||||
"typescript"
|
"typescript"
|
||||||
],
|
],
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true
|
"source.fixAll.eslint": "explicit"
|
||||||
},
|
},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
|
### 2.3.0
|
||||||
|
|
||||||
|
* Update to Node 20 and Discord.js 14. Update a million dependencies
|
||||||
|
* Fix empty attachment bug (#61)
|
||||||
|
|
||||||
### 2.2.0
|
### 2.2.0
|
||||||
|
|
||||||
* Add a `clean` option flag to the `/train` command to allow retraining without overwriting
|
* Add a `clean` option flag to the `/train` command to allow retraining without overwriting
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
########
|
########
|
||||||
# BASE
|
# BASE
|
||||||
########
|
########
|
||||||
FROM node:16-alpine3.15 as base
|
FROM node:20-alpine3.20 as base
|
||||||
|
|
||||||
WORKDIR /usr/app
|
WORKDIR /usr/app
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ FROM base as prodDeps
|
|||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
# Install build tools for erlpack, then install prod deps only
|
# Install build tools for erlpack, then install prod deps only
|
||||||
RUN apk add --no-cache make gcc g++ python3 \
|
RUN apk add --no-cache make gcc g++ python3 \
|
||||||
&& npm ci --only=production
|
&& npm ci --omit=dev
|
||||||
|
|
||||||
########
|
########
|
||||||
# BUILD
|
# BUILD
|
||||||
@@ -26,7 +26,7 @@ FROM base as build
|
|||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
# Install build tools for erlpack, then install prod deps only
|
# Install build tools for erlpack, then install prod deps only
|
||||||
RUN apk add --no-cache make gcc g++ python3 \
|
RUN apk add --no-cache make gcc g++ python3 \
|
||||||
&& npm ci --only=production
|
&& npm ci --omit=dev
|
||||||
|
|
||||||
# Copy all jsons
|
# Copy all jsons
|
||||||
COPY package*.json tsconfig.json ./
|
COPY package*.json tsconfig.json ./
|
||||||
|
|||||||
6010
package-lock.json
generated
6010
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
60
package.json
60
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "markov-discord",
|
"name": "markov-discord",
|
||||||
"version": "2.2.0",
|
"version": "2.3.0",
|
||||||
"description": "A conversational Markov chain bot for Discord",
|
"description": "A conversational Markov chain bot for Discord",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -31,50 +31,46 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/builders": "^0.13.0",
|
"better-sqlite3": "^8.7.0",
|
||||||
"@discordjs/rest": "^0.4.1",
|
"bufferutil": "^4.0.8",
|
||||||
"@types/fs-extra": "^9.0.13",
|
|
||||||
"better-sqlite3": "^7.5.3",
|
|
||||||
"bufferutil": "^4.0.6",
|
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.1",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"discord-api-types": "^0.33.0",
|
"discord.js": "^14.15.3",
|
||||||
"discord.js": "^13.7.0",
|
"dotenv": "^16.4.5",
|
||||||
"dotenv": "^16.0.1",
|
"fs-extra": "^11.2.0",
|
||||||
"erlpack": "github:discord/erlpack",
|
"json5": "^2.2.3",
|
||||||
"fs-extra": "^10.1.0",
|
|
||||||
"json5": "^2.2.2",
|
|
||||||
"markov-strings-db": "^4.2.0",
|
"markov-strings-db": "^4.2.0",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"pino": "^7.11.0",
|
"pino": "^7.11.0",
|
||||||
"pino-pretty": "^7.6.1",
|
"pino-pretty": "^7.6.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.2.2",
|
||||||
"simple-eta": "^3.0.2",
|
"simple-eta": "^3.0.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"typeorm": "^0.3.14",
|
"typeorm": "^0.3.20",
|
||||||
"utf-8-validate": "^5.0.9",
|
"utf-8-validate": "^6.0.4",
|
||||||
"zlib-sync": "^0.1.7"
|
"zlib-sync": "^0.1.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.11.36",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/validator": "^13.7.2",
|
"@types/node": "^20.14.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.25.0",
|
"@types/validator": "^13.12.0",
|
||||||
"@typescript-eslint/parser": "^5.25.0",
|
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
||||||
"eslint": "^8.16.0",
|
"@typescript-eslint/parser": "^7.16.1",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"pm2": "^5.3.1",
|
"pm2": "^5.4.2",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^3.3.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^6.0.1",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.9.2",
|
||||||
"typedoc": "^0.22.15",
|
"typedoc": "^0.26.4",
|
||||||
"types-package-json": "^2.0.39",
|
"types-package-json": "^2.0.39",
|
||||||
"typescript": "^4.6.4"
|
"typescript": "5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { SlashCommandBuilder, SlashCommandChannelOption } from '@discordjs/builders';
|
import {
|
||||||
import { REST } from '@discordjs/rest';
|
SlashCommandChannelOption,
|
||||||
import { ChannelType, Routes } from 'discord-api-types/v10';
|
SlashCommandBuilder,
|
||||||
|
ChannelType,
|
||||||
|
Routes,
|
||||||
|
REST,
|
||||||
|
} from 'discord.js';
|
||||||
import { config } from './config';
|
import { config } from './config';
|
||||||
import { packageJson } from './util';
|
import { packageJson } from './util';
|
||||||
|
|
||||||
@@ -18,21 +22,21 @@ export const messageCommand = new SlashCommandBuilder()
|
|||||||
.setName(config.slashCommandName)
|
.setName(config.slashCommandName)
|
||||||
.setDescription('Generate a message from learned past messages')
|
.setDescription('Generate a message from learned past messages')
|
||||||
.addBooleanOption((tts) =>
|
.addBooleanOption((tts) =>
|
||||||
tts.setName('tts').setDescription('Read the message via text-to-speech.').setRequired(false)
|
tts.setName('tts').setDescription('Read the message via text-to-speech.').setRequired(false),
|
||||||
)
|
)
|
||||||
.addBooleanOption((debug) =>
|
.addBooleanOption((debug) =>
|
||||||
debug
|
debug
|
||||||
.setName('debug')
|
.setName('debug')
|
||||||
.setDescription('Follow up the generated message with the detailed sources that inspired it.')
|
.setDescription('Follow up the generated message with the detailed sources that inspired it.')
|
||||||
.setRequired(false)
|
.setRequired(false),
|
||||||
)
|
)
|
||||||
.addStringOption((seed) =>
|
.addStringOption((seed) =>
|
||||||
seed
|
seed
|
||||||
.setName('seed')
|
.setName('seed')
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`A ${config.stateSize}-word phrase to attempt to start a generated sentence with.`
|
`A ${config.stateSize}-word phrase to attempt to start a generated sentence with.`,
|
||||||
)
|
)
|
||||||
.setRequired(false)
|
.setRequired(false),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,10 +56,10 @@ export const listenChannelCommand = new SlashCommandBuilder()
|
|||||||
sub
|
sub
|
||||||
.setName('add')
|
.setName('add')
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Add channels to learn from. Doesn't add the channel's past messages; re-train to do that.`
|
`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) =>
|
Array.from(Array(CHANNEL_OPTIONS_MAX).keys()).forEach((index) =>
|
||||||
sub.addChannelOption((opt) => channelOptionsGenerator(opt, index))
|
sub.addChannelOption((opt) => channelOptionsGenerator(opt, index)),
|
||||||
);
|
);
|
||||||
return sub;
|
return sub;
|
||||||
})
|
})
|
||||||
@@ -63,42 +67,42 @@ export const listenChannelCommand = new SlashCommandBuilder()
|
|||||||
sub
|
sub
|
||||||
.setName('remove')
|
.setName('remove')
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Remove channels from being learned from. Doesn't remove the channel's data; re-train to do that.`
|
`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) =>
|
Array.from(Array(CHANNEL_OPTIONS_MAX).keys()).forEach((index) =>
|
||||||
sub.addChannelOption((opt) => channelOptionsGenerator(opt, index))
|
sub.addChannelOption((opt) => channelOptionsGenerator(opt, index)),
|
||||||
);
|
);
|
||||||
return sub;
|
return sub;
|
||||||
})
|
})
|
||||||
.addSubcommand((sub) =>
|
.addSubcommand((sub) =>
|
||||||
sub
|
sub
|
||||||
.setName('list')
|
.setName('list')
|
||||||
.setDescription(`List the channels the bot is currently actively listening to.`)
|
.setDescription(`List the channels the bot is currently actively listening to.`),
|
||||||
)
|
)
|
||||||
.addSubcommand((sub) =>
|
.addSubcommand((sub) =>
|
||||||
sub
|
sub
|
||||||
.setName('modify')
|
.setName('modify')
|
||||||
.setDescription(`Add or remove channels via select menu UI (first 25 text channels only)`)
|
.setDescription(`Add or remove channels via select menu UI (first 25 text channels only)`),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const trainCommand = new SlashCommandBuilder()
|
export const trainCommand = new SlashCommandBuilder()
|
||||||
.setName('train')
|
.setName('train')
|
||||||
.setDescription(
|
.setDescription(
|
||||||
'Train from past messages from the configured listened channels. This takes a while.'
|
'Train from past messages from the configured listened channels. This takes a while.',
|
||||||
)
|
)
|
||||||
.addBooleanOption((clean) =>
|
.addBooleanOption((clean) =>
|
||||||
clean
|
clean
|
||||||
.setName('clean')
|
.setName('clean')
|
||||||
.setDescription(
|
.setDescription(
|
||||||
'Whether the database should be emptied before training. Default is true (recommended).'
|
'Whether the database should be emptied before training. Default is true (recommended).',
|
||||||
)
|
)
|
||||||
.setRequired(false)
|
.setRequired(false),
|
||||||
)
|
)
|
||||||
.addAttachmentOption((json) =>
|
.addAttachmentOption((json) =>
|
||||||
json
|
json
|
||||||
.setName('json')
|
.setName('json')
|
||||||
.setDescription('Train from a provided JSON file rather than channel history.')
|
.setDescription('Train from a provided JSON file rather than channel history.')
|
||||||
.setRequired(false)
|
.setRequired(false),
|
||||||
);
|
);
|
||||||
|
|
||||||
const commands = [
|
const commands = [
|
||||||
|
|||||||
203
src/index.ts
203
src/index.ts
@@ -12,8 +12,6 @@ import type { PackageJsonPerson } from 'types-package-json';
|
|||||||
import makeEta from 'simple-eta';
|
import makeEta from 'simple-eta';
|
||||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
||||||
import addSeconds from 'date-fns/addSeconds';
|
import addSeconds from 'date-fns/addSeconds';
|
||||||
import type { APIInteractionGuildMember, APISelectMenuComponent } from 'discord-api-types/v9';
|
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import L from './logger';
|
import L from './logger';
|
||||||
import { Channel } from './entity/Channel';
|
import { Channel } from './entity/Channel';
|
||||||
import { Guild } from './entity/Guild';
|
import { Guild } from './entity/Guild';
|
||||||
@@ -40,21 +38,30 @@ interface SelectMenuChannel {
|
|||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IRefreshUrlsRes {
|
||||||
|
refreshed_urls: Array<{
|
||||||
|
original: string;
|
||||||
|
refreshed: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reply options that can be used in both MessageOptions and InteractionReplyOptions
|
* Reply options that can be used in both MessageOptions and InteractionReplyOptions
|
||||||
*/
|
*/
|
||||||
type AgnosticReplyOptions = Omit<Discord.MessageOptions, 'reply' | 'stickers' | 'flags'>;
|
type AgnosticReplyOptions = Omit<Discord.MessageCreateOptions, 'reply' | 'stickers' | 'flags'>;
|
||||||
|
|
||||||
const INVALID_PERMISSIONS_MESSAGE = 'You do not have the permissions for this action.';
|
const INVALID_PERMISSIONS_MESSAGE = 'You do not have the permissions for this action.';
|
||||||
const INVALID_GUILD_MESSAGE = 'This action must be performed within a server.';
|
const INVALID_GUILD_MESSAGE = 'This action must be performed within a server.';
|
||||||
|
|
||||||
|
const rest = new Discord.REST({ version: '10' }).setToken(config.token);
|
||||||
|
|
||||||
const client = new Discord.Client<true>({
|
const client = new Discord.Client<true>({
|
||||||
failIfNotExists: false,
|
failIfNotExists: false,
|
||||||
intents: [Discord.Intents.FLAGS.GUILD_MESSAGES, Discord.Intents.FLAGS.GUILDS],
|
intents: [Discord.GatewayIntentBits.GuildMessages, Discord.GatewayIntentBits.Guilds],
|
||||||
presence: {
|
presence: {
|
||||||
activities: [
|
activities: [
|
||||||
{
|
{
|
||||||
type: 'PLAYING',
|
type: Discord.ActivityType.Playing,
|
||||||
name: config.activity,
|
name: config.activity,
|
||||||
url: packageJson().homepage,
|
url: packageJson().homepage,
|
||||||
},
|
},
|
||||||
@@ -75,6 +82,14 @@ const markovGenerateOptions: MarkovGenerateOptions<MarkovDataCustom> = {
|
|||||||
maxTries: config.maxTries,
|
maxTries: config.maxTries,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function refreshCdnUrl(url: string): Promise<string> {
|
||||||
|
// Thank you https://github.com/ShufflePerson/Discord_CDN
|
||||||
|
const resp = (await rest.post(`/attachments/refresh-urls`, {
|
||||||
|
body: { attachment_urls: [url] },
|
||||||
|
})) as IRefreshUrlsRes;
|
||||||
|
return resp.refreshed_urls[0].refreshed;
|
||||||
|
}
|
||||||
|
|
||||||
async function getMarkovByGuildId(guildId: string): Promise<Markov> {
|
async function getMarkovByGuildId(guildId: string): Promise<Markov> {
|
||||||
const markov = new Markov({ id: guildId, options: { ...markovOpts, id: guildId } });
|
const markov = new Markov({ id: guildId, options: { ...markovOpts, id: guildId } });
|
||||||
L.trace({ guildId }, 'Setting up markov instance');
|
L.trace({ guildId }, 'Setting up markov instance');
|
||||||
@@ -117,7 +132,7 @@ async function getValidChannels(guild: Discord.Guild): Promise<Discord.TextChann
|
|||||||
L.error({ erroredChannel: dbc, channelId }, 'Error fetching channel');
|
L.error({ erroredChannel: dbc, channelId }, 'Error fetching channel');
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
).filter((c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel);
|
).filter((c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel);
|
||||||
return channels;
|
return channels;
|
||||||
@@ -127,7 +142,7 @@ async function getTextChannels(guild: Discord.Guild): Promise<SelectMenuChannel[
|
|||||||
L.trace('Getting text channels for select menu');
|
L.trace('Getting text channels for select menu');
|
||||||
const MAX_SELECT_OPTIONS = 25;
|
const MAX_SELECT_OPTIONS = 25;
|
||||||
const textChannels = guild.channels.cache.filter(
|
const textChannels = guild.channels.cache.filter(
|
||||||
(c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel
|
(c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel,
|
||||||
);
|
);
|
||||||
const foundDbChannels = await Channel.findByIds(Array.from(textChannels.keys()));
|
const foundDbChannels = await Channel.findByIds(Array.from(textChannels.keys()));
|
||||||
const foundDbChannelsWithName: SelectMenuChannel[] = foundDbChannels.map((c) => ({
|
const foundDbChannelsWithName: SelectMenuChannel[] = foundDbChannels.map((c) => ({
|
||||||
@@ -153,7 +168,7 @@ async function addValidChannels(channels: Discord.TextChannel[], guildId: string
|
|||||||
|
|
||||||
async function removeValidChannels(
|
async function removeValidChannels(
|
||||||
channels: Discord.TextChannel[],
|
channels: Discord.TextChannel[],
|
||||||
guildId: string
|
guildId: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
L.trace(`Removing ${channels.length} channels from valid list`);
|
L.trace(`Removing ${channels.length} channels from valid list`);
|
||||||
const dbChannels = channels.map((c) => {
|
const dbChannels = channels.map((c) => {
|
||||||
@@ -168,12 +183,14 @@ async function removeValidChannels(
|
|||||||
* @return {Boolean} True if the sender is a moderator.
|
* @return {Boolean} True if the sender is a moderator.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function isModerator(member: Discord.GuildMember | APIInteractionGuildMember | null): boolean {
|
function isModerator(
|
||||||
|
member: Discord.GuildMember | Discord.APIInteractionGuildMember | null,
|
||||||
|
): boolean {
|
||||||
const MODERATOR_PERMISSIONS: Discord.PermissionResolvable[] = [
|
const MODERATOR_PERMISSIONS: Discord.PermissionResolvable[] = [
|
||||||
'ADMINISTRATOR',
|
'Administrator',
|
||||||
'MANAGE_CHANNELS',
|
'ManageChannels',
|
||||||
'KICK_MEMBERS',
|
'KickMembers',
|
||||||
'MOVE_MEMBERS',
|
'MoveMembers',
|
||||||
];
|
];
|
||||||
if (!member) return false;
|
if (!member) return false;
|
||||||
if (member instanceof Discord.GuildMember) {
|
if (member instanceof Discord.GuildMember) {
|
||||||
@@ -193,7 +210,9 @@ function isModerator(member: Discord.GuildMember | APIInteractionGuildMember | n
|
|||||||
* @return {Boolean} True if the sender is a moderator.
|
* @return {Boolean} True if the sender is a moderator.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function isAllowedUser(member: Discord.GuildMember | APIInteractionGuildMember | null): boolean {
|
function isAllowedUser(
|
||||||
|
member: Discord.GuildMember | Discord.APIInteractionGuildMember | null,
|
||||||
|
): boolean {
|
||||||
if (!config.userRoleIds.length) return true;
|
if (!config.userRoleIds.length) return true;
|
||||||
if (!member) return false;
|
if (!member) return false;
|
||||||
if (member instanceof Discord.GuildMember) {
|
if (member instanceof Discord.GuildMember) {
|
||||||
@@ -255,7 +274,7 @@ function messageToData(message: Discord.Message): AddDataProps {
|
|||||||
*/
|
*/
|
||||||
async function saveGuildMessageHistory(
|
async function saveGuildMessageHistory(
|
||||||
interaction: Discord.Message | Discord.CommandInteraction,
|
interaction: Discord.Message | Discord.CommandInteraction,
|
||||||
clean = true
|
clean = true,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (!isModerator(interaction.member)) return INVALID_PERMISSIONS_MESSAGE;
|
if (!isModerator(interaction.member)) return INVALID_PERMISSIONS_MESSAGE;
|
||||||
if (!interaction.guildId || !interaction.guild) return INVALID_GUILD_MESSAGE;
|
if (!interaction.guildId || !interaction.guild) return INVALID_GUILD_MESSAGE;
|
||||||
@@ -280,31 +299,31 @@ async function saveGuildMessageHistory(
|
|||||||
const messageContent = `Parsing past messages from ${channels.length} channel(s).`;
|
const messageContent = `Parsing past messages from ${channels.length} channel(s).`;
|
||||||
|
|
||||||
const NO_COMPLETED_CHANNELS_TEXT = 'None';
|
const NO_COMPLETED_CHANNELS_TEXT = 'None';
|
||||||
const completedChannelsField: Discord.EmbedFieldData = {
|
const completedChannelsField: Discord.APIEmbedField = {
|
||||||
name: 'Completed Channels',
|
name: 'Completed Channels',
|
||||||
value: NO_COMPLETED_CHANNELS_TEXT,
|
value: NO_COMPLETED_CHANNELS_TEXT,
|
||||||
inline: true,
|
inline: true,
|
||||||
};
|
};
|
||||||
const currentChannelField: Discord.EmbedFieldData = {
|
const currentChannelField: Discord.APIEmbedField = {
|
||||||
name: 'Current Channel',
|
name: 'Current Channel',
|
||||||
value: `<#${channels[0].id}>`,
|
value: `<#${channels[0].id}>`,
|
||||||
inline: true,
|
inline: true,
|
||||||
};
|
};
|
||||||
const currentChannelPercent: Discord.EmbedFieldData = {
|
const currentChannelPercent: Discord.APIEmbedField = {
|
||||||
name: 'Channel Progress',
|
name: 'Channel Progress',
|
||||||
value: '0%',
|
value: '0%',
|
||||||
inline: true,
|
inline: true,
|
||||||
};
|
};
|
||||||
const currentChannelEta: Discord.EmbedFieldData = {
|
const currentChannelEta: Discord.APIEmbedField = {
|
||||||
name: 'Channel Time Remaining',
|
name: 'Channel Time Remaining',
|
||||||
value: 'Pending...',
|
value: 'Pending...',
|
||||||
inline: true,
|
inline: true,
|
||||||
};
|
};
|
||||||
const embedOptions: Discord.MessageEmbedOptions = {
|
const embedOptions: Discord.EmbedData = {
|
||||||
title: 'Training Progress',
|
title: 'Training Progress',
|
||||||
fields: [completedChannelsField, currentChannelField, currentChannelPercent, currentChannelEta],
|
fields: [completedChannelsField, currentChannelField, currentChannelPercent, currentChannelEta],
|
||||||
};
|
};
|
||||||
const embed = new Discord.MessageEmbed(embedOptions);
|
const embed = new Discord.EmbedBuilder(embedOptions);
|
||||||
let progressMessage: Discord.Message;
|
let progressMessage: Discord.Message;
|
||||||
const updateMessageData = { content: messageContent, embeds: [embed] };
|
const updateMessageData = { content: messageContent, embeds: [embed] };
|
||||||
if (interaction instanceof Discord.Message) {
|
if (interaction instanceof Discord.Message) {
|
||||||
@@ -338,7 +357,7 @@ async function saveGuildMessageHistory(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
L.error(err);
|
L.error(err);
|
||||||
L.error(
|
L.error(
|
||||||
`Error retreiving messages before ${oldestMessageID} in channel ${channel.name}. This is probably a permissions issue.`
|
`Error retreiving messages before ${oldestMessageID} in channel ${channel.name}. This is probably a permissions issue.`,
|
||||||
);
|
);
|
||||||
break; // Give up on this channel
|
break; // Give up on this channel
|
||||||
}
|
}
|
||||||
@@ -347,7 +366,7 @@ async function saveGuildMessageHistory(
|
|||||||
const threadChannels = channelBatchMessages
|
const threadChannels = channelBatchMessages
|
||||||
.filter((m) => m.hasThread)
|
.filter((m) => m.hasThread)
|
||||||
.map((m) => m.thread)
|
.map((m) => m.thread)
|
||||||
.filter((c): c is Discord.ThreadChannel => c !== null);
|
.filter((c): c is Discord.AnyThreadChannel => c !== null);
|
||||||
if (threadChannels.length > 0) {
|
if (threadChannels.length > 0) {
|
||||||
L.debug(`Found ${threadChannels.length} threads. Reading into them.`);
|
L.debug(`Found ${threadChannels.length} threads. Reading into them.`);
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
@@ -367,13 +386,13 @@ async function saveGuildMessageHistory(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
L.error(err);
|
L.error(err);
|
||||||
L.error(
|
L.error(
|
||||||
`Error retreiving thread messages before ${oldestThreadMessageID} in thread ${threadChannel.name}. This is probably a permissions issue.`
|
`Error retreiving thread messages before ${oldestThreadMessageID} in thread ${threadChannel.name}. This is probably a permissions issue.`,
|
||||||
);
|
);
|
||||||
break; // Give up on this thread
|
break; // Give up on this thread
|
||||||
}
|
}
|
||||||
L.trace(
|
L.trace(
|
||||||
{ threadMessagesCount: threadBatchMessages.size },
|
{ threadMessagesCount: threadBatchMessages.size },
|
||||||
`Found some thread messages`
|
`Found some thread messages`,
|
||||||
);
|
);
|
||||||
const lastThreadMessage = threadBatchMessages.last();
|
const lastThreadMessage = threadBatchMessages.last();
|
||||||
allBatchMessages = allBatchMessages.concat(threadBatchMessages); // Add the thread messages to this message batch to be included in later processing
|
allBatchMessages = allBatchMessages.concat(threadBatchMessages); // Add the thread messages to this message batch to be included in later processing
|
||||||
@@ -431,12 +450,12 @@ async function saveGuildMessageHistory(
|
|||||||
lastUpdate = messagesCount;
|
lastUpdate = messagesCount;
|
||||||
L.debug(
|
L.debug(
|
||||||
{ messagesCount, pctComplete: currentChannelPercent.value },
|
{ messagesCount, pctComplete: currentChannelPercent.value },
|
||||||
'Sending metrics update'
|
'Sending metrics update',
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await progressMessage.edit({
|
await progressMessage.edit({
|
||||||
...updateMessageData,
|
...updateMessageData,
|
||||||
embeds: [new Discord.MessageEmbed(embedOptions)],
|
embeds: [new Discord.EmbedBuilder(embedOptions)],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,9 +474,9 @@ interface JSONImport {
|
|||||||
* Train from an attached JSON file
|
* Train from an attached JSON file
|
||||||
*/
|
*/
|
||||||
async function trainFromAttachmentJson(
|
async function trainFromAttachmentJson(
|
||||||
attachment: Discord.MessageAttachment,
|
attachmentUrl: string,
|
||||||
interaction: Discord.CommandInteraction,
|
interaction: Discord.CommandInteraction,
|
||||||
clean = true
|
clean = true,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (!isModerator(interaction.member)) return INVALID_PERMISSIONS_MESSAGE;
|
if (!isModerator(interaction.member)) return INVALID_PERMISSIONS_MESSAGE;
|
||||||
if (!interaction.guildId || !interaction.guild) return INVALID_GUILD_MESSAGE;
|
if (!interaction.guildId || !interaction.guild) return INVALID_GUILD_MESSAGE;
|
||||||
@@ -466,8 +485,7 @@ async function trainFromAttachmentJson(
|
|||||||
|
|
||||||
let trainingData: AddDataProps[];
|
let trainingData: AddDataProps[];
|
||||||
try {
|
try {
|
||||||
const importAttachmentUrl = attachment.attachment.toString();
|
const getResp = await fetch(attachmentUrl);
|
||||||
const getResp = await fetch(importAttachmentUrl);
|
|
||||||
if (!getResp.ok) throw new Error(getResp.statusText);
|
if (!getResp.ok) throw new Error(getResp.statusText);
|
||||||
const importData = (await getResp.json()) as JSONImport[];
|
const importData = (await getResp.json()) as JSONImport[];
|
||||||
|
|
||||||
@@ -480,7 +498,7 @@ async function trainFromAttachmentJson(
|
|||||||
}
|
}
|
||||||
if (datum.attachments?.every((a) => typeof a !== 'string')) {
|
if (datum.attachments?.every((a) => typeof a !== 'string')) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Entry at index ${index} must have all "attachments" each with a type of string`
|
`Entry at index ${index} must have all "attachments" each with a type of string`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let custom: MarkovDataCustom | undefined;
|
let custom: MarkovDataCustom | undefined;
|
||||||
@@ -530,7 +548,7 @@ interface GenerateOptions {
|
|||||||
*/
|
*/
|
||||||
async function generateResponse(
|
async function generateResponse(
|
||||||
interaction: Discord.Message | Discord.CommandInteraction,
|
interaction: Discord.Message | Discord.CommandInteraction,
|
||||||
options?: GenerateOptions
|
options?: GenerateOptions,
|
||||||
): Promise<GenerateResponse> {
|
): Promise<GenerateResponse> {
|
||||||
L.debug({ options }, 'Responding...');
|
L.debug({ options }, 'Responding...');
|
||||||
const { tts = false, debug = false, startSeed } = options || {};
|
const { tts = false, debug = false, startSeed } = options || {};
|
||||||
@@ -558,7 +576,8 @@ async function generateResponse(
|
|||||||
.flatMap((ref) => (ref.custom as MarkovDataCustom).attachments);
|
.flatMap((ref) => (ref.custom as MarkovDataCustom).attachments);
|
||||||
if (attachmentUrls.length > 0) {
|
if (attachmentUrls.length > 0) {
|
||||||
const randomRefAttachment = getRandomElement(attachmentUrls);
|
const randomRefAttachment = getRandomElement(attachmentUrls);
|
||||||
messageOpts.files = [randomRefAttachment];
|
const refreshedUrl = await refreshCdnUrl(randomRefAttachment);
|
||||||
|
messageOpts.files = [refreshedUrl];
|
||||||
} else {
|
} else {
|
||||||
const randomMessage = await MarkovInputData.createQueryBuilder<
|
const randomMessage = await MarkovInputData.createQueryBuilder<
|
||||||
MarkovInputData<MarkovDataCustom>
|
MarkovInputData<MarkovDataCustom>
|
||||||
@@ -570,7 +589,9 @@ async function generateResponse(
|
|||||||
.getOne();
|
.getOne();
|
||||||
const randomMessageAttachmentUrls = randomMessage?.custom?.attachments;
|
const randomMessageAttachmentUrls = randomMessage?.custom?.attachments;
|
||||||
if (randomMessageAttachmentUrls?.length) {
|
if (randomMessageAttachmentUrls?.length) {
|
||||||
messageOpts.files = [{ attachment: getRandomElement(randomMessageAttachmentUrls) }];
|
const attachmentUrl = getRandomElement(randomMessageAttachmentUrls);
|
||||||
|
const refreshedUrl = await refreshCdnUrl(attachmentUrl);
|
||||||
|
messageOpts.files = [{ attachment: refreshedUrl }];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
messageOpts.content = response.string;
|
messageOpts.content = response.string;
|
||||||
@@ -606,52 +627,59 @@ async function listValidChannels(interaction: Discord.CommandInteraction): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getChannelsFromInteraction(
|
function getChannelsFromInteraction(
|
||||||
interaction: Discord.CommandInteraction
|
interaction: Discord.ChatInputCommandInteraction,
|
||||||
): Discord.TextChannel[] {
|
): Discord.TextChannel[] {
|
||||||
const channels = Array.from(Array(CHANNEL_OPTIONS_MAX).keys()).map((index) =>
|
const channels = Array.from(Array(CHANNEL_OPTIONS_MAX).keys()).map((index) =>
|
||||||
interaction.options.getChannel(`channel-${index + 1}`, index === 0)
|
interaction.options.getChannel(`channel-${index + 1}`, index === 0),
|
||||||
);
|
);
|
||||||
const textChannels = channels.filter(
|
const textChannels = channels.filter(
|
||||||
(c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel
|
(c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel,
|
||||||
);
|
);
|
||||||
return textChannels;
|
return textChannels;
|
||||||
}
|
}
|
||||||
|
|
||||||
function helpMessage(): AgnosticReplyOptions {
|
function helpMessage(): AgnosticReplyOptions {
|
||||||
const avatarURL = client.user.avatarURL() || undefined;
|
const avatarURL = client.user.avatarURL() || undefined;
|
||||||
const embed = new Discord.MessageEmbed()
|
const embed = new Discord.EmbedBuilder()
|
||||||
.setAuthor({
|
.setAuthor({
|
||||||
name: client.user.username || packageJson().name,
|
name: client.user.username || packageJson().name,
|
||||||
iconURL: avatarURL,
|
iconURL: avatarURL,
|
||||||
})
|
})
|
||||||
.setThumbnail(avatarURL as string)
|
.setThumbnail(avatarURL as string)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`A Markov chain chatbot that speaks based on learned messages from previous chat input.`
|
`A Markov chain chatbot that speaks based on learned messages from previous chat input.`,
|
||||||
)
|
|
||||||
.addField(
|
|
||||||
`${config.messageCommandPrefix} or /${messageCommand.name}`,
|
|
||||||
`Generates a sentence to say based on the chat database. Send your message as TTS to recieve it as TTS.`
|
|
||||||
)
|
|
||||||
.addField(
|
|
||||||
`/${listenChannelCommand.name}`,
|
|
||||||
`Add, remove, list, or modify the list of channels the bot listens to.`
|
|
||||||
)
|
|
||||||
.addField(
|
|
||||||
`${config.messageCommandPrefix} train or /${trainCommand.name}`,
|
|
||||||
`Fetches the maximum amount of previous messages in the listened to text channels. This takes some time.`
|
|
||||||
)
|
|
||||||
.addField(
|
|
||||||
`${config.messageCommandPrefix} invite or /${inviteCommand.name}`,
|
|
||||||
`Post this bot's invite URL.`
|
|
||||||
)
|
|
||||||
.addField(
|
|
||||||
`${config.messageCommandPrefix} debug or /${messageCommand.name} debug: True`,
|
|
||||||
`Runs the ${config.messageCommandPrefix} command and follows it up with debug info.`
|
|
||||||
)
|
|
||||||
.addField(
|
|
||||||
`${config.messageCommandPrefix} tts or /${messageCommand.name} tts: True`,
|
|
||||||
`Runs the ${config.messageCommandPrefix} command and reads it with text-to-speech.`
|
|
||||||
)
|
)
|
||||||
|
.addFields([
|
||||||
|
{
|
||||||
|
name: `${config.messageCommandPrefix} or /${messageCommand.name}`,
|
||||||
|
value: `Generates a sentence to say based on the chat database. Send your message as TTS to recieve it as TTS.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: `/${listenChannelCommand.name}`,
|
||||||
|
value: `Add, remove, list, or modify the list of channels the bot listens to.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: `${config.messageCommandPrefix} train or /${trainCommand.name}`,
|
||||||
|
value: `Fetches the maximum amount of previous messages in the listened to text channels. This takes some time.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: `${config.messageCommandPrefix} invite or /${inviteCommand.name}`,
|
||||||
|
value: `Post this bot's invite URL.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: `${config.messageCommandPrefix} debug or /${messageCommand.name} debug: True`,
|
||||||
|
value: `Runs the ${config.messageCommandPrefix} command and follows it up with debug info.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: `${config.messageCommandPrefix} tts or /${messageCommand.name} tts: True`,
|
||||||
|
value: `Runs the ${config.messageCommandPrefix} command and reads it with text-to-speech.`,
|
||||||
|
},
|
||||||
|
])
|
||||||
.setFooter({
|
.setFooter({
|
||||||
text: `${packageJson().name} ${getVersion()} by ${
|
text: `${packageJson().name} ${getVersion()} by ${
|
||||||
(packageJson().author as PackageJsonPerson).name
|
(packageJson().author as PackageJsonPerson).name
|
||||||
@@ -664,13 +692,13 @@ function helpMessage(): AgnosticReplyOptions {
|
|||||||
|
|
||||||
function generateInviteUrl(): string {
|
function generateInviteUrl(): string {
|
||||||
return client.generateInvite({
|
return client.generateInvite({
|
||||||
scopes: ['bot', 'applications.commands'],
|
scopes: [Discord.OAuth2Scopes.Bot, Discord.OAuth2Scopes.ApplicationsCommands],
|
||||||
permissions: [
|
permissions: [
|
||||||
'VIEW_CHANNEL',
|
'ViewChannel',
|
||||||
'SEND_MESSAGES',
|
'SendMessages',
|
||||||
'SEND_TTS_MESSAGES',
|
'SendTTSMessages',
|
||||||
'ATTACH_FILES',
|
'AttachFiles',
|
||||||
'READ_MESSAGE_HISTORY',
|
'ReadMessageHistory',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -678,16 +706,18 @@ function generateInviteUrl(): string {
|
|||||||
function inviteMessage(): AgnosticReplyOptions {
|
function inviteMessage(): AgnosticReplyOptions {
|
||||||
const avatarURL = client.user.avatarURL() || undefined;
|
const avatarURL = client.user.avatarURL() || undefined;
|
||||||
const inviteUrl = generateInviteUrl();
|
const inviteUrl = generateInviteUrl();
|
||||||
const embed = new Discord.MessageEmbed()
|
const embed = new Discord.EmbedBuilder()
|
||||||
.setAuthor({ name: `Invite ${client.user?.username}`, iconURL: avatarURL })
|
.setAuthor({ name: `Invite ${client.user?.username}`, iconURL: avatarURL })
|
||||||
.setThumbnail(avatarURL as string)
|
.setThumbnail(avatarURL as string)
|
||||||
.addField('Invite', `[Invite ${client.user.username} to your server](${inviteUrl})`);
|
.addFields([
|
||||||
|
{ name: 'Invite', value: `[Invite ${client.user.username} to your server](${inviteUrl})` },
|
||||||
|
]);
|
||||||
return { embeds: [embed] };
|
return { embeds: [embed] };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleResponseMessage(
|
async function handleResponseMessage(
|
||||||
generatedResponse: GenerateResponse,
|
generatedResponse: GenerateResponse,
|
||||||
message: Discord.Message
|
message: Discord.Message,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (generatedResponse.message) await message.reply(generatedResponse.message);
|
if (generatedResponse.message) await message.reply(generatedResponse.message);
|
||||||
if (generatedResponse.debug) await message.reply(generatedResponse.debug);
|
if (generatedResponse.debug) await message.reply(generatedResponse.debug);
|
||||||
@@ -696,7 +726,7 @@ async function handleResponseMessage(
|
|||||||
|
|
||||||
async function handleUnprivileged(
|
async function handleUnprivileged(
|
||||||
interaction: Discord.CommandInteraction | Discord.SelectMenuInteraction,
|
interaction: Discord.CommandInteraction | Discord.SelectMenuInteraction,
|
||||||
deleteReply = true
|
deleteReply = true,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (deleteReply) await interaction.deleteReply();
|
if (deleteReply) await interaction.deleteReply();
|
||||||
await interaction.followUp({ content: INVALID_PERMISSIONS_MESSAGE, ephemeral: true });
|
await interaction.followUp({ content: INVALID_PERMISSIONS_MESSAGE, ephemeral: true });
|
||||||
@@ -704,7 +734,7 @@ async function handleUnprivileged(
|
|||||||
|
|
||||||
async function handleNoGuild(
|
async function handleNoGuild(
|
||||||
interaction: Discord.CommandInteraction | Discord.SelectMenuInteraction,
|
interaction: Discord.CommandInteraction | Discord.SelectMenuInteraction,
|
||||||
deleteReply = true
|
deleteReply = true,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (deleteReply) await interaction.deleteReply();
|
if (deleteReply) await interaction.deleteReply();
|
||||||
await interaction.followUp({ content: INVALID_GUILD_MESSAGE, ephemeral: true });
|
await interaction.followUp({ content: INVALID_GUILD_MESSAGE, ephemeral: true });
|
||||||
@@ -820,7 +850,7 @@ client.on('threadDelete', async (thread) => {
|
|||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
client.on('interactionCreate', async (interaction) => {
|
client.on('interactionCreate', async (interaction) => {
|
||||||
if (interaction.isCommand()) {
|
if (interaction.isChatInputCommand()) {
|
||||||
L.info({ command: interaction.commandName }, 'Recieved slash command');
|
L.info({ command: interaction.commandName }, 'Recieved slash command');
|
||||||
|
|
||||||
if (interaction.commandName === helpCommand.name) {
|
if (interaction.commandName === helpCommand.name) {
|
||||||
@@ -869,7 +899,7 @@ client.on('interactionCreate', async (interaction) => {
|
|||||||
const channels = getChannelsFromInteraction(interaction);
|
const channels = getChannelsFromInteraction(interaction);
|
||||||
await addValidChannels(channels, interaction.guildId);
|
await addValidChannels(channels, interaction.guildId);
|
||||||
await interaction.editReply(
|
await interaction.editReply(
|
||||||
`Added ${channels.length} text channels to the list. Use \`/train\` to update the past known messages.`
|
`Added ${channels.length} text channels to the list. Use \`/train\` to update the past known messages.`,
|
||||||
);
|
);
|
||||||
} else if (subCommand === 'remove') {
|
} else if (subCommand === 'remove') {
|
||||||
if (!isModerator(interaction.member)) {
|
if (!isModerator(interaction.member)) {
|
||||||
@@ -881,7 +911,7 @@ client.on('interactionCreate', async (interaction) => {
|
|||||||
const channels = getChannelsFromInteraction(interaction);
|
const channels = getChannelsFromInteraction(interaction);
|
||||||
await removeValidChannels(channels, interaction.guildId);
|
await removeValidChannels(channels, interaction.guildId);
|
||||||
await interaction.editReply(
|
await interaction.editReply(
|
||||||
`Removed ${channels.length} text channels from the list. Use \`/train\` to remove these channels from the past known messages.`
|
`Removed ${channels.length} text channels from the list. Use \`/train\` to remove these channels from the past known messages.`,
|
||||||
);
|
);
|
||||||
} else if (subCommand === 'modify') {
|
} else if (subCommand === 'modify') {
|
||||||
if (!interaction.guild) {
|
if (!interaction.guild) {
|
||||||
@@ -892,8 +922,8 @@ client.on('interactionCreate', async (interaction) => {
|
|||||||
}
|
}
|
||||||
await interaction.deleteReply();
|
await interaction.deleteReply();
|
||||||
const dbTextChannels = await getTextChannels(interaction.guild);
|
const dbTextChannels = await getTextChannels(interaction.guild);
|
||||||
const row = new Discord.MessageActionRow().addComponents(
|
const row = new Discord.ActionRowBuilder<Discord.StringSelectMenuBuilder>().addComponents(
|
||||||
new Discord.MessageSelectMenu()
|
new Discord.StringSelectMenuBuilder()
|
||||||
.setCustomId('listen-modify-select')
|
.setCustomId('listen-modify-select')
|
||||||
.setPlaceholder('Nothing selected')
|
.setPlaceholder('Nothing selected')
|
||||||
.setMinValues(0)
|
.setMinValues(0)
|
||||||
@@ -903,8 +933,8 @@ client.on('interactionCreate', async (interaction) => {
|
|||||||
label: `#${c.name}` || c.id,
|
label: `#${c.name}` || c.id,
|
||||||
value: c.id,
|
value: c.id,
|
||||||
default: c.listen || false,
|
default: c.listen || false,
|
||||||
}))
|
})),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await interaction.followUp({
|
await interaction.followUp({
|
||||||
@@ -919,7 +949,7 @@ client.on('interactionCreate', async (interaction) => {
|
|||||||
const trainingJSON = interaction.options.getAttachment('json');
|
const trainingJSON = interaction.options.getAttachment('json');
|
||||||
|
|
||||||
if (trainingJSON) {
|
if (trainingJSON) {
|
||||||
const responseMessage = await trainFromAttachmentJson(trainingJSON, interaction, clean);
|
const responseMessage = await trainFromAttachmentJson(trainingJSON.url, interaction, clean);
|
||||||
await interaction.followUp(responseMessage);
|
await interaction.followUp(responseMessage);
|
||||||
} else {
|
} else {
|
||||||
const reply = (await interaction.fetchReply()) as Discord.Message; // Must fetch the reply ASAP
|
const reply = (await interaction.fetchReply()) as Discord.Message; // Must fetch the reply ASAP
|
||||||
@@ -928,7 +958,7 @@ client.on('interactionCreate', async (interaction) => {
|
|||||||
await reply.reply({ content: responseMessage });
|
await reply.reply({ content: responseMessage });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (interaction.isSelectMenu()) {
|
} else if (interaction.isStringSelectMenu()) {
|
||||||
if (interaction.customId === 'listen-modify-select') {
|
if (interaction.customId === 'listen-modify-select') {
|
||||||
await interaction.deferUpdate();
|
await interaction.deferUpdate();
|
||||||
const { guild } = interaction;
|
const { guild } = interaction;
|
||||||
@@ -940,14 +970,15 @@ client.on('interactionCreate', async (interaction) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const allChannels =
|
const allChannels =
|
||||||
(interaction.component as APISelectMenuComponent).options?.map((o) => o.value) || [];
|
(interaction.component as Discord.StringSelectMenuComponent).options?.map((o) => o.value) ||
|
||||||
|
[];
|
||||||
const selectedChannelIds = interaction.values;
|
const selectedChannelIds = interaction.values;
|
||||||
|
|
||||||
const textChannels = (
|
const textChannels = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
allChannels.map(async (c) => {
|
allChannels.map(async (c) => {
|
||||||
return guild.channels.fetch(c);
|
return guild.channels.fetch(c);
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
).filter((c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel);
|
).filter((c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel);
|
||||||
const unselectedChannels = textChannels.filter((t) => !selectedChannelIds.includes(t.id));
|
const unselectedChannels = textChannels.filter((t) => !selectedChannelIds.includes(t.id));
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const logger = pino(
|
|||||||
},
|
},
|
||||||
PinoPretty({
|
PinoPretty({
|
||||||
translateTime: `SYS:standard`,
|
translateTime: `SYS:standard`,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export default logger;
|
export default logger;
|
||||||
|
|||||||
Reference in New Issue
Block a user