26.1 Dev-6
- Further Dashboard Improvements - Bug Fixes
This commit is contained in:
1 parent
d5fa42b55d
commit
033f32d596
16 files changed
+790
-82
No files matched your search
+360
@@ -0,0 +1,360 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const yaml = require('js-yaml');
|
||||
const YAML = require('yaml');
|
||||
const db = require('../Structures/Database.js');
|
||||
|
||||
class APIServer {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
this.app = express();
|
||||
|
||||
try {
|
||||
const apiCfg = yaml.load(fs.readFileSync('./Configs/api.yml', 'utf8')).API;
|
||||
this.config = apiCfg;
|
||||
} catch (e) {
|
||||
console.error('[API] Failed to load api.yml, defaulting to disabled.');
|
||||
this.config = { Enabled: false };
|
||||
}
|
||||
|
||||
if (!this.config.Enabled) return;
|
||||
|
||||
this.app.use(cors());
|
||||
this.app.use(express.json());
|
||||
|
||||
this.app.use((req, res, next) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || authHeader !== `Bearer ${this.config.SecretKey}`) {
|
||||
return res.status(401).json({ error: 'Unauthorized. Invalid Secret Key.' });
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
this.setupRoutes();
|
||||
}
|
||||
|
||||
start(port) {
|
||||
const listenPort = port || this.config.Port || 3000;
|
||||
this.app.listen(listenPort, () => {
|
||||
console.log(`[API] Server running on port ${listenPort}`);
|
||||
});
|
||||
}
|
||||
|
||||
setupRoutes() {
|
||||
this.app.get('/api/stats', async (req, res) => {
|
||||
try {
|
||||
if (!this.client.user) {
|
||||
return res.json({ success: false, error: 'Bot is still starting up.' });
|
||||
}
|
||||
|
||||
let userCount = 0;
|
||||
this.client.guilds.cache.forEach(guild => {
|
||||
userCount += guild.memberCount;
|
||||
});
|
||||
|
||||
const allTickets = db.getAllTickets() || [];
|
||||
const openTickets = allTickets.filter(t => t.open).length;
|
||||
const closedTickets = allTickets.filter(t => !t.open).length;
|
||||
|
||||
const totalMem = os.totalmem();
|
||||
const freeMem = os.freemem();
|
||||
const usedMem = totalMem - freeMem;
|
||||
const ramPercent = Math.round((usedMem / totalMem) * 100);
|
||||
const cpuLoad = os.loadavg()[0].toFixed(2);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
bot: {
|
||||
username: this.client.user.username,
|
||||
id: this.client.user.id,
|
||||
avatar: this.client.user.displayAvatarURL(),
|
||||
version: require('../package.json').version,
|
||||
ping: this.client.ws.ping,
|
||||
uptime: process.uptime(),
|
||||
},
|
||||
servers: this.client.guilds.cache.size,
|
||||
users: userCount,
|
||||
tickets: {
|
||||
total: allTickets.length,
|
||||
open: openTickets,
|
||||
closed: closedTickets
|
||||
},
|
||||
hosting: {
|
||||
ram_used: (usedMem / 1024 / 1024 / 1024).toFixed(2) + ' GB',
|
||||
ram_total: (totalMem / 1024 / 1024 / 1024).toFixed(2) + ' GB',
|
||||
ram_percent: ramPercent,
|
||||
cpu_load: cpuLoad,
|
||||
uptime: Math.floor(os.uptime() / 86400) + ' Days'
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[API] Error fetching stats:', err);
|
||||
res.status(500).json({ success: false, error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.get('/api/configs/:file', (req, res) => {
|
||||
const validFiles = ['supportbot', 'ticket-panel', 'commands', 'messages', 'supportbot-ai'];
|
||||
const file = req.params.file;
|
||||
if (!validFiles.includes(file)) return res.status(400).json({ success: false, error: 'Invalid file' });
|
||||
try {
|
||||
const configData = fs.readFileSync(`./Configs/${file}.yml`, 'utf8');
|
||||
res.json({ success: true, data: configData });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: 'Failed to read config' });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.put('/api/configs/raw', (req, res) => {
|
||||
const validFiles = ['supportbot', 'ticket-panel', 'commands', 'messages', 'supportbot-ai'];
|
||||
const file = req.body.filename;
|
||||
const content = req.body.content;
|
||||
if (!validFiles.includes(file)) return res.status(400).json({ success: false, error: 'Invalid file' });
|
||||
try {
|
||||
fs.writeFileSync(`./Configs/${file}.yml`, content);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: 'Failed to write config' });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.get('/api/configs/json/:file', (req, res) => {
|
||||
const validFiles = ['supportbot', 'ticket-panel', 'commands', 'messages', 'supportbot-ai'];
|
||||
const file = req.params.file;
|
||||
if (!validFiles.includes(file)) return res.status(400).json({ success: false, error: 'Invalid file' });
|
||||
try {
|
||||
const configData = yaml.load(fs.readFileSync(`./Configs/${file}.yml`, 'utf8'));
|
||||
res.json({ success: true, data: configData });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: 'Failed to parse config' });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.post('/api/configs/update-fields/multi', (req, res) => {
|
||||
const updates = req.body;
|
||||
if (!updates || typeof updates !== 'object') return res.status(400).json({ success: false, error: 'Invalid updates' });
|
||||
try {
|
||||
for (const [keyPath, value] of Object.entries(updates)) {
|
||||
const [fileName, path] = keyPath.split(':');
|
||||
const fileContent = fs.readFileSync(`./Configs/${fileName}.yml`, 'utf8');
|
||||
const doc = YAML.parseDocument(fileContent);
|
||||
const keys = path.split('.');
|
||||
doc.setIn(keys, value);
|
||||
fs.writeFileSync(`./Configs/${fileName}.yml`, doc.toString());
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error('[API] Update failed:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.post('/api/addons/install', async (req, res) => {
|
||||
const { url, filename } = req.body;
|
||||
if (!url || !filename) return res.status(400).json({ success: false, error: 'Missing url or filename' });
|
||||
|
||||
try {
|
||||
const axios = require('axios');
|
||||
const response = await axios({
|
||||
method: 'get',
|
||||
url: url,
|
||||
responseType: 'stream'
|
||||
});
|
||||
|
||||
// Ensure Addons directory exists
|
||||
if (!fs.existsSync('./Addons')) {
|
||||
fs.mkdirSync('./Addons');
|
||||
}
|
||||
|
||||
const writer = fs.createWriteStream(`./Addons/${filename}`);
|
||||
response.data.pipe(writer);
|
||||
|
||||
writer.on('finish', () => {
|
||||
res.json({ success: true, message: `Addon ${filename} installed successfully.` });
|
||||
});
|
||||
|
||||
writer.on('error', (err) => {
|
||||
console.error('[API] Download failed:', err);
|
||||
res.status(500).json({ success: false, error: 'Failed to write addon file' });
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('[API] Addon install failed:', err);
|
||||
res.status(500).json({ success: false, error: 'Failed to download addon' });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.post('/api/addons/push', (req, res) => {
|
||||
const { filename, content } = req.body;
|
||||
if (!filename || !content) return res.status(400).json({ success: false, error: 'Missing filename or content' });
|
||||
|
||||
const sanitizedFilename = filename.replace(/[^a-z0-9_.-]/gi, '_');
|
||||
|
||||
try {
|
||||
if (!fs.existsSync('./Addons')) {
|
||||
fs.mkdirSync('./Addons');
|
||||
}
|
||||
fs.writeFileSync(`./Addons/${sanitizedFilename}`, content);
|
||||
res.json({ success: true, message: `Addon ${sanitizedFilename} deployed successfully.` });
|
||||
} catch (err) {
|
||||
console.error('[API] Push failed:', err);
|
||||
res.status(500).json({ success: false, error: 'Failed to write addon file' });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Nexus Updater API ---
|
||||
this.app.post('/api/system/update', async (req, res) => {
|
||||
const { url, version } = req.body;
|
||||
if (!url) return res.status(400).json({ success: false, error: 'Missing update URL' });
|
||||
|
||||
try {
|
||||
const axios = require('axios');
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
console.log(`[Updater] Starting update to ${version || 'latest'}...`);
|
||||
|
||||
// 1. Download
|
||||
const tempZip = `./update_${Date.now()}.zip`;
|
||||
const response = await axios({ method: 'get', url: url, responseType: 'stream' });
|
||||
const writer = fs.createWriteStream(tempZip);
|
||||
response.data.pipe(writer);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
writer.on('finish', resolve);
|
||||
writer.on('error', reject);
|
||||
});
|
||||
|
||||
// 2. Extract to temp dir
|
||||
const tempDir = `./temp_update_${Date.now()}`;
|
||||
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir);
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
try {
|
||||
if (isWindows) {
|
||||
execSync(`powershell -Command "Expand-Archive -Path '${tempZip}' -DestinationPath '${tempDir}' -Force"`);
|
||||
} else {
|
||||
execSync(`unzip -o "${tempZip}" -d "${tempDir}"`);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Extraction failed. Ensure ${isWindows ? 'PowerShell' : 'unzip'} is installed. ${e.message}`);
|
||||
}
|
||||
|
||||
// Identify inner directory (sometimes zips contain a root folder)
|
||||
let sourcePath = tempDir;
|
||||
const items = fs.readdirSync(tempDir);
|
||||
if (items.length === 1 && fs.statSync(path.join(tempDir, items[0])).isDirectory()) {
|
||||
sourcePath = path.join(tempDir, items[0]);
|
||||
}
|
||||
|
||||
// 3. Smart Config Merge
|
||||
const configFiles = ['supportbot', 'commands', 'messages', 'ticket-panel', 'supportbot-ai'];
|
||||
configFiles.forEach(cf => {
|
||||
const currentPath = `./Configs/${cf}.yml`;
|
||||
const newPath = path.join(sourcePath, 'Configs', `${cf}.yml`);
|
||||
|
||||
if (fs.existsSync(currentPath) && fs.existsSync(newPath)) {
|
||||
const currentCfg = yaml.load(fs.readFileSync(currentPath, 'utf8'));
|
||||
const newCfg = yaml.load(fs.readFileSync(newPath, 'utf8'));
|
||||
|
||||
const merge = (target, source) => {
|
||||
for (const key of Object.keys(source)) {
|
||||
if (source[key] instanceof Object && !Array.isArray(source[key]) && target[key]) {
|
||||
merge(target[key], source[key]);
|
||||
} else if (target[key] === undefined) {
|
||||
target[key] = source[key];
|
||||
console.log(`[Updater] Added missing key: ${key} to ${cf}.yml`);
|
||||
}
|
||||
}
|
||||
};
|
||||
merge(currentCfg, newCfg);
|
||||
fs.writeFileSync(currentPath, YAML.stringify(currentCfg));
|
||||
}
|
||||
});
|
||||
|
||||
// 4. Overwrite Core Folders
|
||||
const coreFolders = ['API', 'Commands', 'Events', 'Structures'];
|
||||
coreFolders.forEach(folder => {
|
||||
const src = path.join(sourcePath, folder);
|
||||
if (fs.existsSync(src)) {
|
||||
if (isWindows) {
|
||||
execSync(`xcopy "${src}" ".\\${folder}" /E /I /Y`);
|
||||
} else {
|
||||
execSync(`cp -R "${src}/"* "./${folder}/"`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 5. Update package.json (version only)
|
||||
const newPkgPath = path.join(sourcePath, 'package.json');
|
||||
if (fs.existsSync(newPkgPath)) {
|
||||
const currentPkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
const newPkg = JSON.parse(fs.readFileSync(newPkgPath, 'utf8'));
|
||||
currentPkg.version = newPkg.version;
|
||||
fs.writeFileSync('./package.json', JSON.stringify(currentPkg, null, 2));
|
||||
}
|
||||
|
||||
fs.unlinkSync(tempZip);
|
||||
if (isWindows) {
|
||||
execSync(`rmdir /s /q "${tempDir}"`);
|
||||
} else {
|
||||
execSync(`rm -rf "${tempDir}"`);
|
||||
}
|
||||
|
||||
res.json({ success: true, message: 'Update installed successfully. Bot is restarting...' });
|
||||
|
||||
// Reboot
|
||||
setTimeout(() => process.exit(0), 1000);
|
||||
|
||||
} catch (err) {
|
||||
console.error('[Updater] Update failed:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Transcript API ---
|
||||
this.app.get('/api/system/transcripts', (req, res) => {
|
||||
const transcriptDir = './Data/Transcripts';
|
||||
try {
|
||||
if (!fs.existsSync(transcriptDir)) {
|
||||
return res.json({ success: true, data: [] });
|
||||
}
|
||||
const files = fs.readdirSync(transcriptDir);
|
||||
const transcripts = files
|
||||
.filter(f => f.endsWith('-transcript.html'))
|
||||
.map(f => {
|
||||
const stats = fs.statSync(`${transcriptDir}/${f}`);
|
||||
return {
|
||||
id: f.replace('-transcript.html', ''),
|
||||
filename: f,
|
||||
createdAt: stats.mtime
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.createdAt - a.createdAt);
|
||||
res.json({ success: true, data: transcripts });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: 'Failed to list transcripts' });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.get('/api/system/transcripts/:id', (req, res) => {
|
||||
const id = req.params.id.replace(/[^0-9]/g, ''); // Sanitize ID
|
||||
const filePath = `./Data/Transcripts/${id}-transcript.html`;
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return res.status(404).json({ success: false, error: 'Transcript not found' });
|
||||
}
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
res.json({ success: true, data: content });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: 'Failed to read transcript' });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = APIServer;
|
||||
@@ -0,0 +1,122 @@
|
||||
// SupportBot | Emerald Services
|
||||
// AI Command (Consolidated)
|
||||
|
||||
const fs = require("fs");
|
||||
const { ApplicationCommandOptionType, ApplicationCommandType, EmbedBuilder, MessageFlags } = require("discord.js");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
|
||||
const supportbotai = yaml.load(fs.readFileSync("./Configs/supportbot-ai.yml", "utf8"));
|
||||
|
||||
const Command = require("../../Structures/Command.js");
|
||||
const AIDatabase = require("../../Structures/AIDatabase.js");
|
||||
|
||||
module.exports = new Command({
|
||||
name: cmdconfig.AI?.Command || "ai",
|
||||
description: cmdconfig.AI?.Description || "Manage SupportBot AI settings and memory.",
|
||||
type: ApplicationCommandType.ChatInput,
|
||||
options: [
|
||||
{
|
||||
name: cmdconfig.AI?.Train?.Command || "train",
|
||||
description: cmdconfig.AI?.Train?.Description || "Train the AI by adding knowledge to its database.",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
options: [
|
||||
{
|
||||
name: "content",
|
||||
description: "The text/knowledge you want the bot to learn",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: cmdconfig.AI?.Knowledge?.Command || "knowledge",
|
||||
description: cmdconfig.AI?.Knowledge?.Description || "Manage the AI knowledge base.",
|
||||
type: ApplicationCommandOptionType.SubcommandGroup,
|
||||
options: [
|
||||
{
|
||||
name: cmdconfig.AI?.Knowledge?.Clear?.Command || "clear",
|
||||
description: cmdconfig.AI?.Knowledge?.Clear?.Description || "Clear all trained knowledge from the AI.",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: cmdconfig.AI?.Memory?.Command || "memory",
|
||||
description: cmdconfig.AI?.Memory?.Description || "Manage the AI conversational memory.",
|
||||
type: ApplicationCommandOptionType.SubcommandGroup,
|
||||
options: [
|
||||
{
|
||||
name: cmdconfig.AI?.Memory?.Clear?.Command || "clear",
|
||||
description: cmdconfig.AI?.Memory?.Clear?.Description || "Clear recent conversation memory for this channel.",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
permissions: cmdconfig.AI?.Permission || ["Administrator"],
|
||||
|
||||
async run(interaction) {
|
||||
if (!supportbotai.Enabled) {
|
||||
return interaction.reply({
|
||||
content: "SupportBot AI is currently disabled.",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
|
||||
const subcommandGroup = interaction.options.getSubcommandGroup(false);
|
||||
const subcommand = interaction.options.getSubcommand(false);
|
||||
|
||||
try {
|
||||
if (subcommand === "train" || subcommand === cmdconfig.AI?.Train?.Command) {
|
||||
const content = interaction.options.getString("content");
|
||||
|
||||
AIDatabase.addKnowledge({
|
||||
sourceType: "user_command",
|
||||
sourceName: interaction.user.tag,
|
||||
title: "Trained Knowledge",
|
||||
content: content,
|
||||
});
|
||||
|
||||
const SuccessEmbed = new EmbedBuilder()
|
||||
.setDescription(`✅ **Knowledge Added!**\n\nThe AI has learned the following information:\n\`\`\`${content}\`\`\``)
|
||||
.setColor(supportbot.Embed.Colours.Success);
|
||||
|
||||
return interaction.reply({ embeds: [SuccessEmbed], flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
|
||||
if (subcommandGroup === (cmdconfig.AI?.Knowledge?.Command || "knowledge")) {
|
||||
if (subcommand === (cmdconfig.AI?.Knowledge?.Clear?.Command || "clear")) {
|
||||
AIDatabase.clearKnowledge();
|
||||
|
||||
const SuccessEmbed = new EmbedBuilder()
|
||||
.setDescription(`✅ **Knowledge Cleared!**\n\nAll manually trained knowledge has been wiped from the database.`)
|
||||
.setColor(supportbot.Embed.Colours.Success);
|
||||
|
||||
return interaction.reply({ embeds: [SuccessEmbed], flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
}
|
||||
|
||||
if (subcommandGroup === (cmdconfig.AI?.Memory?.Command || "memory")) {
|
||||
if (subcommand === (cmdconfig.AI?.Memory?.Clear?.Command || "clear")) {
|
||||
AIDatabase.clearConversations(interaction.channel.id);
|
||||
|
||||
const SuccessEmbed = new EmbedBuilder()
|
||||
.setDescription(`✅ **Memory Cleared!**\n\nThe AI's recent conversation memory in this channel has been wiped. It will no longer remember previous messages sent here.`)
|
||||
.setColor(supportbot.Embed.Colours.Success);
|
||||
|
||||
return interaction.reply({ embeds: [SuccessEmbed], flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("AI Command Error:", error);
|
||||
const ErrorEmbed = new EmbedBuilder()
|
||||
.setDescription(`❌ **Error:** Failed to process the AI command.`)
|
||||
.setColor(supportbot.Embed.Colours.Error);
|
||||
|
||||
return interaction.reply({ embeds: [ErrorEmbed], flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -201,7 +201,7 @@ module.exports = new Command({
|
||||
if (
|
||||
!interaction.member.roles.cache.has(Admin.id) &&
|
||||
!interaction.member.roles.cache.has(Moderator.id) &&
|
||||
(!supportbot.Mod.AllowSupportStaff || !interaction.member.roles.cache.has(SupportStaff.id))
|
||||
(!supportbot.Roles?.Mod?.AllowSupportStaff || !interaction.member.roles.cache.has(SupportStaff.id))
|
||||
) {
|
||||
return interaction.reply({
|
||||
embeds: [NoPerms],
|
||||
@@ -270,7 +270,7 @@ module.exports = new Command({
|
||||
await interaction.channel.setParent(targetCategory.id);
|
||||
|
||||
if (oldRoleId && oldRoleId !== targetRoleId) {
|
||||
await interaction.channel.permissionOverwrites.delete(oldRoleId).catch(() => {});
|
||||
await interaction.channel.permissionOverwrites.delete(oldRoleId).catch(() => { });
|
||||
}
|
||||
|
||||
if (targetRoleId) {
|
||||
@@ -298,7 +298,7 @@ module.exports = new Command({
|
||||
);
|
||||
|
||||
if (interaction.channel.name !== newChannelName) {
|
||||
await interaction.channel.setName(newChannelName).catch(() => {});
|
||||
await interaction.channel.setName(newChannelName).catch(() => { });
|
||||
}
|
||||
|
||||
const deptEmoji = departmentConfig.Emoji || "🎫";
|
||||
@@ -354,7 +354,7 @@ module.exports = new Command({
|
||||
const newChannelName = buildTicketChannelName(ticketNumber, level, departmentConfig);
|
||||
|
||||
if (interaction.channel.name !== newChannelName) {
|
||||
await interaction.channel.setName(newChannelName).catch(() => {});
|
||||
await interaction.channel.setName(newChannelName).catch(() => { });
|
||||
}
|
||||
|
||||
const priorityEmoji = priorityConfig.Emoji || "🟡";
|
||||
@@ -448,7 +448,7 @@ module.exports = new Command({
|
||||
)
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
|
||||
await userToAdd.send({ embeds: [addedToTicketEmbed] }).catch(() => {});
|
||||
await userToAdd.send({ embeds: [addedToTicketEmbed] }).catch(() => { });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error adding user to the ticket:", err);
|
||||
@@ -517,7 +517,7 @@ module.exports = new Command({
|
||||
)
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
|
||||
await userToRemove.send({ embeds: [removedFromTicketEmbed] }).catch(() => {});
|
||||
await userToRemove.send({ embeds: [removedFromTicketEmbed] }).catch(() => { });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error removing user from the ticket:", err);
|
||||
@@ -633,7 +633,7 @@ module.exports = new Command({
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
|
||||
await interaction.channel.delete().catch(() => {});
|
||||
await interaction.channel.delete().catch(() => { });
|
||||
} catch (err) {
|
||||
console.error("Error closing ticket:", err);
|
||||
|
||||
|
||||
@@ -595,8 +595,12 @@ module.exports = new Command({
|
||||
|
||||
async run(interaction) {
|
||||
try {
|
||||
if (!interaction.deferred && !interaction.replied) {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral }).catch(() => {});
|
||||
}
|
||||
|
||||
if (await isBlacklisted(interaction.user.id)) {
|
||||
return interaction.reply({
|
||||
return interaction.editReply({
|
||||
content: msgconfig.Ticket.Blacklisted,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
@@ -620,7 +624,7 @@ module.exports = new Command({
|
||||
(t) => t.user === interaction.user.id && t.open,
|
||||
).length >= supportbot.Ticket.TicketsPerUser
|
||||
) {
|
||||
return interaction.reply({
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
{
|
||||
title: "Too Many Tickets!",
|
||||
@@ -668,7 +672,7 @@ module.exports = new Command({
|
||||
(ticketChannel) => ticketChannel.name === ticketChannelName,
|
||||
)
|
||||
) {
|
||||
return await interaction.reply({
|
||||
return await interaction.editReply({
|
||||
embeds: [TicketExists],
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
@@ -691,7 +695,7 @@ module.exports = new Command({
|
||||
: null;
|
||||
|
||||
if (!Staff || !Admin) {
|
||||
return interaction.reply({
|
||||
return interaction.editReply({
|
||||
content:
|
||||
"Some roles seem to be missing!\nPlease check for errors when starting the bot.",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
@@ -731,7 +735,7 @@ module.exports = new Command({
|
||||
);
|
||||
|
||||
if (!category) {
|
||||
return interaction.reply({
|
||||
return interaction.editReply({
|
||||
content: "The ticket category does not exist!",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
@@ -758,7 +762,7 @@ module.exports = new Command({
|
||||
if (secondaryCategory) {
|
||||
await ticketChannel.setParent(secondaryCategory.id);
|
||||
} else {
|
||||
return interaction.reply({
|
||||
return interaction.editReply({
|
||||
content: "The secondary ticket category does not exist!",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
@@ -844,7 +848,7 @@ module.exports = new Command({
|
||||
)
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
embeds: [CreatedTicket],
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
@@ -1050,7 +1054,7 @@ module.exports = new Command({
|
||||
console.error("Error in run method:", error);
|
||||
|
||||
if (!interaction.replied && !interaction.deferred) {
|
||||
return interaction.reply({
|
||||
return interaction.editReply({
|
||||
content: "An error occurred while creating the ticket.",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
|
||||
@@ -14,6 +14,8 @@ const {
|
||||
SeparatorBuilder,
|
||||
SectionBuilder,
|
||||
StringSelectMenuBuilder,
|
||||
ThumbnailBuilder,
|
||||
EmbedBuilder,
|
||||
} = require("discord.js");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
@@ -92,7 +94,7 @@ module.exports = new Command({
|
||||
try {
|
||||
const oldMessage = await channel.messages.fetch(panelRow.message_id);
|
||||
if (oldMessage) {
|
||||
await oldMessage.delete().catch(() => {});
|
||||
await oldMessage.delete().catch(() => { });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`[TICKET PANEL] Previous panel message not found, creating a new one.`);
|
||||
@@ -139,9 +141,9 @@ module.exports = new Command({
|
||||
.setCustomId(panelconfig.DepartmentMenu?.CustomId || "ticketdepartmentselect")
|
||||
.setPlaceholder(
|
||||
panelconfig.DepartmentMenu?.Placeholder ||
|
||||
departmentSystem.Placeholder ||
|
||||
panelconfig.Button?.Text ||
|
||||
"Choose a ticket department",
|
||||
departmentSystem.Placeholder ||
|
||||
panelconfig.Button?.Text ||
|
||||
"Choose a ticket department",
|
||||
)
|
||||
.addOptions(departmentOptions);
|
||||
} else {
|
||||
@@ -172,6 +174,8 @@ module.exports = new Command({
|
||||
panelconfig.Style?.Title || "## SupportBot Ticket Creation",
|
||||
);
|
||||
|
||||
const rawTitle = (panelconfig.Style?.Title || "SupportBot Ticket Creation").replace(/^#+\s*/, "");
|
||||
|
||||
const panelDesc = new TextDisplayBuilder().setContent(
|
||||
panelconfig.Style?.Description || "Click on the button below to create a support ticket.",
|
||||
);
|
||||
@@ -252,6 +256,49 @@ module.exports = new Command({
|
||||
panelContainer.addActionRowComponents(new ActionRowBuilder().addComponents(openerComponent));
|
||||
}
|
||||
|
||||
if (layout === "4") {
|
||||
// Create a classic Discord Embed for Layout 4
|
||||
const oldEmbed = new EmbedBuilder()
|
||||
.setTitle(rawTitle)
|
||||
.setDescription(panelconfig.Style?.Description || "Click on the button below to create a support ticket.");
|
||||
|
||||
if (panelconfig.Style?.Color) {
|
||||
oldEmbed.setColor(panelconfig.Style.Color);
|
||||
}
|
||||
|
||||
if (showImage && panelconfig.Style?.Image) {
|
||||
oldEmbed.setThumbnail(panelconfig.Style.Image);
|
||||
}
|
||||
|
||||
try {
|
||||
const message = await channel.send({
|
||||
embeds: [oldEmbed],
|
||||
components: [new ActionRowBuilder().addComponents(openerComponent)],
|
||||
});
|
||||
|
||||
db.saveTicketPanel(message.id, channel.id);
|
||||
|
||||
return interaction.reply({
|
||||
content: shouldUseDepartments
|
||||
? getPanelMessage(
|
||||
"PanelSentWithDepartments",
|
||||
"Ticket panel message with department selection has been sent!",
|
||||
)
|
||||
: getPanelMessage("PanelSent", "Ticket panel message has been sent!"),
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Error sending message:", e);
|
||||
return interaction.reply({
|
||||
content: getPanelMessage(
|
||||
"SendError",
|
||||
"An error occurred while sending the ticket panel message.",
|
||||
),
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (showImage && layout === "1") {
|
||||
const panelImage = new MediaGalleryBuilder().addItems([
|
||||
{
|
||||
@@ -275,9 +322,9 @@ module.exports = new Command({
|
||||
return interaction.reply({
|
||||
content: shouldUseDepartments
|
||||
? getPanelMessage(
|
||||
"PanelSentWithDepartments",
|
||||
"Ticket panel message with department selection has been sent!",
|
||||
)
|
||||
"PanelSentWithDepartments",
|
||||
"Ticket panel message with department selection has been sent!",
|
||||
)
|
||||
: getPanelMessage("PanelSent", "Ticket panel message has been sent!"),
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# -------------------------------
|
||||
# API Server Configuration
|
||||
# -------------------------------
|
||||
|
||||
API:
|
||||
Enabled: true
|
||||
Port: 3000 # PORT NUMBER FOR YOUR BOT
|
||||
SecretKey: "PUT_RANDOM_STRING_HERE" # DO NOT SHARE THIS KEY WITH ANYONE
|
||||
@@ -149,6 +149,30 @@ Settings:
|
||||
Permission: ["Administrator"]
|
||||
Enabled: true
|
||||
|
||||
AI:
|
||||
Command: "ai"
|
||||
Description: "Manage SupportBot AI settings and memory."
|
||||
Permission: ["Administrator"]
|
||||
Enabled: true
|
||||
|
||||
Train:
|
||||
Command: "train"
|
||||
Description: "Train the AI by adding knowledge to its database."
|
||||
|
||||
Knowledge:
|
||||
Command: "knowledge"
|
||||
Description: "Manage the AI knowledge base."
|
||||
Clear:
|
||||
Command: "clear"
|
||||
Description: "Clear all trained knowledge from the AI."
|
||||
|
||||
Memory:
|
||||
Command: "memory"
|
||||
Description: "Manage the AI conversational memory."
|
||||
Clear:
|
||||
Command: "clear"
|
||||
Description: "Clear recent conversation memory for this channel."
|
||||
|
||||
# ---------------------------------------
|
||||
# Command Description Configuration
|
||||
# ---------------------------------------
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
Enabled: true
|
||||
|
||||
General:
|
||||
Provider: "openai"
|
||||
Model: "gpt-5.4"
|
||||
Model_API_Key: "AI_API_KEY"
|
||||
Provider: "claude"
|
||||
Model: "claude-sonnet-4-6"
|
||||
Model_API_Key: "MODEL_API_KEY"
|
||||
# Providers Available:
|
||||
# "openai" - Use OpenAI's GPT models. Example: "gpt-5.4" "gpt-5-mini"
|
||||
# "groq" - Use Groq's GroqAI models. Example: "meta-llama/llama-4-scout-17b-16e-instruct"
|
||||
# "claude" - Use Anthropic's Claude models. Example: "claude-sonnet-4-6"
|
||||
|
||||
Tokens: 1200
|
||||
|
||||
@@ -34,7 +35,7 @@ Features:
|
||||
SuggestTickets: true # Should SupportBot AI suggest creating a ticket if the user needs more help?
|
||||
|
||||
Channels:
|
||||
AIChannel: "CHANNEL_ID"
|
||||
AIChannel: "1469028791312715902"
|
||||
|
||||
Embed:
|
||||
Color: "#45b463"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#
|
||||
# SupportBot Configuration (supportbot.yml)
|
||||
|
||||
SupportBot_Version: "v26.0.0"
|
||||
SupportBot_Version: "v26.1 DEV-2"
|
||||
|
||||
# -------------------------
|
||||
# General
|
||||
@@ -154,7 +154,7 @@ Ticket:
|
||||
DMOnRemove: true # DM the user when they are removed from the ticket
|
||||
DMTranscript: true # DM the user a transcript of the ticket when it is closed
|
||||
|
||||
TicketHome: "CHANNEL_ID"
|
||||
TicketHome: "1469028381638000725"
|
||||
|
||||
# What is TicketHome?
|
||||
# - Channel where the Ticket creation panel is sent.
|
||||
@@ -237,13 +237,14 @@ Ticket:
|
||||
# -------------------------
|
||||
|
||||
DepartmentSystem:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
Placeholder: "Choose a ticket department"
|
||||
DefaultDepartment: "general"
|
||||
|
||||
Departments:
|
||||
general:
|
||||
Name: "General Support"
|
||||
Description: "Open a general support ticket"
|
||||
Emoji: "🎫"
|
||||
Category: "CATEGORY_ID"
|
||||
Role: "ROLE_ID"
|
||||
@@ -255,6 +256,7 @@ Ticket:
|
||||
|
||||
purchase:
|
||||
Name: "Purchase Support"
|
||||
Description: "Get help with store purchases"
|
||||
Emoji: "💳"
|
||||
Category: "CATEGORY_ID"
|
||||
Role: "ROLE_ID"
|
||||
@@ -267,6 +269,7 @@ Ticket:
|
||||
|
||||
reports:
|
||||
Name: "Player Report"
|
||||
Description: "Report a player for breaking the rules"
|
||||
Emoji: "🚨"
|
||||
Category: "CATEGORY_ID"
|
||||
Role: "ROLE_ID"
|
||||
@@ -279,6 +282,7 @@ Ticket:
|
||||
|
||||
appeals:
|
||||
Name: "Ban Appeals"
|
||||
Description: "Appeal a server punishment"
|
||||
Emoji: "⚖️"
|
||||
Category: "CATEGORY_ID"
|
||||
Role: "ROLE_ID"
|
||||
|
||||
@@ -24,12 +24,13 @@ Style:
|
||||
Description: "Click on the button to file a `Ticket` and our team will try their best to assist you in the best way we can!"
|
||||
Image: "https://i.imgur.com/dejQvkg.png"
|
||||
Color: "#45b463"
|
||||
Layout: "3"
|
||||
Layout: "4"
|
||||
|
||||
# Available Layout Styles
|
||||
# "1" - Style 1 - Title & Text, Button at the top -- Image at the bottom
|
||||
# "2" - Style 2 - Title & Text, Button at the bottom -- Image at the top
|
||||
# "3" - Style 3 - The classic look - Title & Text at the top -- Image in the middle -- Button at the bottom
|
||||
# "4" - Style 4 - Classic Embed (Title & Text, Image on right, Button at bottom)
|
||||
|
||||
Button:
|
||||
Text: "Create Ticket"
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// Emerald Services | SupportBot
|
||||
// Claude integration for SupportBot AI
|
||||
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
const axios = require("axios");
|
||||
|
||||
const supportbotai = yaml.load(
|
||||
fs.readFileSync("./Configs/supportbot-ai.yml", "utf8"),
|
||||
);
|
||||
|
||||
function extractTextFromMessage(message) {
|
||||
if (!message) return "";
|
||||
|
||||
if (typeof message === "string") {
|
||||
return message.trim();
|
||||
}
|
||||
|
||||
if (message.text) {
|
||||
return message.text.trim();
|
||||
}
|
||||
|
||||
if (Array.isArray(message)) {
|
||||
return message
|
||||
.map((part) => {
|
||||
if (typeof part === "string") return part;
|
||||
if (part?.type === "text" && typeof part.text === "string") {
|
||||
return part.text;
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.join("")
|
||||
.trim();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
async function chat({ messages, temperature, maxTokens }) {
|
||||
const model = supportbotai.General.Model;
|
||||
const apiKey = supportbotai.General.Model_API_Key;
|
||||
|
||||
const systemMessages = messages.filter((m) => m.role === "system");
|
||||
const systemPrompt = systemMessages.map((m) => m.content).join("\n\n");
|
||||
|
||||
const userAssistantMessages = messages.filter((m) => m.role !== "system");
|
||||
|
||||
const response = await axios.post(
|
||||
"https://api.anthropic.com/v1/messages",
|
||||
{
|
||||
model,
|
||||
system: systemPrompt || undefined,
|
||||
messages: userAssistantMessages,
|
||||
temperature: temperature || supportbotai.General.Temperature || 0.4,
|
||||
max_tokens: maxTokens || supportbotai.General.Tokens || 800,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"x-api-key": apiKey,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"content-type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
const content = extractTextFromMessage(data.content);
|
||||
|
||||
if (!content) {
|
||||
console.error("Claude raw response:", JSON.stringify(data, null, 2));
|
||||
throw new Error(
|
||||
`Claude returned empty response. stop_reason=${data?.stop_reason || "unknown"}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
raw: data,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
chat,
|
||||
};
|
||||
+55
-33
@@ -16,6 +16,7 @@ const db = require("../Structures/Database.js");
|
||||
|
||||
const openai = require("./Models/openai.js");
|
||||
const groq = require("./Models/groq.js");
|
||||
const claude = require("./Models/claude.js");
|
||||
|
||||
const PASTEBIN_API_KEY = supportbotai.General.PastebinAPI_Key;
|
||||
const PASTEBIN_API_URL = supportbotai.General.PastebinAPI_URL;
|
||||
@@ -57,6 +58,9 @@ function getProvider() {
|
||||
case "groq":
|
||||
return groq;
|
||||
|
||||
case "claude":
|
||||
return claude;
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported AI provider: ${provider}`);
|
||||
}
|
||||
@@ -103,6 +107,9 @@ function buildSystemPrompt(message) {
|
||||
sections.push(...supportbotai.CustomInstructions);
|
||||
}
|
||||
|
||||
sections.push(
|
||||
`I am currently powered by the ${supportbotai.General.Provider || "Unknown"} provider, running the ${supportbotai.General.Model || "Unknown"} model.`,
|
||||
);
|
||||
sections.push(
|
||||
`The current guild is: ${message.guild?.name || "Unknown Guild"}`,
|
||||
);
|
||||
@@ -131,24 +138,24 @@ function buildConversationFromMemory(currentMessage) {
|
||||
const recentMessages =
|
||||
typeof AIDatabase.getRecentMessagesByScope === "function"
|
||||
? AIDatabase.getRecentMessagesByScope({
|
||||
guildId: currentMessage.guild?.id || null,
|
||||
channelId: currentMessage.channel?.id || null,
|
||||
limit: maxRecentMessages,
|
||||
scope: memoryScope,
|
||||
})
|
||||
guildId: currentMessage.guild?.id || null,
|
||||
channelId: currentMessage.channel?.id || null,
|
||||
limit: maxRecentMessages,
|
||||
scope: memoryScope,
|
||||
})
|
||||
: AIDatabase.getRecentMessages(
|
||||
currentMessage.channel?.id,
|
||||
maxRecentMessages,
|
||||
);
|
||||
currentMessage.channel?.id,
|
||||
maxRecentMessages,
|
||||
);
|
||||
|
||||
const latestSummary =
|
||||
memoryConfig.StoreSummaries !== false
|
||||
? typeof AIDatabase.getLatestSummaryByScope === "function"
|
||||
? AIDatabase.getLatestSummaryByScope({
|
||||
guildId: currentMessage.guild?.id || null,
|
||||
channelId: currentMessage.channel?.id || null,
|
||||
scope: memoryScope,
|
||||
})
|
||||
guildId: currentMessage.guild?.id || null,
|
||||
channelId: currentMessage.channel?.id || null,
|
||||
scope: memoryScope,
|
||||
})
|
||||
: AIDatabase.getLatestSummary(currentMessage.channel?.id)
|
||||
: null;
|
||||
|
||||
@@ -156,29 +163,33 @@ function buildConversationFromMemory(currentMessage) {
|
||||
memoryConfig.StoreFacts !== false
|
||||
? typeof AIDatabase.getFactsByScope === "function"
|
||||
? AIDatabase.getFactsByScope({
|
||||
guildId: currentMessage.guild?.id || null,
|
||||
channelId: currentMessage.channel?.id || null,
|
||||
scope: memoryScope,
|
||||
})
|
||||
guildId: currentMessage.guild?.id || null,
|
||||
channelId: currentMessage.channel?.id || null,
|
||||
scope: memoryScope,
|
||||
})
|
||||
: AIDatabase.getFacts(currentMessage.channel?.id)
|
||||
: [];
|
||||
|
||||
const userFacts =
|
||||
memoryConfig.StoreFacts !== false &&
|
||||
typeof AIDatabase.getUserFactsByScope === "function"
|
||||
typeof AIDatabase.getUserFactsByScope === "function"
|
||||
? AIDatabase.getUserFactsByScope({
|
||||
guildId: currentMessage.guild?.id || null,
|
||||
userId: currentMessage.author?.id || null,
|
||||
scope: memoryScope,
|
||||
})
|
||||
guildId: currentMessage.guild?.id || null,
|
||||
userId: currentMessage.author?.id || null,
|
||||
scope: memoryScope,
|
||||
})
|
||||
: memoryConfig.StoreFacts !== false &&
|
||||
typeof AIDatabase.getUserFacts === "function"
|
||||
typeof AIDatabase.getUserFacts === "function"
|
||||
? AIDatabase.getUserFacts(
|
||||
currentMessage.guild?.id,
|
||||
currentMessage.author?.id,
|
||||
)
|
||||
currentMessage.guild?.id,
|
||||
currentMessage.author?.id,
|
||||
)
|
||||
: [];
|
||||
|
||||
const knowledgeBase = typeof AIDatabase.getKnowledge === "function"
|
||||
? AIDatabase.getKnowledge(25)
|
||||
: [];
|
||||
|
||||
if (latestSummary?.summary) {
|
||||
conversation.push({
|
||||
role: "system",
|
||||
@@ -212,6 +223,17 @@ function buildConversationFromMemory(currentMessage) {
|
||||
});
|
||||
}
|
||||
|
||||
if (knowledgeBase.length > 0) {
|
||||
const knowledgeText = knowledgeBase
|
||||
.map((k) => `- ${k.content}`)
|
||||
.join("\n");
|
||||
|
||||
conversation.push({
|
||||
role: "system",
|
||||
content: `Server Knowledge Base (use this to answer questions):\n${knowledgeText}`,
|
||||
});
|
||||
}
|
||||
|
||||
for (const msg of recentMessages) {
|
||||
conversation.push({
|
||||
role: msg.role === "assistant" ? "assistant" : "user",
|
||||
@@ -431,7 +453,7 @@ module.exports = new Event("messageCreate", async (client, message) => {
|
||||
}
|
||||
|
||||
const typingInterval = setInterval(() => {
|
||||
message.channel.sendTyping().catch(() => {});
|
||||
message.channel.sendTyping().catch(() => { });
|
||||
}, 5000);
|
||||
|
||||
try {
|
||||
@@ -475,7 +497,7 @@ module.exports = new Event("messageCreate", async (client, message) => {
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription(
|
||||
supportbotai.Messages?.ErrorResponse ||
|
||||
"I couldn't generate a response.",
|
||||
"I couldn't generate a response.",
|
||||
)
|
||||
.setColor(supportbotai.Embed.Color);
|
||||
|
||||
@@ -530,16 +552,16 @@ module.exports = new Event("messageCreate", async (client, message) => {
|
||||
) {
|
||||
const hintMessage = await message.channel.send(
|
||||
supportbotai.Messages?.SuggestTicketText ||
|
||||
"Need more help? React with 🎫 to open a ticket.",
|
||||
"Need more help? React with 🎫 to open a ticket.",
|
||||
);
|
||||
|
||||
await hintMessage
|
||||
.react(supportbotai.Messages?.SuggestTicketReaction || "🎫")
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
hintMessage.delete().catch(() => {});
|
||||
hintMessage.delete().catch(() => { });
|
||||
},
|
||||
5 * 60 * 1000,
|
||||
);
|
||||
@@ -552,11 +574,11 @@ module.exports = new Event("messageCreate", async (client, message) => {
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription(
|
||||
supportbotai.Messages?.AIError ||
|
||||
supportbotai.Messages?.ErrorResponse ||
|
||||
"There was an error contacting the AI provider.",
|
||||
supportbotai.Messages?.ErrorResponse ||
|
||||
"There was an error contacting the AI provider.",
|
||||
)
|
||||
.setColor(supportbot.Embed.Colours.Error);
|
||||
|
||||
message.reply({ embeds: [embed] }).catch(() => {});
|
||||
message.reply({ embeds: [embed] }).catch(() => { });
|
||||
}
|
||||
});
|
||||
@@ -426,4 +426,13 @@ module.exports = {
|
||||
)
|
||||
.all(limit);
|
||||
},
|
||||
|
||||
clearKnowledge() {
|
||||
db.prepare(`DELETE FROM ai_knowledge`).run();
|
||||
},
|
||||
|
||||
clearConversations(channelId) {
|
||||
if (!channelId) return;
|
||||
db.prepare(`DELETE FROM ai_conversations WHERE channel_id = ?`).run(channelId);
|
||||
},
|
||||
};
|
||||
+29
-18
@@ -119,28 +119,39 @@ class Client extends Discord.Client {
|
||||
if (supportbot.General.Addons.Enabled) {
|
||||
const addonFiles = fs.readdirSync("./Addons").filter((file) => file.endsWith(".js"));
|
||||
|
||||
const addons = addonFiles.map((file) => {
|
||||
const addon = require(`../Addons/${file}`);
|
||||
addon.name = file.split(".")[0];
|
||||
return addon;
|
||||
});
|
||||
|
||||
section("Addons");
|
||||
|
||||
addons.forEach((addon) => {
|
||||
console.log(`${c.green}✓${c.reset} ${c.white}${addon.name}${c.reset}`);
|
||||
addonFiles.forEach((file) => {
|
||||
let addon = require(`../Addons/${file}`);
|
||||
|
||||
// Handle single or multiple addons per file
|
||||
const addonArray = Array.isArray(addon) ? addon : [addon];
|
||||
|
||||
addonArray.forEach((item, index) => {
|
||||
// If name is missing, use filename (handling index for arrays)
|
||||
if (!item.name) {
|
||||
item.name = index === 0 ? file.split(".")[0] : `${file.split(".")[0]}_${index}`;
|
||||
}
|
||||
|
||||
if (addon instanceof Command) {
|
||||
this.commands.set(addon.name, addon);
|
||||
}
|
||||
console.log(`${c.green}✓${c.reset} ${c.white}${item.name}${c.reset}`);
|
||||
|
||||
if (addon.events && Array.isArray(addon.events)) {
|
||||
addon.events.forEach((event) => {
|
||||
if (event instanceof Event) {
|
||||
this.on(event.event, (...args) => event.run(this, ...args));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (item instanceof Command) {
|
||||
this.commands.set(item.name, item);
|
||||
}
|
||||
|
||||
if (item.events && Array.isArray(item.events)) {
|
||||
item.events.forEach((event) => {
|
||||
if (event instanceof Event) {
|
||||
this.on(event.event, (...args) => event.run(this, ...args));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If it's a direct Event instance
|
||||
if (item instanceof Event) {
|
||||
this.on(item.event, (...args) => item.run(this, ...args));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,15 @@ const client = new Client({
|
||||
intents: ['Guilds', 'GuildMembers', 'GuildMessages', 'MessageContent']
|
||||
});
|
||||
|
||||
const APIServer = require("./API/server.js");
|
||||
|
||||
client.start(supportbot.General.Token);
|
||||
|
||||
client.once('ready', () => {
|
||||
const api = new APIServer(client);
|
||||
api.start();
|
||||
});
|
||||
|
||||
// SupportBot - New Logging System
|
||||
|
||||
const logTypes = ["Output", "Warn", "Error"];
|
||||
|
||||
+5
-1
@@ -21,7 +21,9 @@
|
||||
"@vitalets/google-translate-api": "^9.2.1",
|
||||
"axios": "^1.7.2",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"cors": "^2.8.6",
|
||||
"discord.js": "^14.13.0",
|
||||
"express": "^5.2.1",
|
||||
"figlet": "^1.9.3",
|
||||
"google-translate-api": "^2.3.0",
|
||||
"gradient-string": "^3.0.0",
|
||||
@@ -29,6 +31,8 @@
|
||||
"js-yaml": "^4.1.0",
|
||||
"moment": "^2.29.3",
|
||||
"ms": "^2.1.3",
|
||||
"openai": "^4.50.0"
|
||||
"openai": "^4.50.0",
|
||||
"systeminformation": "^5.31.5",
|
||||
"yaml": "^2.8.3"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user