added two new commands

This commit is contained in:
Lukian LEIZOUR 2024-03-17 13:17:44 +01:00
parent 2901c12bb9
commit 8da6a759a6
12 changed files with 271 additions and 194 deletions

View file

@ -1,7 +1,7 @@
import type { Client } from 'discord.js' import type { Client } from 'discord.js'
declare module 'discord.js' { declare module 'discord.js' {
export interface Client extends Client { export interface Client extends Client {
commands: Collection<unknown, any> commands: Collection<unknown, any>
} }
} }

View file

@ -0,0 +1,11 @@
import { SlashCommandBuilder, CommandInteraction } from "discord.js";
import { helpEmbed } from "../../libs/discord.js";
export default {
data: new SlashCommandBuilder()
.setName("help")
.setDescription("Send the help message"),
async execute(interaction: CommandInteraction) {
await interaction.reply({ embeds: [ helpEmbed() ] });
},
};

View file

@ -1,10 +1,10 @@
import { SlashCommandBuilder, CommandInteraction } from "discord.js"; import { SlashCommandBuilder, CommandInteraction } from "discord.js";
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName("ping") .setName("ping")
.setDescription("Replies with Pong!"), .setDescription("Replies with Pong!"),
async execute(interaction: CommandInteraction) { async execute(interaction: CommandInteraction) {
await interaction.reply("Pong!"); await interaction.reply("Pong!");
}, },
}; };

View file

@ -0,0 +1,25 @@
import { SlashCommandBuilder, CommandInteraction } from "discord.js";
import { connectToDb, getUser } from "../../libs/mysql.js";
import { errorEmbed, quotaEmbed } from "../../libs/discord.js";
export default {
data: new SlashCommandBuilder()
.setName("quota")
.setDescription("Send you quota")
.setDMPermission(false),
async execute(interaction: CommandInteraction) {
await interaction.deferReply();
const connection = await connectToDb();
const user: any[] = await getUser(connection, interaction.member?.user.id ? interaction.member?.user.id : "");
connection.end();
if (!user[0]) {
return interaction.editReply({ embeds: [ errorEmbed("Try asking something to the bot before requesting your quota.") ] });
}
interaction.editReply({ embeds: [ quotaEmbed(user[0].quota) ] });
},
};

View file

@ -3,53 +3,53 @@ import { getChatResponse, MistralMessage, Models, InputPrice, OutputPrice, Retur
import { User, connectToDb, addUser, getUser, incrementQuota } from "../../libs/mysql.js"; import { User, connectToDb, addUser, getUser, incrementQuota } from "../../libs/mysql.js";
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName("ask") .setName("ask")
.setDescription("Make a single request to mistral API") .setDescription("Make a single request to mistral API")
.setDMPermission(false) .setDMPermission(false)
.addStringOption(option => .addStringOption(option =>
option.setName("prompt").setDescription("The prompt to send to the API").setRequired(true) option.setName("prompt").setDescription("The prompt to send to the API").setRequired(true)
), ),
async execute(interaction: ChatInputCommandInteraction) { async execute(interaction: ChatInputCommandInteraction) {
if (interaction.member?.user.id == undefined) { if (interaction.member?.user.id == undefined) {
return; return;
} }
await interaction.deferReply(); await interaction.deferReply();
const connection = await connectToDb(); const connection = await connectToDb();
var user: User[] = await getUser(connection, interaction.member?.user.id); var user: User[] = await getUser(connection, interaction.member?.user.id);
if (!user[0]) { if (!user[0]) {
await addUser(connection, interaction.member?.user.username, interaction.member?.user.id); await addUser(connection, interaction.member?.user.username, interaction.member?.user.id);
user = await getUser(connection, interaction.member?.user.id); user = await getUser(connection, interaction.member?.user.id);
} }
if (user[0].quota > 0.4) { if (user[0].quota > 0.4) {
interaction.editReply("You have exceed your quota.") interaction.editReply("You have exceed your quota.")
connection.end(); connection.end();
return; return;
} }
const prompt: string | null = interaction.options.getString("prompt"); const prompt: string | null = interaction.options.getString("prompt");
const messages: MistralMessage[] = [ const messages: MistralMessage[] = [
{ {
role: "system", role: "system",
content: Prompts.default content: Prompts.default
}, },
{ {
role: "user", role: "user",
content: prompt ? prompt : "" content: prompt ? prompt : ""
}, },
] ]
const response: ReturnedValue = await getChatResponse(messages, Models.multi_tiny); const response: ReturnedValue = await getChatResponse(messages, Models.multi_tiny);
await incrementQuota(connection, interaction.member?.user.id, InputPrice.multi_tiny * response.promptUsage + OutputPrice.multi_tiny * response.responseUsage); await incrementQuota(connection, interaction.member?.user.id, InputPrice.multi_tiny * response.promptUsage + OutputPrice.multi_tiny * response.responseUsage);
connection.end(); connection.end();
interaction.editReply(response.message); interaction.editReply(response.message);
}, },
}; };

