Compare commits
6 commits
965e0a2602
...
ae3a5133b3
Author | SHA1 | Date | |
---|---|---|---|
ae3a5133b3 | |||
8b4b35454b | |||
56a0e686b0 | |||
28dce0b29f | |||
f6ac5281e7 | |||
cb2ae4d4f2 |
10 changed files with 269 additions and 46 deletions
21
package-lock.json
generated
21
package-lock.json
generated
|
@ -13,10 +13,12 @@
|
||||||
"discord.js": "^14.8.0",
|
"discord.js": "^14.8.0",
|
||||||
"fold-to-ascii": "^5.0.1",
|
"fold-to-ascii": "^5.0.1",
|
||||||
"gpt-3-encoder": "^1.1.4",
|
"gpt-3-encoder": "^1.1.4",
|
||||||
"openai": "^3.2.1"
|
"openai": "^3.2.1",
|
||||||
|
"require-directory": "^2.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fold-to-ascii": "^5.0.0",
|
"@types/fold-to-ascii": "^5.0.0",
|
||||||
|
"@types/require-directory": "^2.1.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
||||||
"@typescript-eslint/parser": "^5.55.0",
|
"@typescript-eslint/parser": "^5.55.0",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
|
@ -295,6 +297,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.2.tgz",
|
||||||
"integrity": "sha512-sDPHm2wfx2QhrMDK0pOt2J4KLJMAcerqWNvnED0itPRJWvI+bK+uNHzcH1dFsBlf7G3u8tqXmRF3wkvL9yUwMw=="
|
"integrity": "sha512-sDPHm2wfx2QhrMDK0pOt2J4KLJMAcerqWNvnED0itPRJWvI+bK+uNHzcH1dFsBlf7G3u8tqXmRF3wkvL9yUwMw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/require-directory": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/require-directory/-/require-directory-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-FUG5PJ2rsV2TssSspVZefTR8+wH3Ahr6KdAB6WanLNroSDu0A5ew4WVUxnnGU/E1k6nzip9ZawGAAe/ZqzGn5g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.3.13",
|
"version": "7.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
|
||||||
|
@ -1710,6 +1721,14 @@
|
||||||
"url": "https://github.com/sponsors/Borewit"
|
"url": "https://github.com/sponsors/Borewit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve-from": {
|
"node_modules/resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "tsc && node dist/index.js",
|
"start": "tsc && node dist/index.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"publishCommands": "tsc && node dist/scripts/pushCommands.js"
|
||||||
},
|
},
|
||||||
"author": "Wroclaw",
|
"author": "Wroclaw",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -14,10 +15,12 @@
|
||||||
"discord.js": "^14.8.0",
|
"discord.js": "^14.8.0",
|
||||||
"fold-to-ascii": "^5.0.1",
|
"fold-to-ascii": "^5.0.1",
|
||||||
"gpt-3-encoder": "^1.1.4",
|
"gpt-3-encoder": "^1.1.4",
|
||||||
"openai": "^3.2.1"
|
"openai": "^3.2.1",
|
||||||
|
"require-directory": "^2.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fold-to-ascii": "^5.0.0",
|
"@types/fold-to-ascii": "^5.0.0",
|
||||||
|
"@types/require-directory": "^2.1.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
||||||
"@typescript-eslint/parser": "^5.55.0",
|
"@typescript-eslint/parser": "^5.55.0",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
|
|
41
src/command.ts
Normal file
41
src/command.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { PermissionsBitField } from "discord.js";
|
||||||
|
import { RESTPostAPIApplicationCommandsJSONBody } from "discord.js";
|
||||||
|
import { ApplicationCommandOption, ApplicationCommandType, ChatInputCommandInteraction, LocalizationMap, MessageInteraction, PermissionResolvable, UserSelectMenuInteraction } from "discord.js";
|
||||||
|
|
||||||
|
type InteractionTypeMap = {
|
||||||
|
[ApplicationCommandType.ChatInput]: [ChatInputCommandInteraction, string];
|
||||||
|
[ApplicationCommandType.Message]: [MessageInteraction, never];
|
||||||
|
[ApplicationCommandType.User]: [UserSelectMenuInteraction, never];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Command<Type extends keyof InteractionTypeMap = ApplicationCommandType> {
|
||||||
|
readonly name: string;
|
||||||
|
readonly name_localizations?: LocalizationMap;
|
||||||
|
readonly description: InteractionTypeMap[Type][1];
|
||||||
|
readonly description_localizations?: LocalizationMap;
|
||||||
|
readonly options?: ApplicationCommandOption[];
|
||||||
|
readonly default_member_permissions?: PermissionResolvable;
|
||||||
|
readonly type: Type;
|
||||||
|
readonly nsfw?: boolean;
|
||||||
|
readonly dm_permission?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Command<Type extends keyof InteractionTypeMap = ApplicationCommandType> {
|
||||||
|
abstract execute(interaction: InteractionTypeMap[Type][0]): Promise<void>;
|
||||||
|
|
||||||
|
toRESTPostApplicationCommands(): RESTPostAPIApplicationCommandsJSONBody {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
name_localizations: this.name_localizations,
|
||||||
|
description: this.description,
|
||||||
|
description_localizations: this.description_localizations,
|
||||||
|
options: this.options,
|
||||||
|
default_member_permissions: this.default_member_permissions !== undefined ? new PermissionsBitField(this.default_member_permissions).bitfield.toString() : undefined,
|
||||||
|
type: this.type,
|
||||||
|
nsfw: this.nsfw,
|
||||||
|
dm_permission: this.dm_permission,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Command;
|
16
src/commands/summon.ts
Normal file
16
src/commands/summon.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { ApplicationCommandType, ChatInputCommandInteraction } from "discord.js";
|
||||||
|
|
||||||
|
import Command from "../command";
|
||||||
|
import { queueRequest } from "../execution";
|
||||||
|
|
||||||
|
|
||||||
|
export default class Summon extends Command {
|
||||||
|
name = "summon";
|
||||||
|
description = "Summons a bot to reply in chat without sending any message";
|
||||||
|
type = ApplicationCommandType.ChatInput;
|
||||||
|
dm_permission = false;
|
||||||
|
|
||||||
|
async execute(interaction: ChatInputCommandInteraction) {
|
||||||
|
queueRequest(interaction);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,16 +5,20 @@ import Moderation from "./moderation";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import toOpenAIMessages from "./toOpenAIMessages";
|
import toOpenAIMessages from "./toOpenAIMessages";
|
||||||
|
|
||||||
|
type NonNullableInObject<T, V> = { [k in keyof T]: k extends V ? NonNullable<T[k]> : T[k] };
|
||||||
|
type apiRequest = DiscordApi.Message | DiscordApi.RepliableInteraction;
|
||||||
|
export type request = apiRequest & NonNullableInObject<apiRequest, "channel" | "channelId">;
|
||||||
|
|
||||||
/** Stores the queue requests on the channels. */
|
/** Stores the queue requests on the channels. */
|
||||||
const channelsRunning: DiscordApi.Collection<string, (DiscordApi.Message | DiscordApi.Interaction)[]> = new DiscordApi.Collection();
|
const channelsRunning: DiscordApi.Collection<string, request[]> = new DiscordApi.Collection();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the user that requested the execution
|
* Gets the user that requested the execution
|
||||||
* @param request The request to get the user from
|
* @param request The request to get the user from
|
||||||
* @returns the user or guild member
|
* @returns the user or guild member
|
||||||
*/
|
*/
|
||||||
export function getAuthor(request: DiscordApi.Message | DiscordApi.Interaction) {
|
export function getAuthor(request: apiRequest) {
|
||||||
if (request instanceof DiscordApi.Message) return request.member ?? request.author;
|
if (request instanceof DiscordApi.Message) return request.author;
|
||||||
return request.user;
|
return request.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,13 +54,41 @@ async function getUserLimit(user: string | {id: string}, requestTimestamp: Date)
|
||||||
return {limit: userLimits.limit, remaining: userLimits.limit - usedLimit};
|
return {limit: userLimits.limit, remaining: userLimits.limit - usedLimit};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replies to a request
|
||||||
|
* @param request the request to reply to
|
||||||
|
* @param message the message
|
||||||
|
* @param replyOptions additional options if the request is a message
|
||||||
|
* @param interactionOptions additional options if the request is an interaction
|
||||||
|
* @returns Promise of the done action
|
||||||
|
*/
|
||||||
|
function requestReply(
|
||||||
|
request: request,
|
||||||
|
message: DiscordApi.MessageReplyOptions & DiscordApi.InteractionReplyOptions,
|
||||||
|
// TODO: add support for these below
|
||||||
|
replyOptions: DiscordApi.MessageReplyOptions = {},
|
||||||
|
interactionOptions: DiscordApi.InteractionReplyOptions = {},
|
||||||
|
) {
|
||||||
|
if (request instanceof DiscordApi.Message) {
|
||||||
|
return request.reply(Object.assign(message, replyOptions));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!request.deferred)
|
||||||
|
return request.reply(Object.assign(message, interactionOptions));
|
||||||
|
else
|
||||||
|
return request.editReply(Object.assign(message, interactionOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check and queues up the request and runs it if there is nothing in queue.
|
* Check and queues up the request and runs it if there is nothing in queue.
|
||||||
* @param request the message to check and queue
|
* @param request the message to check and queue
|
||||||
*/
|
*/
|
||||||
export async function queueRequest(request: DiscordApi.Message | DiscordApi.Interaction) {
|
export async function queueRequest(request: apiRequest) {
|
||||||
if (!request.channelId) {
|
if (!request.channelId) {
|
||||||
if (!(request instanceof DiscordApi.Message) && request.isRepliable())
|
if (request instanceof DiscordApi.Message)
|
||||||
|
request.reply("request does not have channelId");
|
||||||
|
else if (request.isRepliable())
|
||||||
request.reply("request does not have channelId");
|
request.reply("request does not have channelId");
|
||||||
console.log("There was incoming execution without channelId set, ignoring");
|
console.log("There was incoming execution without channelId set, ignoring");
|
||||||
console.log(request);
|
console.log(request);
|
||||||
|
@ -90,7 +122,7 @@ export async function queueRequest(request: DiscordApi.Message | DiscordApi.Inte
|
||||||
() => { return []; },
|
() => { return []; },
|
||||||
);
|
);
|
||||||
const shouldStart = messagesForChannel.length == 0;
|
const shouldStart = messagesForChannel.length == 0;
|
||||||
messagesForChannel.push(request);
|
messagesForChannel.push(request as request);
|
||||||
if (shouldStart)
|
if (shouldStart)
|
||||||
executeFromQueue(request.channelId);
|
executeFromQueue(request.channelId);
|
||||||
}
|
}
|
||||||
|
@ -100,8 +132,8 @@ export async function queueRequest(request: DiscordApi.Message | DiscordApi.Inte
|
||||||
* @param channel the channel to run the queue for
|
* @param channel the channel to run the queue for
|
||||||
*/
|
*/
|
||||||
async function executeFromQueue(channel: string) {
|
async function executeFromQueue(channel: string) {
|
||||||
const channelQueue = channelsRunning.get(channel) as DiscordApi.Message[];
|
const channelQueue = channelsRunning.get(channel) as NonNullable<ReturnType<typeof channelsRunning.get>>;
|
||||||
const message = channelQueue.at(0) as DiscordApi.Message;
|
const message = channelQueue.at(0) as NonNullable<ReturnType<typeof channelQueue.at>>;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let messages: DiscordApi.Collection<string, DiscordApi.Message> = await message.channel.messages.fetch({ limit: config.limits.messages, cache: false });
|
let messages: DiscordApi.Collection<string, DiscordApi.Message> = await message.channel.messages.fetch({ limit: config.limits.messages, cache: false });
|
||||||
|
@ -110,7 +142,13 @@ async function executeFromQueue(channel: string) {
|
||||||
|
|
||||||
messages.forEach(m => Moderation.checkMessage(m));
|
messages.forEach(m => Moderation.checkMessage(m));
|
||||||
|
|
||||||
|
if (message instanceof DiscordApi.Message) {
|
||||||
message.channel.sendTyping();
|
message.channel.sendTyping();
|
||||||
|
}
|
||||||
|
else if (message.isRepliable()) {
|
||||||
|
message.deferReply();
|
||||||
|
}
|
||||||
|
|
||||||
const answer = await openai.createChatCompletion({
|
const answer = await openai.createChatCompletion({
|
||||||
...config.chatCompletionConfig,
|
...config.chatCompletionConfig,
|
||||||
messages: toOpenAIMessages(messages),
|
messages: toOpenAIMessages(messages),
|
||||||
|
@ -118,13 +156,13 @@ async function executeFromQueue(channel: string) {
|
||||||
|
|
||||||
const usage = answer.data.usage;
|
const usage = answer.data.usage;
|
||||||
if (usage != undefined) {
|
if (usage != undefined) {
|
||||||
const channelName: string = message.inGuild() ? `${message.channel.name} (${message.guild.name})` : `@${message.author.tag}`;
|
const channelName: string = !message.channel.isDMBased() ? `${message.channel.name} (${message.guild?.name})` : `@${getAuthor(message).tag}`;
|
||||||
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${message.author.tag} (${message.author.id}) in #${channelName}`);
|
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${getAuthor(message).tag} (${getAuthor(message).id}) in #${channelName}`);
|
||||||
|
|
||||||
database.usage.create({
|
database.usage.create({
|
||||||
data: {
|
data: {
|
||||||
timestamp: message.createdAt,
|
timestamp: message.createdAt,
|
||||||
user: BigInt(message.author.id),
|
user: BigInt(getAuthor(message).id),
|
||||||
channel: BigInt(message.channelId),
|
channel: BigInt(message.channelId),
|
||||||
guild: message.guildId ? BigInt(message.guildId) : null,
|
guild: message.guildId ? BigInt(message.guildId) : null,
|
||||||
usageReguest: usage.prompt_tokens,
|
usageReguest: usage.prompt_tokens,
|
||||||
|
@ -138,32 +176,30 @@ async function executeFromQueue(channel: string) {
|
||||||
|
|
||||||
const answerContent = answer.data.choices[0].message?.content;
|
const answerContent = answer.data.choices[0].message?.content;
|
||||||
|
|
||||||
if (answerContent != undefined && answerContent != "") {
|
if (answerContent != undefined || answerContent != "") {
|
||||||
const response = message.reply({
|
|
||||||
content: answerContent,
|
|
||||||
allowedMentions: {
|
|
||||||
repliedUser: false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Moderation.checkMessage(await response);
|
const response = requestReply(message, {content: answerContent}, {allowedMentions: { repliedUser: false }});
|
||||||
|
|
||||||
|
response.then(rval => Moderation.checkMessage(rval));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
message.react("😶");
|
if (message instanceof DiscordApi.Message) message.react("😶");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Error ocurred while handling chat completion request (${(e as object).constructor.name}):`);
|
console.error(`Error ocurred while handling chat completion request (${(e as object).constructor.name}):`);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
||||||
message.reply({
|
requestReply(
|
||||||
|
message,
|
||||||
|
{
|
||||||
embeds: [{
|
embeds: [{
|
||||||
color: 0xff0000,
|
color: 0xff0000,
|
||||||
description: "Something bad happened! :frowning:"
|
description: "Something bad happened! :frowning:"
|
||||||
}],
|
}],
|
||||||
allowedMentions: {
|
},
|
||||||
repliedUser: false,
|
{allowedMentions: { repliedUser: false } },
|
||||||
}
|
{ ephemeral: true },
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
channelQueue.shift();
|
channelQueue.shift();
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import { queueRequest } from "./execution";
|
import { queueRequest } from "./execution";
|
||||||
|
import InteractionManager from "./interactionManager";
|
||||||
|
|
||||||
const discord = new DiscordApi.Client({
|
const discord = new DiscordApi.Client({
|
||||||
intents: [
|
intents: [
|
||||||
|
@ -19,6 +20,9 @@ export const openai = new OpenAIApi(new OpenAIApiConfiguration({
|
||||||
|
|
||||||
export const database = new PrismaClient();
|
export const database = new PrismaClient();
|
||||||
|
|
||||||
|
const interactionManager = new InteractionManager();
|
||||||
|
interactionManager.bindClient(discord);
|
||||||
|
|
||||||
discord.on("ready", async event => {
|
discord.on("ready", async event => {
|
||||||
console.log(`Connected to Discord as ${event.user.tag} (${event.user.id})`);
|
console.log(`Connected to Discord as ${event.user.tag} (${event.user.id})`);
|
||||||
});
|
});
|
||||||
|
@ -30,4 +34,4 @@ discord.on("messageCreate", async message => {
|
||||||
queueRequest(message);
|
queueRequest(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
discord.login(config.tokens.Discord);
|
if (require.main === module) discord.login(config.tokens.Discord);
|
||||||
|
|
40
src/interactionManager.ts
Normal file
40
src/interactionManager.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Interaction, Client as DiscordClient } from "discord.js";
|
||||||
|
import requireDirectory from "require-directory";
|
||||||
|
|
||||||
|
import Command from "./command";
|
||||||
|
|
||||||
|
export default class CommandManager {
|
||||||
|
readonly commands: Command[] = [];
|
||||||
|
|
||||||
|
constructor(directory = "./commands") {
|
||||||
|
const files = requireDirectory(module, directory);
|
||||||
|
for (const i in files ) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
this.commands.push(new (files[i].default as Command)());
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(`Failed to construct command ${i} (${typeof e}):`);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onInteraction(interaction: Interaction) {
|
||||||
|
if (
|
||||||
|
interaction.isChatInputCommand() ||
|
||||||
|
interaction.isMessageContextMenuCommand() ||
|
||||||
|
interaction.isUserContextMenuCommand()
|
||||||
|
) {
|
||||||
|
const foundCommand = this.commands.find((command) => command.name == interaction.commandName );
|
||||||
|
if (!foundCommand) throw new Error(`Unknown command received (${interaction.commandName}). Did you forgot to push updated commands?`);
|
||||||
|
foundCommand.execute(interaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindClient(client: DiscordClient) {
|
||||||
|
client.on("interactionCreate", (e) => this.onInteraction(e));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,31 +1,39 @@
|
||||||
import { Collection, Message } from "discord.js";
|
import { Collection, InteractionResponse, Message } from "discord.js";
|
||||||
import { openai } from "./index";
|
import { openai } from "./index";
|
||||||
import { formatMessage } from "./toOpenAIMessages";
|
import { formatRequestOrResponse } from "./toOpenAIMessages";
|
||||||
|
|
||||||
export default class Moderation {
|
export default class Moderation {
|
||||||
/** Represents cache of messages that have been checked aganist OpenAI moderation API. */
|
/** Represents cache of messages that have been checked aganist OpenAI moderation API. */
|
||||||
private static cache = new Collection<string, boolean>();
|
private static cache = new Collection<string, boolean>();
|
||||||
|
|
||||||
public static async checkMessage(message: Message): Promise<boolean> {
|
public static async checkMessage(message: Message | InteractionResponse): Promise<boolean> {
|
||||||
if (this.cache.has(message.id)) {
|
if (this.cache.has(message.id)) {
|
||||||
return this.cache.get(message.id) as boolean;
|
return this.cache.get(message.id) as boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message instanceof Message) {
|
||||||
const warningReaction = message.reactions.resolve("⚠");
|
const warningReaction = message.reactions.resolve("⚠");
|
||||||
// if bot reacted to that message, we already know that it returned true for moderation API
|
// if bot reacted to that message, we already know that it returned true for moderation API
|
||||||
if (warningReaction && warningReaction.me) {
|
if (warningReaction && warningReaction.me) {
|
||||||
this.cache.set(message.id, true);
|
this.cache.set(message.id, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const answer = await openai.createModeration({
|
const answer = await openai.createModeration({
|
||||||
input: formatMessage(message),
|
input: await formatRequestOrResponse(message),
|
||||||
});
|
});
|
||||||
|
|
||||||
const flagged = answer.data.results[0].flagged;
|
const flagged = answer.data.results[0].flagged;
|
||||||
this.cache.set(message.id, flagged);
|
this.cache.set(message.id, flagged);
|
||||||
if (flagged) message.react("⚠");
|
if (flagged) if (message instanceof Message) {
|
||||||
|
message.react("⚠");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const channelMessage = await message.fetch();
|
||||||
|
channelMessage.react("⚠");
|
||||||
|
}
|
||||||
|
|
||||||
return flagged;
|
return flagged;
|
||||||
}
|
}
|
||||||
|
|
40
src/scripts/pushCommands.ts
Normal file
40
src/scripts/pushCommands.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// https://discordjs.guide/creating-your-bot/command-deployment.html#guild-commands
|
||||||
|
|
||||||
|
import { REST, RESTGetAPIOAuth2CurrentApplicationResult, RESTPostAPIApplicationCommandsJSONBody, Routes } from "discord.js";
|
||||||
|
import config from "../config";
|
||||||
|
import requireDirectory from "require-directory";
|
||||||
|
|
||||||
|
import Command from "../command";
|
||||||
|
|
||||||
|
const post: RESTPostAPIApplicationCommandsJSONBody[] = [];
|
||||||
|
|
||||||
|
const guildId = process.argv.slice(2)[0];
|
||||||
|
requireDirectory<{default: Command}, void>(module, "../commands", {
|
||||||
|
visit: function (obj) {
|
||||||
|
console.log(obj);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
post.push(new obj.default().toRESTPostApplicationCommands());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const rest = new REST().setToken(config.tokens.Discord);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const me = await rest.get(Routes.oauth2CurrentApplication()) as RESTGetAPIOAuth2CurrentApplicationResult;
|
||||||
|
console.log(`Started refreshing ${post.length} application commands.`);
|
||||||
|
if (guildId && guildId != "")
|
||||||
|
await rest.put(
|
||||||
|
Routes.applicationGuildCommands(me.id, guildId),
|
||||||
|
{ body: post },
|
||||||
|
);
|
||||||
|
else {
|
||||||
|
await rest.put(
|
||||||
|
Routes.applicationCommands(me.id),
|
||||||
|
{ body: post },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log("Refreshed successfully");
|
||||||
|
})().catch( e => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
|
@ -1,9 +1,25 @@
|
||||||
import { ChatCompletionRequestMessage as OpenAIMessage } from "openai";
|
import { ChatCompletionRequestMessage as OpenAIMessage } from "openai";
|
||||||
import { Collection, Message as DiscordMessage } from "discord.js";
|
import { Collection, Message as DiscordMessage, InteractionResponse } from "discord.js";
|
||||||
import FoldToAscii from "fold-to-ascii";
|
import FoldToAscii from "fold-to-ascii";
|
||||||
|
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import countTokens from "./tokenCounter";
|
import countTokens from "./tokenCounter";
|
||||||
|
import { request } from "./execution";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* formats the request to use as a message contend in OpenAI api
|
||||||
|
* @param request the request to format
|
||||||
|
* @returns the formatted request
|
||||||
|
*/
|
||||||
|
export async function formatRequestOrResponse(request: request | InteractionResponse): Promise<string> {
|
||||||
|
if (request instanceof DiscordMessage) {
|
||||||
|
return formatMessage(request);
|
||||||
|
}
|
||||||
|
if (request instanceof InteractionResponse) {
|
||||||
|
return formatMessage(await request.fetch());
|
||||||
|
}
|
||||||
|
return formatMessage(await request.fetchReply());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats the message to use as a message content in OpenAI api
|
* Formats the message to use as a message content in OpenAI api
|
||||||
|
|
Loading…
Reference in a new issue