Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
033f32d596 | ||
|
|
d5fa42b55d |
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 (
|
if (
|
||||||
!interaction.member.roles.cache.has(Admin.id) &&
|
!interaction.member.roles.cache.has(Admin.id) &&
|
||||||
!interaction.member.roles.cache.has(Moderator.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({
|
return interaction.reply({
|
||||||
embeds: [NoPerms],
|
embeds: [NoPerms],
|
||||||
@@ -270,7 +270,7 @@ module.exports = new Command({
|
|||||||
await interaction.channel.setParent(targetCategory.id);
|
await interaction.channel.setParent(targetCategory.id);
|
||||||
|
|
||||||
if (oldRoleId && oldRoleId !== targetRoleId) {
|
if (oldRoleId && oldRoleId !== targetRoleId) {
|
||||||
await interaction.channel.permissionOverwrites.delete(oldRoleId).catch(() => {});
|
await interaction.channel.permissionOverwrites.delete(oldRoleId).catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetRoleId) {
|
if (targetRoleId) {
|
||||||
@@ -298,7 +298,7 @@ module.exports = new Command({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (interaction.channel.name !== newChannelName) {
|
if (interaction.channel.name !== newChannelName) {
|
||||||
await interaction.channel.setName(newChannelName).catch(() => {});
|
await interaction.channel.setName(newChannelName).catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
const deptEmoji = departmentConfig.Emoji || "🎫";
|
const deptEmoji = departmentConfig.Emoji || "🎫";
|
||||||
@@ -354,7 +354,7 @@ module.exports = new Command({
|
|||||||
const newChannelName = buildTicketChannelName(ticketNumber, level, departmentConfig);
|
const newChannelName = buildTicketChannelName(ticketNumber, level, departmentConfig);
|
||||||
|
|
||||||
if (interaction.channel.name !== newChannelName) {
|
if (interaction.channel.name !== newChannelName) {
|
||||||
await interaction.channel.setName(newChannelName).catch(() => {});
|
await interaction.channel.setName(newChannelName).catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
const priorityEmoji = priorityConfig.Emoji || "🟡";
|
const priorityEmoji = priorityConfig.Emoji || "🟡";
|
||||||
@@ -448,7 +448,7 @@ module.exports = new Command({
|
|||||||
)
|
)
|
||||||
.setColor(supportbot.Embed.Colours.General);
|
.setColor(supportbot.Embed.Colours.General);
|
||||||
|
|
||||||
await userToAdd.send({ embeds: [addedToTicketEmbed] }).catch(() => {});
|
await userToAdd.send({ embeds: [addedToTicketEmbed] }).catch(() => { });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error adding user to the ticket:", err);
|
console.error("Error adding user to the ticket:", err);
|
||||||
@@ -517,7 +517,7 @@ module.exports = new Command({
|
|||||||
)
|
)
|
||||||
.setColor(supportbot.Embed.Colours.General);
|
.setColor(supportbot.Embed.Colours.General);
|
||||||
|
|
||||||
await userToRemove.send({ embeds: [removedFromTicketEmbed] }).catch(() => {});
|
await userToRemove.send({ embeds: [removedFromTicketEmbed] }).catch(() => { });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error removing user from the ticket:", err);
|
console.error("Error removing user from the ticket:", err);
|
||||||
@@ -633,7 +633,7 @@ module.exports = new Command({
|
|||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
|
|
||||||
await interaction.channel.delete().catch(() => {});
|
await interaction.channel.delete().catch(() => { });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error closing ticket:", err);
|
console.error("Error closing ticket:", err);
|
||||||
|
|
||||||
|
|||||||
@@ -595,8 +595,12 @@ module.exports = new Command({
|
|||||||
|
|
||||||
async run(interaction) {
|
async run(interaction) {
|
||||||
try {
|
try {
|
||||||
|
if (!interaction.deferred && !interaction.replied) {
|
||||||
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral }).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
if (await isBlacklisted(interaction.user.id)) {
|
if (await isBlacklisted(interaction.user.id)) {
|
||||||
return interaction.reply({
|
return interaction.editReply({
|
||||||
content: msgconfig.Ticket.Blacklisted,
|
content: msgconfig.Ticket.Blacklisted,
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
@@ -620,7 +624,7 @@ module.exports = new Command({
|
|||||||
(t) => t.user === interaction.user.id && t.open,
|
(t) => t.user === interaction.user.id && t.open,
|
||||||
).length >= supportbot.Ticket.TicketsPerUser
|
).length >= supportbot.Ticket.TicketsPerUser
|
||||||
) {
|
) {
|
||||||
return interaction.reply({
|
return interaction.editReply({
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
title: "Too Many Tickets!",
|
title: "Too Many Tickets!",
|
||||||
@@ -668,7 +672,7 @@ module.exports = new Command({
|
|||||||
(ticketChannel) => ticketChannel.name === ticketChannelName,
|
(ticketChannel) => ticketChannel.name === ticketChannelName,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return await interaction.reply({
|
return await interaction.editReply({
|
||||||
embeds: [TicketExists],
|
embeds: [TicketExists],
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
@@ -691,7 +695,7 @@ module.exports = new Command({
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!Staff || !Admin) {
|
if (!Staff || !Admin) {
|
||||||
return interaction.reply({
|
return interaction.editReply({
|
||||||
content:
|
content:
|
||||||
"Some roles seem to be missing!\nPlease check for errors when starting the bot.",
|
"Some roles seem to be missing!\nPlease check for errors when starting the bot.",
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
@@ -731,7 +735,7 @@ module.exports = new Command({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!category) {
|
if (!category) {
|
||||||
return interaction.reply({
|
return interaction.editReply({
|
||||||
content: "The ticket category does not exist!",
|
content: "The ticket category does not exist!",
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
@@ -758,7 +762,7 @@ module.exports = new Command({
|
|||||||
if (secondaryCategory) {
|
if (secondaryCategory) {
|
||||||
await ticketChannel.setParent(secondaryCategory.id);
|
await ticketChannel.setParent(secondaryCategory.id);
|
||||||
} else {
|
} else {
|
||||||
return interaction.reply({
|
return interaction.editReply({
|
||||||
content: "The secondary ticket category does not exist!",
|
content: "The secondary ticket category does not exist!",
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
@@ -844,7 +848,7 @@ module.exports = new Command({
|
|||||||
)
|
)
|
||||||
.setColor(supportbot.Embed.Colours.General);
|
.setColor(supportbot.Embed.Colours.General);
|
||||||
|
|
||||||
await interaction.reply({
|
await interaction.editReply({
|
||||||
embeds: [CreatedTicket],
|
embeds: [CreatedTicket],
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
@@ -1050,7 +1054,7 @@ module.exports = new Command({
|
|||||||
console.error("Error in run method:", error);
|
console.error("Error in run method:", error);
|
||||||
|
|
||||||
if (!interaction.replied && !interaction.deferred) {
|
if (!interaction.replied && !interaction.deferred) {
|
||||||
return interaction.reply({
|
return interaction.editReply({
|
||||||
content: "An error occurred while creating the ticket.",
|
content: "An error occurred while creating the ticket.",
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const {
|
|||||||
SeparatorBuilder,
|
SeparatorBuilder,
|
||||||
SectionBuilder,
|
SectionBuilder,
|
||||||
StringSelectMenuBuilder,
|
StringSelectMenuBuilder,
|
||||||
|
ThumbnailBuilder,
|
||||||
|
EmbedBuilder,
|
||||||
} = require("discord.js");
|
} = require("discord.js");
|
||||||
const yaml = require("js-yaml");
|
const yaml = require("js-yaml");
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ module.exports = new Command({
|
|||||||
try {
|
try {
|
||||||
const oldMessage = await channel.messages.fetch(panelRow.message_id);
|
const oldMessage = await channel.messages.fetch(panelRow.message_id);
|
||||||
if (oldMessage) {
|
if (oldMessage) {
|
||||||
await oldMessage.delete().catch(() => {});
|
await oldMessage.delete().catch(() => { });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`[TICKET PANEL] Previous panel message not found, creating a new one.`);
|
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")
|
.setCustomId(panelconfig.DepartmentMenu?.CustomId || "ticketdepartmentselect")
|
||||||
.setPlaceholder(
|
.setPlaceholder(
|
||||||
panelconfig.DepartmentMenu?.Placeholder ||
|
panelconfig.DepartmentMenu?.Placeholder ||
|
||||||
departmentSystem.Placeholder ||
|
departmentSystem.Placeholder ||
|
||||||
panelconfig.Button?.Text ||
|
panelconfig.Button?.Text ||
|
||||||
"Choose a ticket department",
|
"Choose a ticket department",
|
||||||
)
|
)
|
||||||
.addOptions(departmentOptions);
|
.addOptions(departmentOptions);
|
||||||
} else {
|
} else {
|
||||||
@@ -172,6 +174,8 @@ module.exports = new Command({
|
|||||||
panelconfig.Style?.Title || "## SupportBot Ticket Creation",
|
panelconfig.Style?.Title || "## SupportBot Ticket Creation",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const rawTitle = (panelconfig.Style?.Title || "SupportBot Ticket Creation").replace(/^#+\s*/, "");
|
||||||
|
|
||||||
const panelDesc = new TextDisplayBuilder().setContent(
|
const panelDesc = new TextDisplayBuilder().setContent(
|
||||||
panelconfig.Style?.Description || "Click on the button below to create a support ticket.",
|
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));
|
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") {
|
if (showImage && layout === "1") {
|
||||||
const panelImage = new MediaGalleryBuilder().addItems([
|
const panelImage = new MediaGalleryBuilder().addItems([
|
||||||
{
|
{
|
||||||
@@ -275,9 +322,9 @@ module.exports = new Command({
|
|||||||
return interaction.reply({
|
return interaction.reply({
|
||||||
content: shouldUseDepartments
|
content: shouldUseDepartments
|
||||||
? getPanelMessage(
|
? getPanelMessage(
|
||||||
"PanelSentWithDepartments",
|
"PanelSentWithDepartments",
|
||||||
"Ticket panel message with department selection has been sent!",
|
"Ticket panel message with department selection has been sent!",
|
||||||
)
|
)
|
||||||
: getPanelMessage("PanelSent", "Ticket panel message has been sent!"),
|
: getPanelMessage("PanelSent", "Ticket panel message has been sent!"),
|
||||||
flags: MessageFlags.Ephemeral,
|
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"]
|
Permission: ["Administrator"]
|
||||||
Enabled: true
|
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
|
# Command Description Configuration
|
||||||
# ---------------------------------------
|
# ---------------------------------------
|
||||||
|
|||||||
@@ -8,12 +8,13 @@
|
|||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
General:
|
General:
|
||||||
Provider: "openai"
|
Provider: "claude"
|
||||||
Model: "gpt-5.4"
|
Model: "claude-sonnet-4-6"
|
||||||
Model_API_Key: "AI_API_KEY"
|
Model_API_Key: "MODEL_API_KEY"
|
||||||
# Providers Available:
|
# Providers Available:
|
||||||
# "openai" - Use OpenAI's GPT models. Example: "gpt-5.4" "gpt-5-mini"
|
# "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"
|
# "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
|
Tokens: 1200
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ Features:
|
|||||||
SuggestTickets: true # Should SupportBot AI suggest creating a ticket if the user needs more help?
|
SuggestTickets: true # Should SupportBot AI suggest creating a ticket if the user needs more help?
|
||||||
|
|
||||||
Channels:
|
Channels:
|
||||||
AIChannel: "CHANNEL_ID"
|
AIChannel: "1469028791312715902"
|
||||||
|
|
||||||
Embed:
|
Embed:
|
||||||
Color: "#45b463"
|
Color: "#45b463"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#
|
#
|
||||||
# SupportBot Configuration (supportbot.yml)
|
# SupportBot Configuration (supportbot.yml)
|
||||||
|
|
||||||
SupportBot_Version: "v26.0.0"
|
SupportBot_Version: "v26.1 DEV-2"
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# General
|
# General
|
||||||
@@ -154,7 +154,7 @@ Ticket:
|
|||||||
DMOnRemove: true # DM the user when they are removed from the 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
|
DMTranscript: true # DM the user a transcript of the ticket when it is closed
|
||||||
|
|
||||||
TicketHome: "CHANNEL_ID"
|
TicketHome: "1469028381638000725"
|
||||||
|
|
||||||
# What is TicketHome?
|
# What is TicketHome?
|
||||||
# - Channel where the Ticket creation panel is sent.
|
# - Channel where the Ticket creation panel is sent.
|
||||||
@@ -237,13 +237,14 @@ Ticket:
|
|||||||
# -------------------------
|
# -------------------------
|
||||||
|
|
||||||
DepartmentSystem:
|
DepartmentSystem:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
Placeholder: "Choose a ticket department"
|
Placeholder: "Choose a ticket department"
|
||||||
DefaultDepartment: "general"
|
DefaultDepartment: "general"
|
||||||
|
|
||||||
Departments:
|
Departments:
|
||||||
general:
|
general:
|
||||||
Name: "General Support"
|
Name: "General Support"
|
||||||
|
Description: "Open a general support ticket"
|
||||||
Emoji: "🎫"
|
Emoji: "🎫"
|
||||||
Category: "CATEGORY_ID"
|
Category: "CATEGORY_ID"
|
||||||
Role: "ROLE_ID"
|
Role: "ROLE_ID"
|
||||||
@@ -255,6 +256,7 @@ Ticket:
|
|||||||
|
|
||||||
purchase:
|
purchase:
|
||||||
Name: "Purchase Support"
|
Name: "Purchase Support"
|
||||||
|
Description: "Get help with store purchases"
|
||||||
Emoji: "💳"
|
Emoji: "💳"
|
||||||
Category: "CATEGORY_ID"
|
Category: "CATEGORY_ID"
|
||||||
Role: "ROLE_ID"
|
Role: "ROLE_ID"
|
||||||
@@ -267,6 +269,7 @@ Ticket:
|
|||||||
|
|
||||||
reports:
|
reports:
|
||||||
Name: "Player Report"
|
Name: "Player Report"
|
||||||
|
Description: "Report a player for breaking the rules"
|
||||||
Emoji: "🚨"
|
Emoji: "🚨"
|
||||||
Category: "CATEGORY_ID"
|
Category: "CATEGORY_ID"
|
||||||
Role: "ROLE_ID"
|
Role: "ROLE_ID"
|
||||||
@@ -279,6 +282,7 @@ Ticket:
|
|||||||
|
|
||||||
appeals:
|
appeals:
|
||||||
Name: "Ban Appeals"
|
Name: "Ban Appeals"
|
||||||
|
Description: "Appeal a server punishment"
|
||||||
Emoji: "⚖️"
|
Emoji: "⚖️"
|
||||||
Category: "CATEGORY_ID"
|
Category: "CATEGORY_ID"
|
||||||
Role: "ROLE_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!"
|
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"
|
Image: "https://i.imgur.com/dejQvkg.png"
|
||||||
Color: "#45b463"
|
Color: "#45b463"
|
||||||
Layout: "3"
|
Layout: "4"
|
||||||
|
|
||||||
# Available Layout Styles
|
# Available Layout Styles
|
||||||
# "1" - Style 1 - Title & Text, Button at the top -- Image at the bottom
|
# "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
|
# "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
|
# "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:
|
Button:
|
||||||
Text: "Create Ticket"
|
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 openai = require("./Models/openai.js");
|
||||||
const groq = require("./Models/groq.js");
|
const groq = require("./Models/groq.js");
|
||||||
|
const claude = require("./Models/claude.js");
|
||||||
|
|
||||||
const PASTEBIN_API_KEY = supportbotai.General.PastebinAPI_Key;
|
const PASTEBIN_API_KEY = supportbotai.General.PastebinAPI_Key;
|
||||||
const PASTEBIN_API_URL = supportbotai.General.PastebinAPI_URL;
|
const PASTEBIN_API_URL = supportbotai.General.PastebinAPI_URL;
|
||||||
@@ -57,6 +58,9 @@ function getProvider() {
|
|||||||
case "groq":
|
case "groq":
|
||||||
return groq;
|
return groq;
|
||||||
|
|
||||||
|
case "claude":
|
||||||
|
return claude;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported AI provider: ${provider}`);
|
throw new Error(`Unsupported AI provider: ${provider}`);
|
||||||
}
|
}
|
||||||
@@ -103,6 +107,9 @@ function buildSystemPrompt(message) {
|
|||||||
sections.push(...supportbotai.CustomInstructions);
|
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(
|
sections.push(
|
||||||
`The current guild is: ${message.guild?.name || "Unknown Guild"}`,
|
`The current guild is: ${message.guild?.name || "Unknown Guild"}`,
|
||||||
);
|
);
|
||||||
@@ -131,24 +138,24 @@ function buildConversationFromMemory(currentMessage) {
|
|||||||
const recentMessages =
|
const recentMessages =
|
||||||
typeof AIDatabase.getRecentMessagesByScope === "function"
|
typeof AIDatabase.getRecentMessagesByScope === "function"
|
||||||
? AIDatabase.getRecentMessagesByScope({
|
? AIDatabase.getRecentMessagesByScope({
|
||||||
guildId: currentMessage.guild?.id || null,
|
guildId: currentMessage.guild?.id || null,
|
||||||
channelId: currentMessage.channel?.id || null,
|
channelId: currentMessage.channel?.id || null,
|
||||||
limit: maxRecentMessages,
|
limit: maxRecentMessages,
|
||||||
scope: memoryScope,
|
scope: memoryScope,
|
||||||
})
|
})
|
||||||
: AIDatabase.getRecentMessages(
|
: AIDatabase.getRecentMessages(
|
||||||
currentMessage.channel?.id,
|
currentMessage.channel?.id,
|
||||||
maxRecentMessages,
|
maxRecentMessages,
|
||||||
);
|
);
|
||||||
|
|
||||||
const latestSummary =
|
const latestSummary =
|
||||||
memoryConfig.StoreSummaries !== false
|
memoryConfig.StoreSummaries !== false
|
||||||
? typeof AIDatabase.getLatestSummaryByScope === "function"
|
? typeof AIDatabase.getLatestSummaryByScope === "function"
|
||||||
? AIDatabase.getLatestSummaryByScope({
|
? AIDatabase.getLatestSummaryByScope({
|
||||||
guildId: currentMessage.guild?.id || null,
|
guildId: currentMessage.guild?.id || null,
|
||||||
channelId: currentMessage.channel?.id || null,
|
channelId: currentMessage.channel?.id || null,
|
||||||
scope: memoryScope,
|
scope: memoryScope,
|
||||||
})
|
})
|
||||||
: AIDatabase.getLatestSummary(currentMessage.channel?.id)
|
: AIDatabase.getLatestSummary(currentMessage.channel?.id)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -156,29 +163,33 @@ function buildConversationFromMemory(currentMessage) {
|
|||||||
memoryConfig.StoreFacts !== false
|
memoryConfig.StoreFacts !== false
|
||||||
? typeof AIDatabase.getFactsByScope === "function"
|
? typeof AIDatabase.getFactsByScope === "function"
|
||||||
? AIDatabase.getFactsByScope({
|
? AIDatabase.getFactsByScope({
|
||||||
guildId: currentMessage.guild?.id || null,
|
guildId: currentMessage.guild?.id || null,
|
||||||
channelId: currentMessage.channel?.id || null,
|
channelId: currentMessage.channel?.id || null,
|
||||||
scope: memoryScope,
|
scope: memoryScope,
|
||||||
})
|
})
|
||||||
: AIDatabase.getFacts(currentMessage.channel?.id)
|
: AIDatabase.getFacts(currentMessage.channel?.id)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const userFacts =
|
const userFacts =
|
||||||
memoryConfig.StoreFacts !== false &&
|
memoryConfig.StoreFacts !== false &&
|
||||||
typeof AIDatabase.getUserFactsByScope === "function"
|
typeof AIDatabase.getUserFactsByScope === "function"
|
||||||
? AIDatabase.getUserFactsByScope({
|
? AIDatabase.getUserFactsByScope({
|
||||||
guildId: currentMessage.guild?.id || null,
|
guildId: currentMessage.guild?.id || null,
|
||||||
userId: currentMessage.author?.id || null,
|
userId: currentMessage.author?.id || null,
|
||||||
scope: memoryScope,
|
scope: memoryScope,
|
||||||
})
|
})
|
||||||
: memoryConfig.StoreFacts !== false &&
|
: memoryConfig.StoreFacts !== false &&
|
||||||
typeof AIDatabase.getUserFacts === "function"
|
typeof AIDatabase.getUserFacts === "function"
|
||||||
? AIDatabase.getUserFacts(
|
? AIDatabase.getUserFacts(
|
||||||
currentMessage.guild?.id,
|
currentMessage.guild?.id,
|
||||||
currentMessage.author?.id,
|
currentMessage.author?.id,
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const knowledgeBase = typeof AIDatabase.getKnowledge === "function"
|
||||||
|
? AIDatabase.getKnowledge(25)
|
||||||
|
: [];
|
||||||
|
|
||||||
if (latestSummary?.summary) {
|
if (latestSummary?.summary) {
|
||||||
conversation.push({
|
conversation.push({
|
||||||
role: "system",
|
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) {
|
for (const msg of recentMessages) {
|
||||||
conversation.push({
|
conversation.push({
|
||||||
role: msg.role === "assistant" ? "assistant" : "user",
|
role: msg.role === "assistant" ? "assistant" : "user",
|
||||||
@@ -431,7 +453,7 @@ module.exports = new Event("messageCreate", async (client, message) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const typingInterval = setInterval(() => {
|
const typingInterval = setInterval(() => {
|
||||||
message.channel.sendTyping().catch(() => {});
|
message.channel.sendTyping().catch(() => { });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -475,7 +497,7 @@ module.exports = new Event("messageCreate", async (client, message) => {
|
|||||||
const embed = new Discord.EmbedBuilder()
|
const embed = new Discord.EmbedBuilder()
|
||||||
.setDescription(
|
.setDescription(
|
||||||
supportbotai.Messages?.ErrorResponse ||
|
supportbotai.Messages?.ErrorResponse ||
|
||||||
"I couldn't generate a response.",
|
"I couldn't generate a response.",
|
||||||
)
|
)
|
||||||
.setColor(supportbotai.Embed.Color);
|
.setColor(supportbotai.Embed.Color);
|
||||||
|
|
||||||
@@ -530,16 +552,16 @@ module.exports = new Event("messageCreate", async (client, message) => {
|
|||||||
) {
|
) {
|
||||||
const hintMessage = await message.channel.send(
|
const hintMessage = await message.channel.send(
|
||||||
supportbotai.Messages?.SuggestTicketText ||
|
supportbotai.Messages?.SuggestTicketText ||
|
||||||
"Need more help? React with 🎫 to open a ticket.",
|
"Need more help? React with 🎫 to open a ticket.",
|
||||||
);
|
);
|
||||||
|
|
||||||
await hintMessage
|
await hintMessage
|
||||||
.react(supportbotai.Messages?.SuggestTicketReaction || "🎫")
|
.react(supportbotai.Messages?.SuggestTicketReaction || "🎫")
|
||||||
.catch(() => {});
|
.catch(() => { });
|
||||||
|
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => {
|
() => {
|
||||||
hintMessage.delete().catch(() => {});
|
hintMessage.delete().catch(() => { });
|
||||||
},
|
},
|
||||||
5 * 60 * 1000,
|
5 * 60 * 1000,
|
||||||
);
|
);
|
||||||
@@ -552,11 +574,11 @@ module.exports = new Event("messageCreate", async (client, message) => {
|
|||||||
const embed = new Discord.EmbedBuilder()
|
const embed = new Discord.EmbedBuilder()
|
||||||
.setDescription(
|
.setDescription(
|
||||||
supportbotai.Messages?.AIError ||
|
supportbotai.Messages?.AIError ||
|
||||||
supportbotai.Messages?.ErrorResponse ||
|
supportbotai.Messages?.ErrorResponse ||
|
||||||
"There was an error contacting the AI provider.",
|
"There was an error contacting the AI provider.",
|
||||||
)
|
)
|
||||||
.setColor(supportbot.Embed.Colours.Error);
|
.setColor(supportbot.Embed.Colours.Error);
|
||||||
|
|
||||||
message.reply({ embeds: [embed] }).catch(() => {});
|
message.reply({ embeds: [embed] }).catch(() => { });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -426,4 +426,13 @@ module.exports = {
|
|||||||
)
|
)
|
||||||
.all(limit);
|
.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) {
|
if (supportbot.General.Addons.Enabled) {
|
||||||
const addonFiles = fs.readdirSync("./Addons").filter((file) => file.endsWith(".js"));
|
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");
|
section("Addons");
|
||||||
|
|
||||||
addons.forEach((addon) => {
|
addonFiles.forEach((file) => {
|
||||||
console.log(`${c.green}✓${c.reset} ${c.white}${addon.name}${c.reset}`);
|
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) {
|
console.log(`${c.green}✓${c.reset} ${c.white}${item.name}${c.reset}`);
|
||||||
this.commands.set(addon.name, addon);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addon.events && Array.isArray(addon.events)) {
|
if (item instanceof Command) {
|
||||||
addon.events.forEach((event) => {
|
this.commands.set(item.name, item);
|
||||||
if (event instanceof Event) {
|
}
|
||||||
this.on(event.event, (...args) => event.run(this, ...args));
|
|
||||||
}
|
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']
|
intents: ['Guilds', 'GuildMembers', 'GuildMessages', 'MessageContent']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const APIServer = require("./API/server.js");
|
||||||
|
|
||||||
client.start(supportbot.General.Token);
|
client.start(supportbot.General.Token);
|
||||||
|
|
||||||
|
client.once('ready', () => {
|
||||||
|
const api = new APIServer(client);
|
||||||
|
api.start();
|
||||||
|
});
|
||||||
|
|
||||||
// SupportBot - New Logging System
|
// SupportBot - New Logging System
|
||||||
|
|
||||||
const logTypes = ["Output", "Warn", "Error"];
|
const logTypes = ["Output", "Warn", "Error"];
|
||||||
|
|||||||
+5
-1
@@ -21,7 +21,9 @@
|
|||||||
"@vitalets/google-translate-api": "^9.2.1",
|
"@vitalets/google-translate-api": "^9.2.1",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"better-sqlite3": "^12.2.0",
|
"better-sqlite3": "^12.2.0",
|
||||||
|
"cors": "^2.8.6",
|
||||||
"discord.js": "^14.13.0",
|
"discord.js": "^14.13.0",
|
||||||
|
"express": "^5.2.1",
|
||||||
"figlet": "^1.9.3",
|
"figlet": "^1.9.3",
|
||||||
"google-translate-api": "^2.3.0",
|
"google-translate-api": "^2.3.0",
|
||||||
"gradient-string": "^3.0.0",
|
"gradient-string": "^3.0.0",
|
||||||
@@ -29,6 +31,8 @@
|
|||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"moment": "^2.29.3",
|
"moment": "^2.29.3",
|
||||||
"ms": "^2.1.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