View file

@ -1,21 +1,21 @@
import { Events, Interaction } from "discord.js"; import { Events, Interaction } from "discord.js";
export default { export default {
name: Events.InteractionCreate, name: Events.InteractionCreate,
async execute(interaction: Interaction) { async execute(interaction: Interaction) {
if (interaction.isChatInputCommand()) { if (interaction.isChatInputCommand()) {
const command = interaction.client.commands.get(interaction.commandName); const command = interaction.client.commands.get(interaction.commandName);
if (!command) { if (!command) {
return console.error(`No command matching ${interaction.commandName} was found.`); return console.error(`No command matching ${interaction.commandName} was found.`);
} }
try { try {
await command.execute(interaction); await command.execute(interaction);
} }
catch (error) { catch (error) {
console.error(error); console.error(error);
} }
} }
}, },
}; };

View file

@ -4,36 +4,36 @@ import { User, connectToDb, addUser, getUser, incrementQuota } from "../libs/mys
import { getMessages } from "../libs/discord.js"; import { getMessages } from "../libs/discord.js";
export default { export default {
name: Events.MessageCreate, name: Events.MessageCreate,
async execute(message: Message) { async execute(message: Message) {
if (!message.guildId && message.author.id != process.env.BOT_ID) { if (!message.guildId && message.author.id != process.env.BOT_ID) {
const prompt: string = message.content; const prompt: string = message.content;
const connection = await connectToDb(); const connection = await connectToDb();
var user: User[] = await getUser(connection, message.author.id); var user: User[] = await getUser(connection, message.author.id);
if (!user[0]) { if (!user[0]) {
await addUser(connection, message.author.username, message.author.id); await addUser(connection, message.author.username, message.author.id);
user = await getUser(connection, message.author.id); user = await getUser(connection, message.author.id);
} }
if (user[0].quota > 0.4) { if (user[0].quota > 0.4) {
message.reply("You have exceed your quota.") message.reply("You have exceed your quota.")
connection.end(); connection.end();
return; return;
} }
const messages: MistralMessage[] = await getMessages(message, message.channelId, message.author.id); const messages: MistralMessage[] = await getMessages(message, message.channelId, message.author.id);
await message.channel.sendTyping(); await message.channel.sendTyping();
const response: ReturnedValue = await getChatResponse(messages, Models.multi_tiny); const response: ReturnedValue = await getChatResponse(messages, Models.multi_tiny);
await incrementQuota(connection, message.author.id, InputPrice.multi_tiny * response.promptUsage + OutputPrice.multi_tiny * response.responseUsage); await incrementQuota(connection, message.author.id, InputPrice.multi_tiny * response.promptUsage + OutputPrice.multi_tiny * response.responseUsage);
connection.end(); connection.end();
message.reply(response.message); message.reply(response.message);
} }
}, },
}; };

View file

@ -2,19 +2,19 @@ import { Events, Client } from "discord.js";
import { checkReset } from "../libs/quotaReset.js"; import { checkReset } from "../libs/quotaReset.js";
export default { export default {
name: Events.ClientReady, name: Events.ClientReady,
once: true, once: true,
execute(client: Client) { execute(client: Client) {
console.log(`Ready! Logged in as ${client.user?.tag}`); console.log(`Ready! Logged in as ${client.user?.tag}`);
client.user?.setPresence({ activities: [{ name: '/ask | Version 3.0 !', type: 3 }] }); client.user?.setPresence({ activities: [{ name: '/ask | Version 3.0 !', type: 3 }] });
setInterval(async () => { setInterval(async () => {
await checkReset(); await checkReset();
}, 10 * 60 * 1000); }, 1000); //10 * 60 *
setInterval(async () => { setInterval(async () => {
client.user?.setPresence({ activities: [{ name: '/ask | Version 3.0 !', type: 3 }] }); client.user?.setPresence({ activities: [{ name: '/ask | Version 3.0 !', type: 3 }] });
}, 10 * 60 * 1000); }, 10 * 60 * 1000);
}, },
}; };

View file

@ -5,11 +5,11 @@ import { Client, Collection, REST, Routes, RESTPutAPIApplicationCommandsResult,
const client: Client = new Client({ const client: Client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, GatewayIntentBits.MessageContent,
GatewayIntentBits.DirectMessages, GatewayIntentBits.DirectMessages,
], ],
partials: [ partials: [
Partials.Channel, Partials.Channel,
Partials.Message, Partials.Message,
@ -24,41 +24,41 @@ async function loadCommands() {
const commandFolders = fs.readdirSync(foldersPath); const commandFolders = fs.readdirSync(foldersPath);
for (const folder of commandFolders) { for (const folder of commandFolders) {
const commandsPath = `./src/commands/${folder}`; const commandsPath = `./src/commands/${folder}`;
const commandFiles = fs const commandFiles = fs
.readdirSync(commandsPath) .readdirSync(commandsPath)
.filter((file) => file.endsWith(".ts") || file.endsWith(".js")); .filter((file) => file.endsWith(".ts") || file.endsWith(".js"));
for (const file of commandFiles) { for (const file of commandFiles) {
const filePath = `./commands/${folder}/${file}`; const filePath = `./commands/${folder}/${file}`;
const command = await import(filePath.replace(".ts", ".js")); const command = await import(filePath.replace(".ts", ".js"));
if ("data" in command.default && "execute" in command.default) { if ("data" in command.default && "execute" in command.default) {
client.commands.set(command.default.data.name, command.default); client.commands.set(command.default.data.name, command.default);
commands.push(command.default.data.toJSON()); commands.push(command.default.data.toJSON());
} }
else { else {
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
} }
} }
} }
} }
async function loadEvents() { async function loadEvents() {
const eventsPath = "./src/events"; const eventsPath = "./src/events";
const eventFiles = fs const eventFiles = fs
.readdirSync(eventsPath) .readdirSync(eventsPath)
.filter((file) => file.endsWith(".ts") || file.endsWith(".js")); .filter((file) => file.endsWith(".ts") || file.endsWith(".js"));
for (const file of eventFiles) { for (const file of eventFiles) {
const filePath = `./events/${file}`; const filePath = `./events/${file}`;
const event = await import(filePath.replace(".ts", ".js")); const event = await import(filePath.replace(".ts", ".js"));
if (event.default.once) { if (event.default.once) {
client.once(event.default.name, (...args) => event.default.execute(...args)); client.once(event.default.name, (...args) => event.default.execute(...args));
} }
else { else {
client.on(event.default.name, (...args) => event.default.execute(...args)); client.on(event.default.name, (...args) => event.default.execute(...args));
} }
} }
} }
@ -67,18 +67,18 @@ const rest = new REST().setToken(process.env.DISCORD_TOKEN ? process.env.DISCORD
(async () => { (async () => {
await loadCommands(); await loadCommands();
await loadEvents(); await loadEvents();
try { try {
console.log(`Started refreshing ${commands.length} application (/) commands.`); console.log(`Started refreshing ${commands.length} application (/) commands.`);
const data = await rest.put( const data = await rest.put(
Routes.applicationCommands(process.env.BOT_ID ? process.env.BOT_ID : ""), Routes.applicationCommands(process.env.BOT_ID ? process.env.BOT_ID : ""),
{ body: commands } { body: commands }
); );
console.log(`Successfully reloaded ${commands.length} application (/) commands.`); console.log(`Successfully reloaded ${commands.length} application (/) commands.`);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
})(); })();
client.login(process.env.DISCORD_TOKEN); client.login(process.env.DISCORD_TOKEN);

View file

@ -1,25 +1,66 @@
import { Events, Message, Collection } from "discord.js"; import { Events, Message, Collection, EmbedBuilder } from "discord.js";
import { getChatResponse, MistralMessage, Models, InputPrice, OutputPrice, ReturnedValue, Prompts } from "../libs/mistralai.js"; import { getChatResponse, MistralMessage, Models, InputPrice, OutputPrice, ReturnedValue, Prompts } from "../libs/mistralai.js";
import { User, connectToDb, addUser, getUser, incrementQuota } from "../libs/mysql.js"; import { User, connectToDb, addUser, getUser, incrementQuota } from "../libs/mysql.js";
export async function getMessages(message: Message, channelid: string, userid: string): Promise<MistralMessage[]> { export function helpEmbed() {
var discordMessages = await message.channel.messages.fetch({ limit: 7 }) return new EmbedBuilder()
discordMessages.filter((m) => m.content && (m.author.id == message.author.id || m.author.id == process.env.BOT_ID)) .setTitle("Help :")
discordMessages.reverse(); .setDescription(
`
**Commands**
var messages: MistralMessage[] = [ - \`/help\` : display this message
{ - \`/ask\` : make a single request to mistralAi API
role: "system", - \`/quota\` : send how many credits you have left for the month
content: Prompts.default,
}
]
discordMessages.forEach(discordMessage => { **Other way to use the bot**
messages.push({
role: discordMessage.author.id == process.env.BOT_ID ? "assistant" : "user", - You can DM the bot and it will answer you and remember the 6 previous messages
content: discordMessage.content,
}) **Quota**
})
- You have 0.4$ of free credits
`
)
.setFooter({ text: "Bot by @ninja_jambon."})
.setColor("#000000");
return messages; }
export function errorEmbed(error: string) {
return new EmbedBuilder()
.setTitle("Error")
.setDescription(error)
.setFooter({ text: "Bot by @ninja_jambon."})
.setColor("#000000");
}
export function quotaEmbed(quota: number) {
return new EmbedBuilder()
.setTitle("Quota left")
.setDescription(`You have ${0.4 - quota}$ left this month.`)
.setFooter({ text: "Bot by @ninja_jambon."})
.setColor("#000000");
}
export async function getMessages(message: Message, channelid: string, userid: string): Promise<MistralMessage[]> {
var discordMessages = await message.channel.messages.fetch({ limit: 7 })
discordMessages.filter((m) => m.content && (m.author.id == message.author.id || m.author.id == process.env.BOT_ID))
discordMessages.reverse();
var messages: MistralMessage[] = [
{
role: "system",
content: Prompts.default,
}
]
discordMessages.forEach(discordMessage => {
messages.push({
role: discordMessage.author.id == process.env.BOT_ID ? "assistant" : "user",
content: discordMessage.content,
})
})
return messages;
} }

View file

@ -2,42 +2,42 @@ import MistralClient from '@mistralai/mistralai';
import "dotenv/config"; import "dotenv/config";
export interface MistralMessage { export interface MistralMessage {
role: string, role: string,
content: string, content: string,
} }
export enum Models { export enum Models {
tiny = "open-mistral-7b", tiny = "open-mistral-7b",
multi_tiny = "open-mixtral-8x7b", multi_tiny = "open-mixtral-8x7b",
small = "mistral-small-latest", small = "mistral-small-latest",
medium = "mistral-medium-latest", medium = "mistral-medium-latest",
large = "mistral-large-latest", large = "mistral-large-latest",
} }
export enum InputPrice { export enum InputPrice {
tiny = 0.25 / 1000000, tiny = 0.25 / 1000000,
multi_tiny = 0.7 / 1000000, multi_tiny = 0.7 / 1000000,
small = 2 / 1000000, small = 2 / 1000000,
medium = 2.7 / 1000000, medium = 2.7 / 1000000,
large = 8 / 1000000, large = 8 / 1000000,
} }
export enum OutputPrice { export enum OutputPrice {
tiny = 0.25 / 1000000, tiny = 0.25 / 1000000,
multi_tiny = 0.7 / 1000000, multi_tiny = 0.7 / 1000000,
small = 6 / 1000000, small = 6 / 1000000,
medium = 8.1 / 1000000, medium = 8.1 / 1000000,
large = 24 / 1000000, large = 24 / 1000000,
} }
export interface ReturnedValue { export interface ReturnedValue {
message: string, message: string,
promptUsage: number, promptUsage: number,
responseUsage: number, responseUsage: number,
} }
export enum Prompts { export enum Prompts {
default = "You are an helpful assistant, you always answer in the language of the user.", default = "You are an helpful assistant, you always answer in the language of the user.",
} }
const apiKey = process.env.MISTRAL_API_KEY; const apiKey = process.env.MISTRAL_API_KEY;
@ -45,14 +45,14 @@ const apiKey = process.env.MISTRAL_API_KEY;
const client = new MistralClient(apiKey); const client = new MistralClient(apiKey);
export async function getChatResponse(messages: MistralMessage[], model: Models): Promise<ReturnedValue> { export async function getChatResponse(messages: MistralMessage[], model: Models): Promise<ReturnedValue> {
const chatResponse = await client.chat({ const chatResponse = await client.chat({
model: model, model: model,
messages: messages, messages: messages,
}); });
return { return {
message: chatResponse.choices[0].message.content, message: chatResponse.choices[0].message.content,
promptUsage: chatResponse.usage.prompt_tokens, promptUsage: chatResponse.usage.prompt_tokens,
responseUsage: chatResponse.usage.completion_tokens, responseUsage: chatResponse.usage.completion_tokens,
}; };
} }

View file

@ -2,7 +2,7 @@ import * as fs from "fs";
import { connectToDb, resetQuota } from "./mysql.js"; import { connectToDb, resetQuota } from "./mysql.js";
function getLastResetDate(): number { function getLastResetDate(): number {
const data: string = fs.readFileSync("../data/lastreset.txt", "utf8"); const data: string = fs.readFileSync("./src/data/lastreset.txt", "utf8");
return parseInt(data); return parseInt(data);
} }
@ -11,16 +11,16 @@ export async function checkReset() {
const now = Date.now() / 1000; const now = Date.now() / 1000;
if (now - lastResetDate > 1000 * 60 * 60 * 24 * 30) { if (now - lastResetDate > 1000 * 60 * 60 * 24 * 30) {
fs.writeFileSync("../data/lastreset.txt", now.toString()); fs.writeFileSync("./src/data/lastreset.txt", now.toString());
const connection = await connectToDb(); const connection = await connectToDb();
await resetQuota(connection); await resetQuota(connection);
connection.end(); connection.end();
return; return;
} else { } else {
return false; return false;
} }
} }