Compare commits

...
107 Commits
Author SHA1 Message Date
1tzEmerald 7383bd2c48 8.4-DEV-2
Added Buttons to WelcomeMessage
Added Components v2 Support for Welcome Message
Added improved logging system
2025-09-22 18:36:04 +01:00
1tzEmerald a05d65099b 8.4 DEV 1
Changed JSON to SQLlite
Added Suggestion Forums
Added /suggestadmin
2025-09-20 23:09:22 +01:00
1tzEmerald c6db929d12 8.4-Dev 1
Added Suggestions mode switch
2025-08-07 13:54:35 +01:00
1tzEmerald 5c6a9f7306 Update close.js 2024-11-10 13:30:42 +00:00
1tzEmerald 0b8b092cb9 Update profile.js 2024-11-09 13:03:55 +00:00
1tzEmerald 5a1c4ad99c Update close.js 2024-11-09 02:46:25 +00:00
1tzEmerald d9cb72f22e Update close.js 2024-11-09 02:44:48 +00:00
1tzEmerald fece90d2bd Update close.js 2024-11-09 02:37:26 +00:00
1tzEmerald f34158caa6 Update close.js 2024-11-09 02:32:52 +00:00
1tzEmerald 13590b08a8 Update ticket.js 2024-11-09 01:07:16 +00:00
1tzEmerald 72b8c2247e Merge branch 'release' into dev 2024-11-09 00:45:28 +00:00
1tzEmerald 925504a928 Merge branch 'dev' of https://github.com/Emerald-Services/SupportBot into dev 2024-11-09 00:37:53 +00:00
1tzEmerald 236d67707f 8.3 Update 2024-11-09 00:37:51 +00:00
1tzEmerald 753f432c95 Merge pull request #179 from Emerald-Services/1tzemerald-patch-4
Update package.json
2024-10-11 15:04:37 +01:00
1tzEmerald c824bc51b2 Update package.json 2024-10-11 15:03:47 +01:00
1tzEmerald 39ee540b55 Merge pull request #177 from Emerald-Services/dev
8.2
2024-10-11 15:00:05 +01:00
1tzEmerald 903b214051 Merge branch 'release' into dev 2024-10-11 14:59:50 +01:00
1tzEmerald ac0daf5fe7 8.2 2024-10-10 19:14:45 +01:00
ItsNyoty 80e38a07e9 Merge pull request #175 from Emerald-Services/ItsNyoty-patch-1
Update README.md
2024-09-19 13:06:05 +02:00
ItsNyoty d2598e09f7 Update README.md 2024-09-19 13:05:26 +02:00
ItsNyoty 28aab47150 Update README.md 2024-09-19 13:04:44 +02:00
1tzEmerald 81bda67ae4 Merge pull request #172 from Emerald-Services/1tzemerald-patch-3
Update supportbot.yml
2024-07-30 17:58:41 +01:00
1tzEmerald 25bbd1ff85 Update supportbot.yml 2024-07-30 17:58:01 +01:00
1tzEmerald 164d695a5f Merge pull request #171 from Emerald-Services/1tzemerald-patch-2
Create 0000000000000000.json
2024-07-30 17:57:33 +01:00
1tzEmerald 57e4a3e77c Create 0000000000000000.json 2024-07-30 17:57:12 +01:00
1tzEmerald a6ac532b2b Merge pull request #169 from Emerald-Services/dev
8.0.1 Dev -> Release Branch
2024-07-30 17:47:13 +01:00
1tzEmerald 4f14722e14 8.0.1 Update 2024-07-30 17:45:35 +01:00
1tzEmerald 9a6eece7a1 v8.1 2024-07-09 22:49:49 +01:00
1tzEmerald fd65d0a9a7 8.1-BETA 2024-07-03 13:46:57 +01:00
ItsNyoty 48500fe8a4 Update README.md 2024-07-03 13:42:46 +02:00
1tzEmerald 8d8d9e4bee 8.0.1 Bug Fix 2024-06-13 18:01:41 +01:00
1tzEmerald dcbddf9f23 8.0.1 - Bug Fix 2024-06-13 17:36:44 +01:00
1tzEmerald 7449c0a61a License files 2024-06-12 12:55:45 +01:00
1tzEmerald c95b8ccb32 SupportBot 8.0 2024-06-12 12:54:31 +01:00
1tzEmerald 2c5bfe8e33 Merge pull request #135 from ItsNyoty/patch-1
Update messages.yml
2022-09-05 13:42:52 +01:00
ItsNyoty a3bbd97333 Update messages.yml
TicketExists wasn't defined before
2022-08-29 14:58:01 +02:00
1tzEmerald 4ea488951d Delete oldconfig.yml 2022-08-12 01:38:07 +01:00
1tzEmerald 481424d5a6 Update LICENSE.md 2022-08-12 01:37:49 +01:00
1tzEmerald fe82e96cc1 Update LICENSE.md 2022-08-12 01:37:31 +01:00
1tzEmerald 976258c959 Delete .gitignore 2022-08-12 01:37:14 +01:00
1tzEmerald 83cf0689e2 Delete .DS_Store 2022-08-12 01:37:00 +01:00
ItzEmerald c911cb4423 v8.0 New configuration files 2022-08-12 01:35:36 +01:00
ItzEmerald 4eadef8b65 8.0 Dev 1.2
Reworked configuration files & completed upgrade to V14
2022-08-11 19:19:29 +01:00
ItzEmerald 4e081358ff v8.0-Dev-1.1 2022-07-23 07:50:12 +01:00
ItzEmerald 209ec0b435 v8.0 - Dev-1 2022-07-22 13:19:05 +01:00
Xiled Emerald 6d363abb54 Merge pull request #129 from SypherRed/dev
Better looking readme
2022-07-01 22:19:03 +01:00
SypherRed 6f8b84aeda Update README.md 2022-07-01 23:16:16 +02:00
SypherRed 5b8c5da33a Update README.md 2022-07-01 23:14:01 +02:00
Xiled Emerald b6ef0f95cb Merge pull request #126 from SypherRed/release
Muted Role check and issue fix
2022-06-30 15:49:14 +01:00
SypherRed cce1480a42 remove empty declaration
AboutMe was removed in a previous version.
2022-06-29 13:12:56 +02:00
ItzEmerald 44fbe9f2d6 FINAL 7.7 2022-06-28 03:15:52 +01:00
ItzEmerald 29db6c7d67 7.7 FINAL BETA 2022-06-28 03:14:54 +01:00
SypherRed 26e9b56961 Issue fix with Muted Role check
Fixed the issue to recognize a Muted Role and made changes to ready.js accordingly
2022-06-27 19:03:34 +02:00
SypherRed 979271cfb5 beta push of muted check
push of a check if author has a muted role on ticket opening, if so, decline the interaction

Also either check for missing role or not
2022-06-23 20:58:03 +02:00
ItzEmerald 69f0b00ed3 7.7 2022-06-23 00:05:08 +01:00
ItzEmerald c1a1839457 7.7 BETA 3 2022-06-22 23:30:59 +01:00
Xiled Emerald dd4b53800e Merge pull request #124 from Emerald-Services/add-license-1
Create LICENSE.md
2022-06-22 23:05:38 +01:00
Xiled Emerald 1fc94528fc Merge pull request #123 from SypherRed/release
embed fix and updates
2022-06-22 23:05:25 +01:00
SypherRed bb1a817980 fix previous push of embed
fixed the if-statement of embed
2022-06-22 20:59:45 +02:00
SypherRed 53305acbc7 Fixed embed command
Added a check if the field options are used, if not leave it ignored and don't add some.

This was fixed due to ticket-0038.
2022-06-22 18:21:25 +02:00
Xiled Emerald d0f1bae0bd Create LICENSE.md 2022-06-22 02:24:19 +01:00
SypherRed c0cf3b1bb7 Update several files
* added to .gitignore ".DS_Store"

* changed commented head in "suggest.js" and "translate.js"

* Removed "AboutMe" due it to being unfunctional and can be set in the Developer Portal

* Removed the icon in "ticket-panel.yml"

* Updated dependencies and changed version to match current
2022-06-21 09:54:22 +02:00
Xiled Emerald ce67b668d5 Update Client.js 2022-06-21 01:42:59 +01:00
Xiled Emerald 3273cfd0e1 Update help.js 2022-06-21 01:41:53 +01:00
Xiled Emerald fd992a6514 Update supportbot.yml 2022-06-21 01:41:17 +01:00
Xiled Emerald 550d681fe1 Merge pull request #122 from RandomTomato/release
Added 3 new settings in supportbot.yml for more customization
2022-06-21 01:39:35 +01:00
ItzEmerald 7d29226953 BETA 2 - V7.7 2022-06-21 01:38:22 +01:00
BuildTools 38da72ca91 Added 3 new settings in supportbot.yml for more customization 2022-06-18 11:19:12 +09:30
Xiled Emerald eab11361cb Update ticket-panel-id.json 2022-06-16 22:30:13 +01:00
Xiled Emerald b281bba36f Merge pull request #120 from Emerald-Services/beta
7.6 Release
2022-06-16 22:26:50 +01:00
ItzEmerald 5e38573aed 7.6 2022-06-16 22:12:14 +01:00
ItzEmerald da7b6d487c 7.6-Pre1
Whats New?
  - Ability to add as many departments as you need (Don't go to crazy 😂 )
  - Merged Sypher's Translate & UserInfo Addon into Features

------------

  - Add a role automatically when a user joins the server
  - Announce a welcome message with full cusomtization
  - Announce a leave message with full cusomtization
  - Added "ephermal" to majority messages.

Install modules use - npm install
2022-06-16 02:31:58 +01:00
Xiled Emerald 6bf4e5f4bb Update README.md 2022-06-14 23:02:12 +01:00
Xiled Emerald 213f532a3c Merge pull request #114 from Emerald-Services/dev
7.5 push
2022-02-27 04:37:24 +00:00
ItzEmerald 45bd88e4d2 7.5 commit 2022-02-27 04:30:15 +00:00
Xiled Emerald 4eb8658459 Update supportbot.yml 2022-02-27 00:39:36 +00:00
Noah b37118fee2 Fix addon handler 2022-02-25 21:57:54 -06:00
Noah 026caba68a Fix setFooters 2022-02-25 21:55:33 -06:00
Noah 2003f8801a Fixed ticket perms 2022-02-25 21:51:22 -06:00
Noah 473bacf13a Removed Guild 2022-02-17 19:57:37 -06:00
Noah 8b3f2bc61a Fix missing user or subuser 2022-02-17 19:57:11 -06:00
Noah 3aa373c33c fix bad code 2022-02-11 23:47:06 -06:00
Noah 9b6263e583 Fix DM help menu 2022-02-11 23:41:02 -06:00
Noah 3e066d08ce New Departments 2022-02-11 23:34:38 -06:00
Noah aa48e28162 Fix Typos 2022-02-04 20:00:31 -06:00
Noah dc660ed94a Bug Fixes 2022-02-04 17:56:11 -06:00
Noah e02ecf9553 Bug fixes 2022-02-04 17:55:02 -06:00
Noah 23acc0926f Fix BitField Error 2022-02-04 17:49:02 -06:00
Xiled Emerald 3fc1cd18d5 Merge pull request #109 from Emerald-Services/dev
7.4.2 Pull Request
2022-02-03 17:26:54 +00:00
Noah ebe29b403e Prettier 2022-01-30 00:07:04 -06:00
Noah dcba3312dd Misc 2022-01-29 23:57:10 -06:00
Noah 7fddeeec37 Change Max to 100 2022-01-10 17:48:31 -06:00
Noah b5341b023f Fix embed command 2021-12-29 10:20:29 -06:00
Noah bbb7443ef2 Fix suggest command 2021-12-28 19:06:35 -06:00
Noah 22df347bb9 Merge pull request #108 from astranger1k/dev
Updated Documentation URLs + Added invite URL to log upon launch.
2021-12-23 09:18:47 -06:00
Kieran Davies 9aed6b8f35 Updated Documentation URLs + Added invite URL to log upon launch. 2021-12-23 11:48:51 +00:00
Noah 9e7ec9f1bd 7.4.2 2021-12-19 13:06:32 -06:00
Noah 373f43eac5 Channel can now be id or name 2021-12-19 13:03:33 -06:00
Noah 6c91b2791a Fix users can't send messages 2021-12-19 13:02:43 -06:00
Noah bde62a431f Merge branch 'dev' into release 2021-12-18 22:59:41 -06:00
Noah 615a074540 move filter 2021-12-18 22:59:05 -06:00
Noah 09b335ac46 Update ticket.js 2021-12-18 22:42:51 -06:00
Noah 2701880c77 Merge pull request #106 from Emerald-Services/dev
v7.4.1 Release
2021-12-18 22:37:29 -06:00
Noah 24c8f2fb38 v7.4.1 2021-12-18 22:28:08 -06:00
Noah c3ec158ac3 Fix id 2021-12-18 22:23:18 -06:00
Noah b3690222d0 Fixed id 2021-12-18 22:18:05 -06:00
Noah 4d7583785b Fixed ticket id 2021-12-18 22:14:11 -06:00
60 changed files with 5912 additions and 2252 deletions

No files matched your search

-40
View File
@@ -1,40 +0,0 @@
---
name: "\U0001F41B Bug report"
about: Create a report to help us improve
title: "[BUG]"
labels: Bug
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
@@ -1,19 +0,0 @@
---
name: "❓Suggest an Idea?"
about: Suggest an idea for SupportBot?
title: "[SUGGESTION]"
labels: Suggestion
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-28
View File
@@ -1,28 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [dev, release]
pull_request:
branches: [dev, release]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 17.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
-1
View File
@@ -1 +0,0 @@
node_modules/
-45
View File
@@ -1,45 +0,0 @@
// SupportBot | Emerald Services
// Example (default) Addon
const fs = require("fs");
const Discord = 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"));
// Your Addon Config file! Saved in /Addons/Configs/YourAddonConfig.yml
// const config = yaml.load(fs.readFileSync('./Addons/Configs/YourAddonConfig.yml', 'utf8'));
const { Command, Event } = require("../Structures/Addon");
module.exports.commands = [
/*
// EXAMPLE COMMAND
new Command({
name: "hello",
description: "hello",
options: [],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
const Embed = new Discord.MessageEmbed()
.setDescription("Hello World!")
.setColor(supportbot.InfoColour);
interaction.reply({
embeds: [Embed],
});
},
}),
*/
];
module.exports.events = [
/*
// EXAMPLE EVENT
new Event("ready", async (client) => {
console.log("Bot started!");
}),
*/
];
+62
View File
@@ -0,0 +1,62 @@
const fs = require("fs");
const Discord = 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 Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.Help.Command,
description: cmdconfig.Help.Description,
type: Discord.ApplicationCommandType.ChatInput,
options: [],
permissions: cmdconfig.Help.Permission,
async run(interaction) {
const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) {
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
}
// Separate regular commands and addon commands
let botCommands = "";
let addonCommands = "";
interaction.client.commands.forEach((command) => {
if (command.permissions) {
if (command.addon) {
addonCommands += `\`${command.name}\` `;
} else {
botCommands += `\`${command.name}\` `;
}
}
});
// Create embed
const HelpEmbed1 = new Discord.EmbedBuilder()
.setTitle(supportbot.General.Name + " Commands")
.setThumbnail(interaction.user.displayAvatarURL())
.setColor(supportbot.Embed.Colours.General)
.addFields({
name: "🖥️ Commands\n",
value: botCommands || "No commands available.",
inline: false,
});
HelpEmbed1.setFooter({
text: supportbot.Embed.Footer,
iconURL: interaction.user.displayAvatarURL(),
});
interaction.reply({
flags: Discord.MessageFlags.Ephemeral,
embeds: [HelpEmbed1],
});
},
});
+51
View File
@@ -0,0 +1,51 @@
// SupportBot | Emerald Services
// Info Command
const fs = require("fs");
const Discord = 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 msgconfig = yaml.load(
fs.readFileSync("./Configs/messages.yml", "utf8")
);
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.Info.Command,
description: cmdconfig.Info.Description,
options: [],
permissions: cmdconfig.Info.Permission,
async run(interaction) {
let disableCommand = true;
const InfoButton = new Discord.ButtonBuilder()
.setLabel(msgconfig.Info.Button)
.setURL(msgconfig.Info.URL)
.setStyle("Link");
const inforow = new Discord.ActionRowBuilder().addComponents(InfoButton);
const InfoEmbed = new Discord.EmbedBuilder()
.setURL(msgconfig.Info.URL)
.setTitle(msgconfig.Info.Title)
.setDescription(msgconfig.Info.Description)
.setColor(msgconfig.Info.Colour)
interaction.reply({
embeds: [InfoEmbed],
components: [inforow],
});
},
});
@@ -10,20 +10,22 @@ const supportbot = yaml.load(
);
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.PingCommand,
description: cmdconfig.PingCommandDesc,
name: cmdconfig.Ping.Command,
description: cmdconfig.Ping.Description,
options: [],
permissions: ["SEND_MESSAGES"],
permissions: cmdconfig.Ping.Permission,
async run(interaction) {
const PingEmbed = new Discord.MessageEmbed()
let disableCommand = true;
const PingEmbed = new Discord.EmbedBuilder()
.setDescription(
`:ping_pong: **Ping:** \`${interaction.client.ws.ping} ms\``
)
.setColor(supportbot.EmbedColour);
.setColor(supportbot.Embed.Colours.General);
interaction.reply({
embeds: [PingEmbed],
+296
View File
@@ -0,0 +1,296 @@
const {
EmbedBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
ApplicationCommandType,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ApplicationCommandOptionType
} = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const db = require("../../Structures/Database.js");
const Command = require("../../Structures/Command.js");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const clockedInUsers = new Set();
function updateClockedInStatus(embed, clockedIn) {
embed.data.fields = embed.data.fields.map(f =>
f.name === "Clocked In Status"
? { ...f, value: clockedIn ? "✅ Clocked In" : "❌ Clocked Out" }
: f
);
return embed;
}
module.exports = new Command({
name: cmdconfig.Profile.Command,
description: cmdconfig.Profile.Description,
type: ApplicationCommandType.ChatInput,
permissions: cmdconfig.Profile.Permission,
options: [
{
name: "user",
description: "The user to view the profile of",
type: ApplicationCommandOptionType.User,
required: false
}
],
async run(interaction) {
await interaction.deferReply({ ephemeral: true });
const viewingUser = interaction.options.getUser("user") || interaction.user;
const viewingUserId = viewingUser.id;
const interactionUserId = interaction.user.id;
const member = await interaction.guild.members.fetch(viewingUserId);
const { getRole } = interaction.client;
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!Staff || !Admin) {
return interaction.editReply({
content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.",
ephemeral: true
});
}
const isStaff = member.roles.cache.has(Staff.id) || member.roles.cache.has(Admin.id);
const isOwnProfile = interactionUserId === viewingUserId;
const profileData = db.getProfile(viewingUserId);
let { bio, timezone, clockedIn } = profileData;
clockedIn = Boolean(clockedIn);
let profileEmbed = new EmbedBuilder()
.setTitle(`${viewingUser.username}'s Profile`)
.setColor(supportbot.Embed.Colours.General)
.setThumbnail(viewingUser.displayAvatarURL())
.addFields(
{ name: "Bio", value: bio || "No bio set.", inline: false },
{ name: "Timezone", value: timezone || "No timezone set.", inline: true }
);
if (isStaff) {
profileEmbed.addFields({
name: "Clocked In Status",
value: clockedIn ? "✅ Clocked In" : "❌ Clocked Out",
inline: true
});
}
const buttonRow = new ActionRowBuilder();
if (isOwnProfile) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId("editProfile")
.setLabel("Edit Profile")
.setStyle(ButtonStyle.Secondary)
);
if (isStaff) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId("clockInOut")
.setLabel(clockedIn ? "Clock Out" : "Clock In")
.setStyle(ButtonStyle.Secondary)
);
}
}
if (isStaff && supportbot.Ticket.ClaimTickets.Enabled) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId("viewTicketStats")
.setLabel("View Ticket Stats")
.setStyle(ButtonStyle.Secondary)
);
}
await interaction.editReply({
embeds: [profileEmbed],
components: buttonRow.components.length ? [buttonRow] : []
});
const filter = i =>
["editProfile", "clockInOut", "viewTicketStats", "showOpenTickets", "backToStats", "backToProfile"]
.includes(i.customId) &&
i.user.id === interactionUserId;
const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 });
collector.on("collect", async i => {
if (i.customId === "editProfile") {
const modal = new ModalBuilder()
.setCustomId("editProfileModal")
.setTitle("Edit Profile")
.addComponents(
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId("bio")
.setLabel("Bio")
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder("Enter your bio")
.setValue(String(bio || ""))
.setRequired(false)
),
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId("timezone")
.setLabel("Timezone")
.setStyle(TextInputStyle.Short)
.setPlaceholder("Enter your timezone")
.setValue(String(timezone || ""))
.setRequired(false)
)
);
return i.showModal(modal);
}
if (i.customId === "clockInOut") {
const newStatus = !clockedIn;
db.setClockedIn(viewingUserId, newStatus);
clockedIn = newStatus;
profileEmbed = updateClockedInStatus(profileEmbed, newStatus);
if (newStatus) clockedInUsers.add(viewingUserId);
else clockedInUsers.delete(viewingUserId);
const updatedRow = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId("editProfile")
.setLabel("Edit Profile")
.setStyle(ButtonStyle.Secondary)
);
if (isStaff) {
updatedRow.addComponents(
new ButtonBuilder()
.setCustomId("clockInOut")
.setLabel(newStatus ? "Clock Out" : "Clock In")
.setStyle(ButtonStyle.Secondary)
);
if (supportbot.Ticket.ClaimTickets.Enabled) {
updatedRow.addComponents(
new ButtonBuilder()
.setCustomId("viewTicketStats")
.setLabel("View Ticket Stats")
.setStyle(ButtonStyle.Secondary)
);
}
}
await i.update({ embeds: [profileEmbed], components: [updatedRow] });
const clockEmbed = new EmbedBuilder()
.setTitle(newStatus ? "Clocked In!" : "Clocked Out!")
.setDescription(newStatus ? "You have successfully clocked in." : "You have successfully clocked out.")
.setColor(supportbot.Embed.Colours.General);
return interaction.followUp({ embeds: [clockEmbed], ephemeral: true });
}
if (i.customId === "viewTicketStats") {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === viewingUserId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(t => {
if (t.claimedAt && t.createdAt) {
totalResponseTime += new Date(t.claimedAt) - new Date(t.createdAt);
responseCount++;
}
});
const avgMinutes = responseCount ? Math.round(totalResponseTime / responseCount / 60000) : 0;
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${viewingUserId}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${avgMinutes} minutes`, inline: false }
);
const statsRow = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("showOpenTickets").setLabel("Show Open Tickets").setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId("backToProfile").setLabel("Back to Profile").setStyle(ButtonStyle.Secondary)
);
return i.update({ embeds: [statsEmbed], components: [statsRow] });
}
if (i.customId === "showOpenTickets") {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsOpen = TicketData.tickets.filter(t => t.claimedBy === viewingUserId && t.open);
const openEmbed = new EmbedBuilder()
.setTitle("Active Tickets")
.setDescription(ticketsOpen.map(t => `<#${t.id}>`).join("\n") || "No active tickets.")
.setColor(supportbot.Embed.Colours.General);
const backRow = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("backToStats").setLabel("Back to Stats").setStyle(ButtonStyle.Secondary)
);
return i.update({ embeds: [openEmbed], components: [backRow] });
}
if (i.customId === "backToStats") {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === viewingUserId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(t => {
if (t.claimedAt && t.createdAt) {
totalResponseTime += new Date(t.claimedAt) - new Date(t.createdAt);
responseCount++;
}
});
const avgMinutes = responseCount ? Math.round(totalResponseTime / responseCount / 60000) : 0;
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${viewingUserId}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${avgMinutes} minutes`, inline: false }
);
const statsRow = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("showOpenTickets").setLabel("Show Open Tickets").setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId("backToProfile").setLabel("Back to Profile").setStyle(ButtonStyle.Secondary)
);
return i.update({ embeds: [statsEmbed], components: [statsRow] });
}
if (i.customId === "backToProfile") {
return i.update({
embeds: [profileEmbed],
components: buttonRow.components.length ? [buttonRow] : []
});
}
});
}
});
module.exports.clockedInUsers = clockedInUsers;
+117
View File
@@ -0,0 +1,117 @@
// SupportBot | Emerald Services
// Translate Command
const fs = require("fs");
const Discord = 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 msgconfig = yaml.load(
fs.readFileSync("./Configs/messages.yml", "utf8")
)
const Command = require("../../Structures/Command.js");
const translate = require("@vitalets/google-translate-api");
module.exports = new Command({
name: cmdconfig.Translate.Command,
description: cmdconfig.Translate.Description,
type: Discord.ApplicationCommandType.ChatInput,
options: [
{
type: Discord.ApplicationCommandOptionType.String,
name: "language",
description: "A 2 letter language code",
required: true,
},
{
type: Discord.ApplicationCommandOptionType.String,
name: "text",
description: "The text to translate",
required: true,
}, // The input text for the translation
],
permissions: cmdconfig.Translate.Permission, // The permission the user/role at least requires
async run(interaction) {
let disableCommand = true;
const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin)
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
const NoPerms = new Discord.EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn);
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
)
return interaction.reply({ embeds: [NoPerms] });
const { getChannel } = interaction.client;
let lang = interaction.options.getString("language"); // Grab choice of language code by user
let url = "https://www.science.co.il/language/Codes.php"; // URL to all available language codes
let text = await interaction.options.getString("text"); // Grab the text to translate by the user
let translatelog = await getChannel(
supportbot.Translate.TranslateLog,
interaction.guild
); // Grab the set logging channel for translations
// Return message if user has provided an invalid language code
const result = await translate(text, { to: lang }).catch(async (err) => {
if (err && err.code == 400) {
await interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setColor(supportbot.Embed.Colours.Warn)
.setTitle("Valid Language Codes")
.setURL(url)
.setDescription(
"Click the title to see which 2 letter language codes are valid."
),
],
flags: Discord.MessageFlags.Ephemeral,
});
}
});
if (!result) return;
// Embed containing language code, original text and translated text
let transembed = new Discord.EmbedBuilder()
.setAuthor({ name: interaction.user.tag, iconURL: interaction.user.displayAvatarURL({ dynamic: true }) })
.setColor(supportbot.Embed.Colours.Success)
.setDescription(`**Translation to ${lang}**`)
.addFields(
{ name: "Original text", value: text },
{ name: "Translated text", value: result.text },
)
.setFooter({ text: `Author: ${interaction.user.id}` })
.setTimestamp();
// Send embed [transembed] with translated message to channel
await interaction.reply({ embeds: [transembed] });
// Send embed [transembed] to logging channel
await translatelog.send({ embeds: [transembed] });
},
})
+147
View File
@@ -0,0 +1,147 @@
const fs = require("fs");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const path = require("path");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const DB = require("../../Structures/Database.js");
module.exports = new Command({
name: "suggest",
description: cmdconfig.Suggestion?.Description || "Submit a new suggestion",
type: Discord.ApplicationCommandType.ChatInput,
options: [
{
name: "text",
description: "Your suggestion text",
type: Discord.ApplicationCommandOptionType.String,
required: true
}
],
async run(interaction) {
const suggestion = interaction.options.getString("text");
const mode = supportbot.Suggestions.Mode || "Channel";
const embed = new Discord.EmbedBuilder()
.addFields(
{ name: "Suggestion", value: suggestion, inline: true },
{ name: "From", value: `<@${interaction.user.id}>` },
{ name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`, value: "0", inline: true },
{ name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`, value: "0", inline: true }
)
.setThumbnail(interaction.user.displayAvatarURL())
.setColor(supportbot.Embed.Colours.General);
const row = new Discord.ActionRowBuilder().addComponents(
new Discord.ButtonBuilder()
.setCustomId("upvote")
.setEmoji(supportbot.Suggestions.UpvoteEmoji)
.setStyle(supportbot.Suggestions.Buttons.Upvote),
new Discord.ButtonBuilder()
.setCustomId("downvote")
.setEmoji(supportbot.Suggestions.DownvoteEmoji)
.setStyle(supportbot.Suggestions.Buttons.Downvote),
new Discord.ButtonBuilder()
.setCustomId("removevote")
.setLabel(supportbot.Suggestions.Buttons.RemoveVote_Title)
.setStyle(supportbot.Suggestions.Buttons.RemoveVote)
);
if (mode === "Forum") {
const forum = await interaction.guild.channels
.fetch(supportbot.Suggestions.ForumChannel)
.catch(() => null);
if (!forum || forum.type !== Discord.ChannelType.GuildForum) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("⚠ Missing Forum Channel")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const appliedTags = supportbot.Suggestions.Tags?.Created
? [supportbot.Suggestions.Tags.Created]
: [];
const thread = await forum.threads.create({
name: suggestion.slice(0, 100),
message: {
content: "\u200B",
embeds: [embed],
components: [row]
},
appliedTags,
autoArchiveDuration: Discord.ThreadAutoArchiveDuration.OneDay,
reason: "New Suggestion"
});
embed.setFooter({
text: `${supportbot.Embed.Footer} | Suggestion ID: ${thread.id}`,
iconURL: interaction.user.displayAvatarURL()
});
const starter = await thread.fetchStarterMessage();
await starter.edit({ embeds: [embed], components: [row] });
DB.add(thread.id, interaction.user.id, suggestion);
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle(msgconfig.Suggestions.Sent_Title)
.setDescription(`${msgconfig.Suggestions.Sent}`)
.addFields({ name: 'Posted in:', value: `<#${forum.id}>` })
.setColor(supportbot.Embed.Colours.Success)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
// ---------- Channel Mode ----------
const channel = await interaction.guild.channels
.fetch(supportbot.Suggestions.Channel)
.catch(() => null);
if (!channel) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("⚠ Missing Suggestion Channel")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const message = await channel.send({ embeds: [embed], components: [row] });
embed.setFooter({
text: `${supportbot.Embed.Footer} | Suggestion ID: ${message.id}`,
iconURL: interaction.user.displayAvatarURL()
});
await message.edit({ embeds: [embed], components: [row] });
DB.add(message.id, interaction.user.id, suggestion);
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle(msgconfig.Suggestions.Sent_Title)
.setDescription(`${msgconfig.Suggestions.Sent}`)
.addFields({ name: 'Posted in:', value: `<#${channel.id}>` })
.setColor(supportbot.Embed.Colours.Success)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
});
+183
View File
@@ -0,0 +1,183 @@
const Discord = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const SuggestionDB = require("../../Structures/Database.js");
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.SuggestionAdmin.Command,
description: cmdconfig.SuggestionAdmin.Description,
type: Discord.ApplicationCommandType.ChatInput,
permissions: cmdconfig.SuggestionAdmin.Permission,
options: [
{
name: "status",
description: "New status",
type: Discord.ApplicationCommandOptionType.String,
required: true,
choices: [
{ name: "Solved", value: "Solved" },
{ name: "Denied", value: "Denied" },
{ name: "Considering", value: "Considering" }
]
},
{
name: "id",
description: "Message ID (required in Channel mode)",
type: Discord.ApplicationCommandOptionType.String,
required: false
}
],
async run(interaction) {
const status = interaction.options.getString("status");
const idOption = interaction.options.getString("id");
const mode = supportbot.Suggestions.Mode || "Channel";
const { getRole } = interaction.client;
const staffRole = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const adminRole = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (
!interaction.member.roles.cache.has(staffRole?.id) &&
!interaction.member.roles.cache.has(adminRole?.id)
) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\nRequired: ${supportbot.Roles.StaffMember.Staff} or ${supportbot.Roles.StaffMember.Admin}`
)
.setColor(supportbot.Embed.Colours.Warn)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
if (mode === "Forum") {
const thread = interaction.channel;
if (!thread.isThread() || thread.parentId !== supportbot.Suggestions.ForumChannel) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Not a Suggestion Thread")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const tagMap = {
Solved: supportbot.Suggestions.Tags.Solved,
Denied: supportbot.Suggestions.Tags.Denied,
Considering: supportbot.Suggestions.Tags.Considering
};
const tagId = tagMap[status];
if (!tagId) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Tag Missing")
.setDescription("No forum tag configured for this status.")
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
await thread.setAppliedTags([tagId]).catch(() => null);
SuggestionDB.setStatus(thread.id, status);
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Suggestion Updated")
.setDescription(`Status set to **${status}**.`)
.setColor(supportbot.Embed.Colours.Success)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
if (!idOption) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Message ID Required")
.setDescription("Provide the suggestion’s message ID when in Channel mode.")
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const channel = await interaction.guild.channels
.fetch(supportbot.Suggestions.Channel)
.catch(() => null);
if (!channel) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Missing Suggestion Channel")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const targetMsg = await channel.messages.fetch(idOption).catch(() => null);
if (!targetMsg) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Message Not Found")
.setDescription("Could not fetch a suggestion with that ID.")
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const oldEmbed = targetMsg.embeds[0];
if (!oldEmbed) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("No Embed Found")
.setDescription("The target message has no suggestion embed.")
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const colorMap = {
Solved: supportbot.Embed.Colours.Success,
Denied: supportbot.Embed.Colours.Error,
Considering: supportbot.Embed.Colours.Warn
};
const newEmbed = Discord.EmbedBuilder.from(oldEmbed)
.setColor(colorMap[status])
.addFields({ name: "Status", value: status, inline: true });
await targetMsg.edit({ embeds: [newEmbed], components: targetMsg.components });
SuggestionDB.setStatus(idOption, status);
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Suggestion Updated")
.setDescription(`Suggestion **${idOption}** marked as **${status}**.`)
.setColor(supportbot.Embed.Colours.Success)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
});
+119
View File
@@ -0,0 +1,119 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const db = require("../../Structures/Database.js");
module.exports = new Command({
name: cmdconfig.ForceAddUser.Command,
description: cmdconfig.ForceAddUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to forcefully add",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.ForceAddUser.Permission,
async run(interaction) {
const userToAdd = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.NotInTicket_Title)
.setDescription(msgconfig.ForceAddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [onlyInTicket],
flags: MessageFlags.Ephemeral,
});
}
const ticketRow = db.getTicket(ticketChannel.id);
if (!ticketRow) {
const notFound = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.NotInTicket_Title)
.setDescription(msgconfig.ForceAddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [notFound],
flags: Discord.MessageFlags.Ephemeral,
});
}
try {
if (supportbot.Ticket.TicketType === "threads") {
const botMember = await ticketChannel.members.fetch(interaction.client.user.id).catch(() => null);
if (!botMember) await ticketChannel.join();
await new Promise(res => setTimeout(res, 1000));
await ticketChannel.members.add(userToAdd.id);
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(userToAdd.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
db.addUserToTicket(ticketChannel.id, userToAdd.id);
const addedEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Added_Title)
.setDescription(
msgconfig.ForceAddUser.Added_Description.replace("%username%", userToAdd.username)
)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [addedEmbed],
flags: Discord.MessageFlags.Ephemeral,
});
const addedToTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.AddedToTicket_Title)
.setDescription(
msgconfig.ForceAddUser.AddedToTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
)
)
.setColor(supportbot.Embed.Colours.General);
await userToAdd.send({
embeds: [addedToTicketEmbed],
});
} catch (err) {
console.error("Error adding user to the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Error_Title)
.setDescription(msgconfig.ForceAddUser.Error_Adding)
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
flags: Discord.MessageFlags.Ephemeral,
});
}
},
});
+441
View File
@@ -0,0 +1,441 @@
const fs = require("fs");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const db = require("../../Structures/Database.js");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.CloseTicket.Command,
description: cmdconfig.CloseTicket.Description,
type: Discord.ApplicationCommandType.ChatInput,
options: [
{
name: "reason",
description: "Ticket Close Reason",
type: Discord.ApplicationCommandOptionType.String,
}
],
permissions: cmdconfig.CloseTicket.Permission,
async run(interaction) {
const { getRole, getChannel } = interaction.client;
if (supportbot.Ticket.Close.StaffOnly) {
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) {
return interaction.reply("Some roles seem to be missing! Please check for errors when starting the bot.");
}
const NoPerms = new Discord.EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``)
.setColor(supportbot.Embed.Colours.Warn);
if (!interaction.member.roles.cache.has(SupportStaff.id) && !interaction.member.roles.cache.has(Admin.id)) {
return interaction.reply({ embeds: [NoPerms] });
}
}
const isThread = interaction.channel.type === Discord.ChannelType.PrivateThread;
if (
(supportbot.Ticket.TicketType === "threads" && !isThread) ||
(supportbot.Ticket.TicketType === "channels" && interaction.channel.type !== Discord.ChannelType.GuildText)
) {
const NotTicketChannel = new Discord.EmbedBuilder()
.setTitle("Invalid Channel!")
.setDescription(`This command can only be used in a ${supportbot.Ticket.TicketType === "threads" ? "ticket thread" : "ticket channel"}.`)
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [NotTicketChannel], ephemeral: true });
}
await interaction.deferReply();
const ticket = db.getTicket(interaction.channel.id);
if (!ticket) {
const Exists = new Discord.EmbedBuilder()
.setTitle("No Ticket Found!")
.setDescription(msgconfig.Error.NoValidTicket)
.setColor(supportbot.Embed.Colours.Warn);
return interaction.followUp({ embeds: [Exists] });
}
let reason = interaction.options?.getString("reason") || "No Reason Provided.";
const ticketUserId = ticket.user;
if (supportbot.Ticket.ReviewSystem.Enabled) {
const rating = await collectReviewRating(interaction, ticketUserId);
const comment = await collectReviewComment(interaction, ticketUserId);
const reviewChannel = await getChannel(supportbot.Ticket.ReviewSystem.Channel, interaction.guild);
const reviewer = supportbot.Ticket.ClaimTickets ? `<@${ticket.claimedBy}>` : null;
const reviewEmbed = new Discord.EmbedBuilder()
.setTitle(msgconfig.ReviewSystem.ReviewEmbed.Title)
.addFields(
{
name: msgconfig.ReviewSystem.ReviewEmbed.RatingTitle,
value: `${msgconfig.ReviewSystem.ReviewEmbed.ReviewEmoji.repeat(rating)}`,
inline: false
},
{
name: msgconfig.ReviewSystem.ReviewEmbed.CommentTitle,
value: `\`\`\`${comment}\`\`\``,
inline: false
}
)
.setColor(msgconfig.ReviewSystem.ReviewEmbed.Color);
if (supportbot.Ticket.ClaimTickets.Enabled) {
reviewEmbed.setDescription(
`**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedStaffTitle}** ${reviewer}\n**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${ticketUserId}>` || `N/A`
);
} else {
reviewEmbed.setDescription(
`**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${ticketUserId}>`
);
}
if (reviewChannel) {
await reviewChannel.send({ embeds: [reviewEmbed] });
}
if (!interaction.replied && !interaction.deferred) {
await interaction.followUp({ embeds: [reviewEmbed] });
}
await handleCloseTicket(interaction, reason, ticket);
} else {
await handleCloseTicket(interaction, reason, ticket);
}
},
});
async function collectReviewRating(interaction, ticketUserId) {
const reviewPrompt = new Discord.EmbedBuilder()
.setTitle(msgconfig.ReviewSystem.Rate.Title)
.setDescription(`${msgconfig.ReviewSystem.Rate.Description}`)
.setColor(supportbot.Embed.Colours.General);
const stars = new Discord.ActionRowBuilder().addComponents(
new Discord.StringSelectMenuBuilder()
.setCustomId("starRating")
.setPlaceholder("Select a rating")
.addOptions([
{ label: msgconfig.ReviewSystem.Stars.One, value: "1" },
{ label: msgconfig.ReviewSystem.Stars.Two, value: "2" },
{ label: msgconfig.ReviewSystem.Stars.Three, value: "3" },
{ label: msgconfig.ReviewSystem.Stars.Four, value: "4" },
{ label: msgconfig.ReviewSystem.Stars.Five, value: "5" },
])
);
await interaction.followUp({
content: `<@${ticketUserId}>`,
embeds: [reviewPrompt],
components: [stars],
flags: Discord.MessageFlags.Ephemeral,
});
const starRating = await interaction.channel.awaitMessageComponent({
componentType: Discord.ComponentType.StringSelect,
filter: (i) => i.customId === "starRating" && i.user.id === ticketUserId,
});
await starRating.deferUpdate();
return starRating.values[0];
}
async function collectReviewComment(interaction, ticketUserId) {
const commentPrompt = new Discord.EmbedBuilder()
.setTitle(msgconfig.ReviewSystem.Comment.Title)
.setDescription(`${msgconfig.ReviewSystem.Comment.Description}`)
.setColor(supportbot.Embed.Colours.General);
await interaction.followUp({
content: `<@${ticketUserId}>`,
embeds: [commentPrompt],
flags: Discord.MessageFlags.Ephemeral,
});
const filter = (response) => response.author.id === ticketUserId;
const commentCollection = await interaction.channel.awaitMessages({ filter, max: 1 });
const comment = commentCollection.first()?.content;
return comment && comment.toLowerCase() !== "no" ? comment : "No Comment Provided";
}
async function handleCloseTicket(interaction, reason, ticket) {
const { getChannel } = interaction.client;
let tUser = interaction.client.users.cache.get(ticket.user_id);
let transcriptChannel = await getChannel(supportbot.Ticket.Log.TicketDataLog, interaction.guild);
if (!transcriptChannel) {
console.log("Transcript channel missing or inaccessible");
return interaction.followUp("Error: Transcript log channel is missing or bot lacks permission.");
}
try {
let allMessages = [];
let lastId = null;
while (true) {
const options = { limit: 100 };
if (lastId) {
options.before = lastId;
}
const messages = await interaction.channel.messages.fetch(options);
if (messages.size === 0) break;
allMessages = [...allMessages, ...messages.values()];
lastId = messages.last().id;
if (messages.size < 100) break;
}
allMessages.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
const transcriptData = allMessages.map(msg => ({
content: msg.content || "No content",
username: msg.author.username,
userId: msg.author.id,
avatar: msg.author.displayAvatarURL(),
timestamp: msg.createdAt.toISOString(),
attachments: Array.from(msg.attachments.values()).map(att => ({
url: att.url,
name: att.name
})),
embeds: msg.embeds.map(embed => ({
title: embed.title,
description: embed.description,
fields: embed.fields
}))
}));
db.updateTicketStatus(interaction.channel.id, "closed");
const transcriptEmbed = new Discord.EmbedBuilder()
.setTitle(msgconfig.TicketLog.Title)
.setColor(msgconfig.TicketLog.Colour)
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
.setDescription(
`> **Ticket:** ${interaction.channel.name} (\`${interaction.channel.id}\`)\n` +
`> **User:** ${tUser?.tag || "Unknown User"} (\`${tUser?.id || ticket.user_id}\`)` +
`> **Closed by:** <@${interaction.user.id}>\n` +
`> **Message Count:** ${transcriptData.length}`
)
.addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: true });
const html = createTranscriptHTML(
{
id: interaction.channel.id,
messages: transcriptData,
name: interaction.channel.name
},
reason
);
const transcriptDir = "./Data/Transcripts";
if (!fs.existsSync(transcriptDir)) {
fs.mkdirSync(transcriptDir, { recursive: true });
}
const fileName = `${interaction.channel.id}-transcript.html`;
fs.writeFileSync(`./Data/Transcripts/${fileName}`, html);
await transcriptChannel.send({
embeds: [transcriptEmbed],
files: [{
attachment: `./Data/Transcripts/${fileName}`,
name: `SPOILER_${fileName}`,
spoiler: true
}]
});
if (interaction.channel.type === Discord.ChannelType.GuildText ||
interaction.channel.type === Discord.ChannelType.PrivateThread) {
await interaction.channel.delete();
}
} catch (err) {
console.error("Error handling ticket close:", err);
interaction.followUp("An error occurred while closing the ticket.");
}
}
function parseMarkdown(content) {
content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
content = content.replace(/_(.*?)_/g, '<em>$1</em>');
content = content.replace(/`(.*?)`/g, '<code>$1</code>');
content = content.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>');
content = content.replace(/```(.*?)```/gs, '<pre><code>$1</code></pre>');
return content;
}
function createTranscriptHTML(ticket, reason) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<style>
body {
background: url('file-EWLgzFEYsq16ANVg6ENJrP.png') no-repeat center center fixed;
background-size: cover;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: #ffffff;
margin: 0;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
backdrop-filter: blur(10px);
background: rgba(0,0,0,0.4);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}
h1 {
font-size: 2.5rem;
font-weight: bold;
text-align: center;
margin-bottom: 20px;
}
.ticket-info p {
margin: 5px 0;
}
.message {
backdrop-filter: blur(15px);
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 15px;
padding: 15px;
margin-bottom: 15px;
transition: transform 0.2s;
}
.message:hover {
transform: scale(1.02);
}
.message-header {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 15px;
border: 2px solid rgba(255,255,255,0.3);
}
.username {
font-weight: bold;
color: #00ffe4;
font-size: 1.1rem;
}
.timestamp {
margin-left: auto;
color: #aaa;
font-size: 0.85rem;
}
.content {
word-break: break-word;
font-size: 1rem;
}
.embed {
border-left: 4px solid #00ffe4;
padding-left: 10px;
margin: 10px 0;
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
}
.attachment {
display: inline-block;
background-color: rgba(0, 255, 228, 0.1);
border: 1px solid #00ffe4;
border-radius: 5px;
padding: 5px 10px;
margin: 5px 0;
color: #00ffe4;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<h1>Ticket Transcript</h1>
<div class="ticket-info mb-6">
<p><strong>Channel:</strong> ${ticket.name}</p>
<p><strong>Ticket ID:</strong> ${ticket.id}</p>
<p><strong>Message Count:</strong> ${ticket.messages.length}</p>
<p><strong>Close Reason:</strong> ${reason}</p>
</div>
<div class="messages">
${ticket.messages.map(msg => `
<div class="message">
<div class="message-header">
<img src="${msg.avatar}" alt="Avatar" class="avatar">
<span class="username">${msg.username}</span>
<span class="timestamp">${new Date(msg.timestamp).toLocaleString()}</span>
</div>
<div class="content">
${parseMarkdown(msg.content)}
${msg.embeds.map(embed => `
<div class="embed">
${embed.title ? `<div class="font-bold">${embed.title}</div>` : ''}
${embed.description ? `<div>${embed.description}</div>` : ''}
${embed.fields.map(field => `
<div class="mt-2">
<strong>${field.name}:</strong>
<div>${field.value}</div>
</div>
`).join('')}
</div>
`).join('')}
${msg.attachments.map(att => `
<a href="${att.url}" class="attachment" target="_blank">
📎 ${att.name}
</a>
`).join('')}
</div>
</div>
`).join('')}
</div>
</div>
</body>
</html>
`;
}
+119
View File
@@ -0,0 +1,119 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const db = require("../../Structures/Database.js");
module.exports = new Command({
name: cmdconfig.ForceAddUser.Command,
description: cmdconfig.ForceAddUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to forcefully add",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.ForceAddUser.Permission,
async run(interaction) {
const userToAdd = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.NotInTicket_Title)
.setDescription(msgconfig.ForceAddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [onlyInTicket],
flags: MessageFlags.Ephemeral,
});
}
const ticketRow = db.getTicket(ticketChannel.id);
if (!ticketRow) {
const notFound = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.NotInTicket_Title)
.setDescription(msgconfig.ForceAddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [notFound],
flags: MessageFlags.Ephemeral,
});
}
try {
if (supportbot.Ticket.TicketType === "threads") {
const botMember = await ticketChannel.members.fetch(interaction.client.user.id).catch(() => null);
if (!botMember) await ticketChannel.join();
await new Promise(res => setTimeout(res, 1000));
await ticketChannel.members.add(userToAdd.id);
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(userToAdd.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
db.addUserToTicket(ticketChannel.id, userToAdd.id);
const addedEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Added_Title)
.setDescription(
msgconfig.ForceAddUser.Added_Description.replace("%username%", userToAdd.username)
)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [addedEmbed],
flags: MessageFlags.Ephemeral,
});
const addedToTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.AddedToTicket_Title)
.setDescription(
msgconfig.ForceAddUser.AddedToTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
)
)
.setColor(supportbot.Embed.Colours.General);
await userToAdd.send({
embeds: [addedToTicketEmbed],
});
} catch (err) {
console.error("Error adding user to the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Error_Title)
.setDescription(msgconfig.ForceAddUser.Error_Adding)
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
flags: MessageFlags.Ephemeral,
});
}
},
});
+108
View File
@@ -0,0 +1,108 @@
// SupportBot | Emerald Services
// Remove user from a ticket
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const fs = require("fs");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const db = require("../../Structures/Database.js");
module.exports = new Command({
name: cmdconfig.RemoveUser.Command,
description: cmdconfig.RemoveUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to remove",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.RemoveUser.Permission,
async run(interaction) {
const { getRole } = interaction.client;
const SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) {
return interaction.reply("Missing staff/admin roles in config.");
}
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
) {
const NoPerms = new EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [NoPerms], flags: MessageFlags.Ephemeral });
}
const userToRemove = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.NotInTicket_Title)
.setDescription(msgconfig.RemoveUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({ embeds: [onlyInTicket], flags: MessageFlags.Ephemeral });
}
try {
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.remove(userToRemove.id);
} else {
await ticketChannel.permissionOverwrites.delete(userToRemove.id);
}
db.removeUserFromTicket(ticketChannel.id, userToRemove.id);
const removedEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.Removed_Title)
.setDescription(msgconfig.RemoveUser.Removed_Message)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({ embeds: [removedEmbed], flags: MessageFlags.Ephemeral });
const removedFromTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.RemovedFromTicket_Title)
.setDescription(
msgconfig.RemoveUser.RemovedFromTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
)
)
.setColor(supportbot.Embed.Colours.General);
await userToRemove.send({ embeds: [removedFromTicketEmbed] }).catch(() => {});
} catch (err) {
console.error("Error removing user from the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.Error_Title)
.setDescription(msgconfig.RemoveUser.Error_Removing)
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral });
}
},
});
+551
View File
@@ -0,0 +1,551 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
ActionRowBuilder,
ButtonBuilder,
StringSelectMenuBuilder,
StringSelectMenuOptionBuilder,
ButtonStyle,
MessageCollector,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const db = require("../../Structures/Database.js");
const panelconfig = yaml.load(fs.readFileSync("./Configs/ticket-panel.yml", "utf8"));
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const TicketNumberID = require("../../Structures/TicketID.js");
async function isBlacklisted(userId) {
let blacklistData;
try {
blacklistData = JSON.parse(fs.readFileSync("./Data/BlacklistedUsers.json", "utf8"));
} catch (error) {
console.error("Error reading BlacklistedUsers.json:", error);
return false;
}
if (!blacklistData || !Array.isArray(blacklistData.blacklistedUsers)) {
console.error("blacklistedUsers is not defined or is not an array");
return false;
}
return blacklistData.blacklistedUsers.includes(userId);
}
async function getClockedInUsers(guild) {
const clockedInUsers = new Set();
const rows = guild.members.cache.map(m => m.id);
for (const id of rows) {
const profile = db.getProfile(id);
if (profile && profile.clockedIn) {
clockedInUsers.add(id);
}
}
return clockedInUsers;
}
async function askTicketQuestions(ticketChannel, interaction, questions) {
const answers = [];
let questionIndex = 0;
const sendNextQuestion = async () => {
if (questionIndex < questions.length) {
const questionsEmbed = new EmbedBuilder()
.setDescription(`**${questionIndex + 1}.** ${questions[questionIndex]}`)
.setColor(supportbot.Embed.Colours.General);
const questionMessage = await ticketChannel.send({ embeds: [questionsEmbed] });
const collector = new MessageCollector(ticketChannel, {
filter: (m) => m.author.id === interaction.user.id,
max: 1,
time: 60000,
});
collector.on("collect", async (message) => {
answers.push({ question: questions[questionIndex], answer: message.content });
questionIndex++;
await message.delete();
await questionMessage.delete();
sendNextQuestion();
});
} else {
const summaryEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.TicketQuestions.Details.Title)
.setDescription(
msgconfig.Ticket.TicketQuestions.Details.Description.replace("%user%", interaction.user.id)
)
.setColor(supportbot.Embed.Colours.General)
.addFields(
answers.map((a) => ({
name: `Q: ${a.question}`,
value: `A: ${a.answer}`,
inline: false,
}))
)
.setTimestamp();
await ticketChannel.send({ embeds: [summaryEmbed] });
}
};
sendNextQuestion();
}
async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts = 0) {
try {
const clockedInUsers = await getClockedInUsers(interaction.guild);
if (clockedInUsers.size === 0 || attempts >= 2) {
const claimChannel = await interaction.guild.channels.cache.get(supportbot.Ticket.ClaimTickets.Channel);
if (claimChannel) {
const claimEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage)
.setColor(supportbot.Embed.Colours.General);
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || "Claim Ticket")
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
const claimMessage = await claimChannel.send({
content: `<@&${Staff.id}> <@&${Admin.id}>`,
embeds: [claimEmbed],
components: [row],
});
const filter = (i) =>
i.customId === `claimticket-${ticketChannel.id}` &&
i.member.roles.cache.has(Staff.id);
const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 });
collector.on("collect", async (i) => {
await ticketChannel.send({ content: `<@${i.user.id}> has claimed the ticket.` });
});
collector.on("end", async (collected) => {
if (collected.size === 0) {
await claimMessage.delete();
await claimChannel.send({
content: `Ticket is still unclaimed. \nCC: [<@&${Staff.id}> <@&${Admin.id}>]`,
embeds: [
new EmbedBuilder()
.setDescription(`Ticket is still unclaimed: <#${ticketChannel.id}>`)
.setColor(supportbot.Embed.Colours.Warn),
],
});
}
});
return null;
} else {
console.error("Claim channel not found");
return null;
}
}
const clockedInArray = Array.from(clockedInUsers);
const assignedUserId =
clockedInArray[Math.floor(Math.random() * clockedInArray.length)];
const assignedUser = await interaction.guild.members.fetch(assignedUserId);
const claimEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
.setDescription(
msgconfig.Ticket.ClaimTickets.ClaimMessage.replace("%user%", interaction.user.id)
)
.setColor(supportbot.Embed.Colours.General);
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || "Claim Ticket")
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
const claimChannel = await interaction.guild.channels.cache.get(
supportbot.Ticket.ClaimTickets.Channel
);
if (!claimChannel) {
console.error("Claim channel not found");
return null;
}
const claimMessage = await claimChannel.send({
content: `${assignedUser}`,
embeds: [claimEmbed],
components: [row],
});
const filter = (i) =>
i.customId === `claimticket-${ticketChannel.id}` && i.user.id === assignedUserId;
const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 });
collector.on("collect", async (i) => {
await ticketChannel
.send({ content: `<@${assignedUserId}>` })
.then((msg) => setTimeout(() => msg.delete(), 5000));
});
collector.on("end", async (collected) => {
if (collected.size === 0) {
await claimMessage.delete();
if (attempts >= 1) {
await claimChannel.send({
content: `No one claimed the ticket. Tagging roles: <@&${Staff.id}> <@&${Admin.id}>`,
embeds: [
new EmbedBuilder()
.setDescription(`Ticket is still unclaimed: <#${ticketChannel.id}>`)
.setColor(supportbot.Embed.Colours.Warn),
],
});
const claimEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage)
.setColor(supportbot.Embed.Colours.General);
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle)
.setEmoji(supportbot.Ticket.ClaimTickets.ButtonEmoji)
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
const claimMessage = await claimChannel.send({
embeds: [claimEmbed],
components: [row],
});
const filter = (i) =>
i.customId === `claimticket-${ticketChannel.id}` &&
i.member.roles.cache.has(Staff.id);
const collector = claimMessage.createMessageComponentCollector({
filter,
time: 120000,
});
collector.on("collect", async (i) => {
console.log(`Ticket ${ticketChannel.id} claimed by user ${i.user.id}`);
await ticketChannel.member.add(i.user.id);
});
collector.on("end", async (collected) => {
if (collected.size === 0) {
console.log(
`No one claimed the ticket (${ticketChannel.id}), notifying all staff`
);
await claimMessage.delete();
await claimChannel.send({
content: `No one claimed the ticket. Tagging roles: <@&${Staff.id}> <@&${Admin.id}>`,
});
}
});
} else {
const ReassigningEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ReassigningTitle)
.setDescription(
msgconfig.Ticket.ClaimTickets.ReassigningMessage.replace("%user%", assignedUserId)
)
.setColor(supportbot.Embed.Colours.General);
await claimChannel.send({ embeds: [ReassigningEmbed], components: [] });
await assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts + 1);
}
}
});
return assignedUserId;
} catch (error) {
console.error("Error in assignTicketToUser function:", error);
return null;
}
}
module.exports = new Command({
name: cmdconfig.OpenTicket.Command,
description: cmdconfig.OpenTicket.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "reason",
description: "Ticket Reason",
type: ApplicationCommandOptionType.String,
},
],
permissions: cmdconfig.OpenTicket.Permission,
async run(interaction) {
try {
if (await isBlacklisted(interaction.user.id)) {
return interaction.reply({
content: msgconfig.Ticket.Blacklisted,
flags: MessageFlags.Ephemeral
});
}
let TicketReason = null;
if (supportbot.Ticket.TicketReason) {
TicketReason = interaction.reason || null;
}
let TicketData = { tickets: db.getAllTickets() };
const { getRole, getChannel } = interaction.client;
let User = interaction.guild.members.cache.get(interaction.user.id);
if (
supportbot.Ticket.TicketsPerUser &&
TicketData.tickets.filter((t) => t.user === interaction.user.id && t.open).length >= supportbot.Ticket.TicketsPerUser
) {
return interaction.reply({
embeds: [
{
title: "Too Many Tickets!",
description: `You can't have more than ${supportbot.Ticket.TicketsPerUser} open tickets!`,
color: supportbot.Embed.Colours.Warn,
},
],
flags: MessageFlags.Ephemeral
});
}
let ticketNumberID = await TicketNumberID.pad();
const TicketSubject = TicketReason || msgconfig.Ticket.InvalidSubject;
const TicketExists = new EmbedBuilder()
.setTitle("Ticket Exists!")
.setDescription(msgconfig.Ticket.TicketExists);
if (
await interaction.guild.channels.cache.find(
(ticketChannel) => ticketChannel.name === `${supportbot.Ticket.Channel}${ticketNumberID}`
)
) {
return await interaction.reply({
embeds: [TicketExists],
flags: MessageFlags.Ephemeral
});
}
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!Staff || !Admin)
return interaction.reply({
content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.",
flags: MessageFlags.Ephemeral
});
let ticketChannel;
if (supportbot.Ticket.TicketType === "threads") {
const TicketHome = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.Ticket.TicketHome || c.id === supportbot.Ticket.TicketHome
);
const channel = await getChannel(supportbot.Ticket.TicketHome, interaction.guild);
ticketChannel = await channel.threads.create({
name: `${supportbot.Ticket.Channel}${ticketNumberID}`,
type: ChannelType.PrivateThread,
parent: TicketHome,
autoArchiveDuration: 60,
reason: 'support ticket',
});
} else if (supportbot.Ticket.TicketType === "channels") {
const category = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.Ticket.TicketChannelsCategory || c.id === supportbot.Ticket.TicketChannelsCategory
);
if (!category) {
return interaction.reply({
content: "The ticket category does not exist!",
flags: MessageFlags.Ephemeral
});
}
ticketChannel = await interaction.guild.channels.create({
name: `${supportbot.Ticket.Channel}${ticketNumberID}`,
type: ChannelType.GuildText,
parent: category.id,
reason: 'support ticket',
});
if (category.children.size >= 50) {
const secondaryCategory = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.Ticket.TicketChannelsCategory2 || c.id === supportbot.Ticket.TicketChannelsCategory2
);
if (secondaryCategory) {
await ticketChannel.setParent(secondaryCategory.id);
} else {
return interaction.reply({
content: "The secondary ticket category does not exist!",
flags: MessageFlags.Ephemeral
});
}
}
}
if (supportbot.Ticket.ClaimTickets.Enabled) {
const assignedUserId = await assignTicketToUser(ticketChannel, Staff, Admin, interaction);
if (!assignedUserId) {
const claimChannel = await interaction.guild.channels.cache.get(supportbot.Ticket.ClaimTickets.Channel);
}
}
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(interaction.user.id);
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(interaction.user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(Admin, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(interaction.guild.roles.everyone, {
ViewChannel: false,
});
}
db.addTicket({
id: ticketChannel.id,
name: ticketChannel.name,
user: interaction.user.id,
number: ticketNumberID,
reason: TicketSubject,
open: true,
subUsers: [],
createdAt: new Date().toISOString(),
claimedAt: null,
});
TicketData.tickets = db.getAllTickets();
const CreatedTicket = new EmbedBuilder()
.setDescription(
msgconfig.Ticket.TicketCreatedAlert.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
.replace(/%ticketreason%/g, TicketSubject)
)
.setColor(supportbot.Embed.Colours.General);
await interaction.reply({ embeds: [CreatedTicket], flags: MessageFlags.Ephemeral });
const TicketMessage = new EmbedBuilder()
.setAuthor({
name: msgconfig.Ticket.TicketAuthorTitle.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username),
iconURL: interaction.user.displayAvatarURL(),
})
.setTitle(
msgconfig.Ticket.TicketTitle.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
)
.setDescription(
msgconfig.Ticket.TicketMessage.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
.replace(/%ticketreason%/g, TicketSubject)
)
.setColor(supportbot.Embed.Colours.General);
if (supportbot.Ticket.TicketReason && TicketReason) {
TicketMessage.addFields(
{ name: "Reason", value: TicketReason, inline: false }
);
}
const SelectMenus = new StringSelectMenuBuilder()
.setCustomId("ticketcontrolpanel")
.setPlaceholder("Ticket Control Panel")
.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel("Close")
.setDescription("Close the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.CloseEmoji)
.setValue("ticketclose"),
new StringSelectMenuOptionBuilder()
.setLabel("Voice Channel")
.setDescription("Create a support voice channel.")
.setEmoji(supportbot.SelectMenus.Tickets.VCEmoji)
.setValue("supportvc"),
new StringSelectMenuOptionBuilder()
.setLabel("Archive")
.setDescription("Archive the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.ArchiveEmoji)
.setValue("archiveticket"),
...(supportbot.Ticket.TicketType === "threads" ? [
new StringSelectMenuOptionBuilder()
.setLabel("Lock Ticket")
.setDescription("Lock the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.LockEmoji)
.setValue("lockticket")
] : []),
new StringSelectMenuOptionBuilder()
.setLabel("Rename Ticket")
.setDescription("Rename the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.RenameEmoji)
.setValue("renameticket")
);
if (supportbot.Ticket.TicketType === "threads") {
SelectMenus.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel("Enable Invites")
.setDescription("Enable invites.")
.setEmoji(supportbot.SelectMenus.Tickets.EnableInvitesEmoji)
.setValue("enableinvites"),
new StringSelectMenuOptionBuilder()
.setLabel("Disable Invites")
.setDescription("Disable invites.")
.setEmoji(supportbot.SelectMenus.Tickets.DisableInvitesEmoji)
.setValue("disableinvites"),
);
}
const row2 = new ActionRowBuilder().addComponents(SelectMenus);
await ticketChannel.send({
embeds: [TicketMessage],
components: [row2],
});
if (supportbot.Ticket.Questions.Enabled) {
const questions = supportbot.Ticket.Questions.List || [];
if (questions.length > 0) {
await askTicketQuestions(ticketChannel, interaction, questions);
}
}
} catch (error) {
console.error("Error in run method:", error);
}
},
});
+113
View File
@@ -0,0 +1,113 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandType,
ActionRowBuilder,
ButtonBuilder,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const panelconfig = yaml.load(fs.readFileSync("./Configs/ticket-panel.yml", "utf8"));
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const db = require("../../Structures/Database.js");
module.exports = new Command({
name: cmdconfig.TicketPanel.Command,
description: cmdconfig.TicketPanel.Description,
type: ApplicationCommandType.ChatInput,
options: [],
permissions: cmdconfig.TicketPanel.Permission,
async run(interaction) {
if (!panelconfig.Panel) {
console.log(
"\u001b[31m",
`[TICKET PANEL]`,
"\u001b[33m",
"Ticket Panel is not setup, You can set this in", `\u001b[31m`, '/Configs/ticket-panel.yml', `\n `,
);
return interaction.reply({
content: "Ticket Panel is not set up. Please configure it in `/Configs/ticket-panel.yml`.",
flags: MessageFlags.Ephemeral
});
}
const { getChannel } = interaction.client;
const channel = await getChannel(
supportbot.Ticket.TicketHome,
interaction.guild
);
if (!channel) {
console.log(`[TICKET PANEL] ${channelName} channel not found. Please check your config file.`);
return interaction.reply({
content: `${channelName} channel not found. Please check your config file.`,
flags: MessageFlags.Ephemeral
});
}
const panelRow = db.getTicketPanel();
if (panelRow) {
try {
await channel.messages.fetch(panelRow.message_id);
return interaction.reply({
content: "Ticket panel message already exists.",
flags: MessageFlags.Ephemeral
});
} catch (err) {}
}
let embed = new EmbedBuilder()
.setTitle(panelconfig.PanelTitle)
.setColor(panelconfig.PanelColour)
.setFooter({
text: supportbot.Embed.Footer,
iconURL: interaction.user.displayAvatarURL(),
});
if (panelconfig.TicketPanel_Description) {
embed.setDescription(panelconfig.PanelMessage);
}
if (panelconfig.TicketPanel_Thumbnail) {
embed.setThumbnail(panelconfig.PanelThumbnail);
}
if (panelconfig.TicketPanel_Image) {
embed.setImage(panelconfig.PanelImage);
}
const createTicketButton = new ButtonBuilder()
.setCustomId("createticket")
.setLabel(panelconfig.Button.Text)
.setEmoji(panelconfig.Button.Emoji)
.setStyle(panelconfig.Button.Color);
let row = new ActionRowBuilder().addComponents(createTicketButton);
try {
const message = await channel.send({
embeds: [embed],
components: [row],
});
db.saveTicketPanel(message.id, channel.id);
return interaction.reply({
content: "Ticket panel message has been sent!",
flags: MessageFlags.Ephemeral
});
} catch (e) {
console.log("Error sending message:", e);
return interaction.reply({
content: "An error occurred while sending the ticket panel message.",
flags: MessageFlags.Ephemeral
});
}
},
});
+468
View File
@@ -0,0 +1,468 @@
const {
ApplicationCommandType,
ApplicationCommandOptionType,
ActionRowBuilder,
ButtonBuilder,
EmbedBuilder,
StringSelectMenuBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
ButtonStyle,
ComponentType,
MessageFlags,
} = require('discord.js');
const Command = require('../../Structures/Command.js');
const fs = require('fs');
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 msgconfig = yaml.load(fs.readFileSync('./Configs/messages.yml', 'utf8'));
function parseColor(color) {
if (!color) return 0x5865F2;
if (typeof color === 'number') return color;
if (typeof color !== 'string') return 0x5865F2;
color = color.replace('#', '');
if (/^[0-9A-F]{6}$/i.test(color)) {
return parseInt(color, 16);
}
return 0x5865F2;
}
module.exports = new Command({
name: cmdconfig.Embed.Command,
description: cmdconfig.Embed.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: 'channel',
description: 'Select channel to send the message',
type: ApplicationCommandOptionType.Channel,
required: true
}
],
permissions: cmdconfig.Embed.Permission,
async run(interaction) {
const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) {
return interaction.reply('Some roles seem to be missing!\nPlease check for errors when starting the bot.');
}
if (!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)) {
const NoPerms = new EmbedBuilder()
.setTitle('Invalid Permissions!')
.setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``)
.setColor(0xFF0000);
return interaction.reply({ embeds: [NoPerms], flags: MessageFlags.Ephemeral });
}
const channel = interaction.options.getChannel('channel');
const messageData = {
type: 'embed',
title: 'New Embed',
description: 'Click the buttons below to edit this embed',
color: 0x5865F2,
fields: [],
footer: null,
thumbnail: null,
image: null,
timestamp: false
};
function createEmbed() {
const embed = new EmbedBuilder()
.setTitle(messageData.title)
.setDescription(messageData.description)
.setColor(messageData.color);
if (messageData.fields.length > 0) {
embed.addFields(messageData.fields);
}
if (messageData.footer) {
embed.setFooter({ text: messageData.footer });
}
if (messageData.thumbnail) {
embed.setThumbnail(messageData.thumbnail);
}
if (messageData.image) {
embed.setImage(messageData.image);
}
if (messageData.timestamp) {
embed.setTimestamp();
}
return embed;
}
const typeRow = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId('message_type')
.setPlaceholder('Select message type')
.addOptions([
{
label: 'Embed Message',
description: 'Send as an embedded message',
value: 'embed'
},
{
label: 'Text Message',
description: 'Send as a plain text message',
value: 'text'
}
])
);
function createButtons(messageType) {
const buttons = [];
if (messageType === 'embed') {
const row1 = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('edit_title')
.setLabel('Edit Title')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('edit_description')
.setLabel('Edit Description')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('edit_color')
.setLabel('Edit Color')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('add_field')
.setLabel('Add Field')
.setStyle(ButtonStyle.Secondary)
);
const row2 = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('edit_footer')
.setLabel('Edit Footer')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('edit_image')
.setLabel('Add Image')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('edit_thumbnail')
.setLabel('Add Thumbnail')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('toggle_timestamp')
.setLabel('Toggle Timestamp')
.setStyle(ButtonStyle.Secondary)
);
buttons.push(row1, row2);
} else {
const textRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('edit_description')
.setLabel('Edit Text')
.setStyle(ButtonStyle.Secondary)
);
buttons.push(textRow);
}
// Send button always shows
const sendRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('send_message')
.setLabel('Send')
.setStyle(ButtonStyle.Success)
);
buttons.push(sendRow);
return buttons;
}
async function updatePreview(i) {
if (messageData.type === 'embed') {
await i.update({
content: 'Customize your message:',
embeds: [createEmbed()],
components: [typeRow, ...createButtons('embed')]
});
} else {
await i.update({
content: messageData.description,
embeds: [],
components: [typeRow, ...createButtons('text')]
});
}
}
const message = await interaction.reply({
content: messageData.type === 'text' ? messageData.description : 'Customize your message:',
components: [typeRow, ...createButtons(messageData.type)],
embeds: messageData.type === 'embed' ? [createEmbed()] : [],
flags: MessageFlags.Ephemeral
});
const collector = message.createMessageComponentCollector({
time: 900000
});
collector.on('collect', async (i) => {
if (i.user.id !== interaction.user.id) {
return i.reply({
content: 'You cannot edit this message.',
flags: MessageFlags.Ephemeral
});
}
switch (i.customId) {
case 'message_type':
messageData.type = i.values[0];
await updatePreview(i);
break;
case 'edit_description':
const descModal = new ModalBuilder()
.setCustomId('desc_modal')
.setTitle(messageData.type === 'embed' ? 'Edit Description' : 'Edit Text');
descModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('desc_input')
.setLabel(messageData.type === 'embed' ? 'Description' : 'Message Text')
.setStyle(TextInputStyle.Paragraph)
.setValue(messageData.description)
.setMaxLength(4000)
)
);
await i.showModal(descModal);
break;
case 'edit_title':
const titleModal = new ModalBuilder()
.setCustomId('title_modal')
.setTitle('Edit Title');
titleModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('title_input')
.setLabel('Title')
.setStyle(TextInputStyle.Short)
.setValue(messageData.title)
.setMaxLength(256)
)
);
await i.showModal(titleModal);
break;
case 'edit_color':
const colorModal = new ModalBuilder()
.setCustomId('color_modal')
.setTitle('Edit Color');
colorModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('color_input')
.setLabel('Color (HEX)')
.setStyle(TextInputStyle.Short)
.setPlaceholder('#FF0000')
.setMinLength(6)
.setMaxLength(7)
.setValue(messageData.color.toString(16).padStart(6, '0'))
)
);
await i.showModal(colorModal);
break;
case 'add_field':
const fieldModal = new ModalBuilder()
.setCustomId('field_modal')
.setTitle('Add Field');
fieldModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('field_name')
.setLabel('Field Name')
.setStyle(TextInputStyle.Short)
.setMaxLength(256)
),
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('field_value')
.setLabel('Field Value')
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(1024)
),
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('field_inline')
.setLabel('Inline? (true/false)')
.setStyle(TextInputStyle.Short)
.setValue('false')
.setPlaceholder('true or false')
)
);
await i.showModal(fieldModal);
break;
case 'edit_footer':
const footerModal = new ModalBuilder()
.setCustomId('footer_modal')
.setTitle('Edit Footer');
footerModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('footer_input')
.setLabel('Footer Text')
.setStyle(TextInputStyle.Short)
.setValue(messageData.footer || '')
.setMaxLength(2048)
)
);
await i.showModal(footerModal);
break;
case 'edit_image':
const imageModal = new ModalBuilder()
.setCustomId('image_modal')
.setTitle('Add Image');
imageModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('image_input')
.setLabel('Image URL')
.setStyle(TextInputStyle.Short)
.setValue(messageData.image || '')
.setPlaceholder('https://example.com/image.png')
)
);
await i.showModal(imageModal);
break;
case 'edit_thumbnail':
const thumbnailModal = new ModalBuilder()
.setCustomId('thumbnail_modal')
.setTitle('Add Thumbnail');
thumbnailModal.addComponents(
new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('thumbnail_input')
.setLabel('Thumbnail URL')
.setStyle(TextInputStyle.Short)
.setValue(messageData.thumbnail || '')
.setPlaceholder('https://example.com/thumbnail.png')
)
);
await i.showModal(thumbnailModal);
break;
case 'toggle_timestamp':
messageData.timestamp = !messageData.timestamp;
await updatePreview(i);
break;
case 'send_message':
try {
if (messageData.type === 'embed') {
await channel.send({ embeds: [createEmbed()] });
} else {
await channel.send(messageData.description);
}
await i.update({
content: 'Message sent successfully!',
components: [],
embeds: []
});
collector.stop();
} catch (error) {
await i.reply({
content: 'Failed to send message. Please check channel permissions.',
flags: MessageFlags.Ephemeral
});
}
break;
}
});
// Handle modal submissions
interaction.client.on('interactionCreate', async (modal) => {
if (!modal.isModalSubmit()) return;
if (modal.user.id !== interaction.user.id) return;
switch (modal.customId) {
case 'title_modal':
messageData.title = modal.fields.getTextInputValue('title_input');
break;
case 'desc_modal':
messageData.description = modal.fields.getTextInputValue('desc_input');
break;
case 'color_modal':
messageData.color = parseColor(modal.fields.getTextInputValue('color_input'));
break;
case 'field_modal':
const inlineValue = modal.fields.getTextInputValue('field_inline').toLowerCase();
messageData.fields.push({
name: modal.fields.getTextInputValue('field_name'),
value: modal.fields.getTextInputValue('field_value'),
inline: inlineValue === 'true'
});
break;
case 'footer_modal':
messageData.footer = modal.fields.getTextInputValue('footer_input');
break;
case 'image_modal':
messageData.image = modal.fields.getTextInputValue('image_input');
break;
case 'thumbnail_modal':
messageData.thumbnail = modal.fields.getTextInputValue('thumbnail_input');
break;
default:
return;
}
await updatePreview(modal);
});
collector.on('end', () => {
interaction.editReply({
content: 'Message editor timed out.',
components: [],
embeds: []
}).catch(() => {});
});
}
});
+261
View File
@@ -0,0 +1,261 @@
const fs = require("fs");
const { ApplicationCommandOptionType, ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType, 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 msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Command = require("../../Structures/Command.js");
// Function to chunk array into smaller arrays
function chunkArray(arr, size) {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
module.exports = new Command({
name: cmdconfig.Mod.Command,
description: cmdconfig.Mod.Command,
type: ApplicationCommandType.ChatInput,
options: [
{
name: cmdconfig.Mod.TicketBlacklist.Command,
description: cmdconfig.Mod.TicketBlacklist.Description,
type: ApplicationCommandOptionType.SubcommandGroup,
options: [
{
name: cmdconfig.Mod.TicketBlacklist.Add.Command,
description: msgconfig.Mod.TicketBlacklist.Add.Description || "Add a user to the ticket blacklist",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'user',
description: msgconfig.Mod.TicketBlacklist.Add.UserDescription || "User to blacklist",
type: ApplicationCommandOptionType.User,
required: true
},
{
name: 'reason',
description: msgconfig.Mod.TicketBlacklist.Add.ReasonDescription || "Reason for blacklisting",
type: ApplicationCommandOptionType.String,
required: false
}
]
},
{
name: cmdconfig.Mod.TicketBlacklist.Remove.Command,
description: msgconfig.Mod.TicketBlacklist.Remove.Description || "Remove a user from the ticket blacklist",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'user',
description: msgconfig.Mod.TicketBlacklist.Remove.UserDescription || "User to remove from blacklist",
type: ApplicationCommandOptionType.User,
required: true
},
{
name: 'reason',
description: msgconfig.Mod.TicketBlacklist.Remove.ReasonDescription || "Reason for removing from blacklist",
type: ApplicationCommandOptionType.String,
required: false
}
]
},
{
name: "view",
description: msgconfig.Mod.TicketBlacklist.View.Description || "View all blacklisted users",
type: ApplicationCommandOptionType.Subcommand,
}
]
}
],
permissions: cmdconfig.Mod.Permission,
async run(interaction) {
const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
let Moderator = await getRole(supportbot.Roles.StaffMember.Moderator, interaction.guild);
if (!SupportStaff || !Admin || !Moderator) {
const missingRolesEmbed = new EmbedBuilder()
.setDescription(msgconfig.Error.InvalidChannel || "Required roles are missing!")
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [missingRolesEmbed] });
}
const NoPerms = new EmbedBuilder()
.setDescription(msgconfig.Error.IncorrectPerms || "You do not have the correct permissions!")
.setColor(supportbot.Embed.Colours.Warn);
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))) {
return interaction.reply({ embeds: [NoPerms] });
}
const subcommand = interaction.options.getSubcommand(false);
const user = interaction.options.getUser('user');
const reason = interaction.options.getString('reason') || "No reason provided";
try {
let blacklistedUsers;
try {
blacklistedUsers = JSON.parse(fs.readFileSync("./Data/BlacklistedUsers.json", "utf8")).blacklistedUsers;
if (!Array.isArray(blacklistedUsers)) {
blacklistedUsers = [];
}
} catch (error) {
blacklistedUsers = [];
}
if (subcommand === cmdconfig.Mod.TicketBlacklist.Add.Command) {
if (blacklistedUsers.includes(user.id)) {
const alreadyBlacklistedEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.Add.AlreadyBlacklisted.replace("{userTag}", user.tag) || ":x: User is already blacklisted.")
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [alreadyBlacklistedEmbed], flags: MessageFlags.Ephemeral });
}
blacklistedUsers.push(user.id);
fs.writeFileSync("./Data/BlacklistedUsers.json", JSON.stringify({ "blacklistedUsers": blacklistedUsers }, null, 4));
const successEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.Add.Success.replace("{userTag}", user.tag) || ":white_check_mark: User has been blacklisted.")
.setColor(supportbot.Embed.Colours.Success);
const blacklistChannel = interaction.guild.channels.cache.get(supportbot.Ticket.Log.TicketBlacklistLog);
if (blacklistChannel) {
const blacklistLogEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketBlacklistLog.Title || "Ticket Blacklist Log")
.setColor(msgconfig.TicketBlacklistLog.Colour || supportbot.Embed.Colours.Success)
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
.setDescription(`> **User:** ${user.tag} (\`${user.id}\`)\n> **Actioned by:** <@${interaction.user.id}>`)
.addFields({ name: "Action", value: "\`\`\`Removed from blacklist\`\`\`", inline: false })
.addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: false });
blacklistChannel.send({ embeds: [blacklistLogEmbed] });
}
return interaction.reply({
embeds: [successEmbed],
flags: MessageFlags.Ephemeral
});
} else if (subcommand === cmdconfig.Mod.TicketBlacklist.Remove.Command) {
if (!blacklistedUsers.includes(user.id)) {
const notBlacklistedEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.Remove.NotBlacklisted.replace("{userTag}", user.tag) || ":x: User is not blacklisted.")
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [notBlacklistedEmbed], flags: MessageFlags.Ephemeral });
}
blacklistedUsers = blacklistedUsers.filter(id => id !== user.id);
fs.writeFileSync("./Data/BlacklistedUsers.json", JSON.stringify({ "blacklistedUsers": blacklistedUsers }, null, 4));
const removedEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.Remove.Success.replace("{userTag}", user.tag) || ":white_check_mark: User has been removed from the blacklist.")
.setColor(supportbot.Embed.Colours.Success);
const blacklistChannel = interaction.guild.channels.cache.get(supportbot.Ticket.Log.TicketBlacklistLog);
if (blacklistChannel) {
const blacklistLogEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketBlacklistLog.Title || "Ticket Blacklist Log")
.setColor(msgconfig.TicketBlacklistLog.Colour || supportbot.Embed.Colours.Success)
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
.setDescription(`> **User:** ${user.tag} (\`${user.id}\`)\n> **Actioned by:** <@${interaction.user.id}>`)
.addFields({ name: "Action", value: "\`\`\`Removed from blacklist\`\`\`", inline: false })
.addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: false });
blacklistChannel.send({ embeds: [blacklistLogEmbed] });
}
return interaction.reply({ embeds: [removedEmbed], flags: MessageFlags.Ephemeral });
} else if (subcommand === "view") {
if (blacklistedUsers.length === 0) {
const noBlacklistedUsersEmbed = new EmbedBuilder()
.setDescription(msgconfig.Mod.TicketBlacklist.View.NoBlacklistedUsers || "No users are blacklisted.")
.setColor(supportbot.Embed.Colours.Success);
return interaction.reply({ embeds: [noBlacklistedUsersEmbed], flags: MessageFlags.Ephemeral });
}
const chunkedUsers = chunkArray(blacklistedUsers, 5);
let currentPage = 0;
const createEmbed = (page) => {
return new EmbedBuilder()
.setTitle(msgconfig.Mod.TicketBlacklist.View.EmbedTitle || "Blacklisted Users")
.setDescription(chunkedUsers[page].map(id => `<@${id}>`).join("\n"))
.setColor(supportbot.Embed.Colours.Success)
.setFooter({ text: `Page ${page + 1} of ${chunkedUsers.length}` });
};
const buttons = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('previous')
.setLabel('Previous')
.setStyle(ButtonStyle.Secondary)
.setDisabled(currentPage === 0),
new ButtonBuilder()
.setCustomId('next')
.setLabel('Next')
.setStyle(ButtonStyle.Secondary)
.setDisabled(currentPage === chunkedUsers.length - 1)
);
const response = await interaction.reply({
embeds: [createEmbed(currentPage)],
components: chunkedUsers.length > 1 ? [buttons] : [],
flags: MessageFlags.Ephemeral
});
if (chunkedUsers.length <= 1) return;
const collector = response.createMessageComponentCollector({
componentType: ComponentType.Button,
time: 60000
});
collector.on('collect', async (i) => {
if (i.user.id !== interaction.user.id) {
return i.reply({
content: 'You cannot use these buttons.',
flags: MessageFlags.Ephemeral
});
}
if (i.customId === 'previous' && currentPage > 0) {
currentPage--;
} else if (i.customId === 'next' && currentPage < chunkedUsers.length - 1) {
currentPage++;
}
buttons.components[0].setDisabled(currentPage === 0);
buttons.components[1].setDisabled(currentPage === chunkedUsers.length - 1);
await i.update({
embeds: [createEmbed(currentPage)],
components: [buttons]
});
});
collector.on('end', () => {
buttons.components.forEach(button => button.setDisabled(true));
interaction.editReply({
components: [buttons]
}).catch(() => {});
});
}
} catch (error) {
console.error("Error in mod command:", error);
const errorEmbed = new EmbedBuilder()
.setDescription(msgconfig.Error.ActionFailed || "An error occurred while processing your request.")
.setColor(supportbot.Embed.Colours.Warn);
interaction.reply({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral });
}
}
});
+237
View File
@@ -0,0 +1,237 @@
const { Command } = require('../../Structures/Addon.js');
const Discord = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const db = require("../../Structures/Database.js");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
async function loadSettings() {
return new Promise((resolve, reject) => {
db.get("SELECT data FROM settings WHERE id = 1", (err, row) => {
if (err) return reject(err);
if (!row) return resolve({});
try {
resolve(JSON.parse(row.data));
} catch (e) {
resolve({});
}
});
});
}
async function saveSettings(settings) {
const data = JSON.stringify(settings);
return new Promise((resolve, reject) => {
db.run(
"INSERT INTO settings (id, data) VALUES (1, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data",
[data],
(err) => (err ? reject(err) : resolve())
);
});
}
module.exports = new Command({
name: cmdconfig.Settings.Command,
description: cmdconfig.Settings.Description,
permissions: cmdconfig.Settings.Permission,
options: [
{
name: 'setstatus',
type: Discord.ApplicationCommandOptionType.Subcommand,
description: 'Set the bot status',
options: [
{
name: 'status',
type: Discord.ApplicationCommandOptionType.String,
description: 'Choose the bot status',
required: true,
choices: [
{ name: 'Online 🟢', value: 'online' },
{ name: 'Idle 🌙', value: 'idle' },
{ name: 'Do Not Disturb ⛔', value: 'dnd' },
{ name: 'Invisible 👻', value: 'invisible' }
],
},
],
},
{
name: 'setactivity',
type: Discord.ApplicationCommandOptionType.Subcommand,
description: 'Set the bot activity',
options: [
{
name: 'type',
type: Discord.ApplicationCommandOptionType.String,
description: 'Choose the activity type',
required: true,
choices: [
{ name: 'Playing 🎮', value: 'PLAYING' },
{ name: 'Watching 👀', value: 'WATCHING' },
{ name: 'Listening 🎧', value: 'LISTENING' },
{ name: 'Competing 🏆', value: 'COMPETING' }
],
},
{
name: 'message',
type: Discord.ApplicationCommandOptionType.String,
description: 'The activity message',
required: true,
},
],
},
{
name: 'streammode',
type: Discord.ApplicationCommandOptionType.Subcommand,
description: 'Set the bot streaming status',
options: [
{
name: 'toggle',
type: Discord.ApplicationCommandOptionType.String,
description: 'Turn streaming mode on or off',
required: true,
choices: [
{ name: 'On', value: 'on' },
{ name: 'Off', value: 'off' }
],
},
{
name: 'twitchurl',
type: Discord.ApplicationCommandOptionType.String,
description: 'Twitch stream URL (Required if turning on)',
required: false,
},
],
},
{
name: 'setnickname',
type: Discord.ApplicationCommandOptionType.Subcommand,
description: 'Set the bot nickname',
options: [
{
name: 'nickname',
type: Discord.ApplicationCommandOptionType.String,
description: 'The new nickname for the bot',
required: true,
},
],
},
],
async run(interaction) {
const subcommand = interaction.options.getSubcommand();
const settings = await loadSettings();
if (subcommand === 'setstatus') {
const status = interaction.options.getString('status');
try {
interaction.client.user.setPresence({
status: status,
activities: interaction.client.user.presence.activities || [],
});
settings.status = status;
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Status set to **${status.charAt(0).toUpperCase() + status.slice(1)}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to set status: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
}
if (subcommand === 'setactivity') {
const activityType = interaction.options.getString('type');
const activityMessage = interaction.options.getString('message');
try {
interaction.client.user.setPresence({
activities: [{ name: activityMessage, type: Discord.ActivityType[activityType] }],
status: interaction.client.user.presence.status || 'online',
});
settings.activity = { type: activityType, message: activityMessage };
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Activity set to **${activityType.toLowerCase()} ${activityMessage}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to set activity: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
} else if (subcommand === 'streammode') {
const toggle = interaction.options.getString('toggle');
const twitchUrl = interaction.options.getString('twitchurl');
if (toggle === 'on') {
if (!twitchUrl) {
return interaction.reply({ content: 'Please provide a Twitch URL to stream.', flags: Discord.MessageFlags.Ephemeral });
}
try {
interaction.client.user.setPresence({
activities: [{ name: 'Streaming', type: Discord.ActivityType.Streaming, url: twitchUrl }],
status: 'online',
});
settings.streaming = { active: true, url: twitchUrl };
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`**Streaming mode enabled!**\nStreaming: [Twitch](${twitchUrl})`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to enable streaming mode: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
} else if (toggle === 'off') {
try {
interaction.client.user.setPresence({
activities: interaction.client.user.presence.activities.filter(a => a.type !== Discord.ActivityType.Streaming),
status: settings.status || 'online',
});
settings.streaming = { active: false };
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`**Streaming mode disabled!**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to disable streaming mode: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
}
} else if (subcommand === 'setnickname') {
const nickname = interaction.options.getString('nickname');
try {
const guild = interaction.guild;
const botMember = guild.members.cache.get(interaction.client.user.id);
await botMember.setNickname(nickname);
settings.nickname = nickname;
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Bot nickname set to **${nickname}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to set nickname: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
} else {
return interaction.reply({ content: 'Invalid command usage.', flags: Discord.MessageFlags.Ephemeral });
}
},
});
-92
View File
@@ -1,92 +0,0 @@
// SupportBot | Emerald Services
// Ticket Add Command
const fs = require("fs");
const Discord = 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 Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.TicketAdd,
description: cmdconfig.TicketAddDesc,
options: [
{
name: "user",
description: "User to add",
type: "USER",
required: true,
},
],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
const { getRole, getChannel, getCategory } = interaction.client;
let SupportStaff = await getRole(supportbot.Staff, interaction.guild);
let Admin = await getRole(supportbot.Admin, interaction.guild);
if (!SupportStaff || !Admin)
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
const NoPerms = new Discord.MessageEmbed()
.setTitle("Invalid Permissions!")
.setDescription(
`${supportbot.IncorrectPerms}\n\nRole Required: \`${supportbot.Staff}\` or \`${supportbot.Admin}\``
)
.setColor(supportbot.WarningColour);
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
)
return interaction.reply({ embeds: [NoPerms] });
let TicketData = await JSON.parse(
fs.readFileSync("./Data/TicketData.json", "utf8")
);
let ticket = await TicketData.tickets.findIndex(
(t) => t.id == interaction.channel.id
);
if (ticket == -1) {
const Exists = new Discord.MessageEmbed()
.setTitle("No Ticket Found!")
.setDescription(`${supportbot.NoValidTicket}`)
.setColor(supportbot.WarningColour);
return interaction.reply({ embeds: [Exists] });
}
let uMember = interaction.options.getUser("user");
const UserNotExist = new Discord.MessageEmbed()
.setTitle("User Not Found!")
.setDescription(
`${supportbot.UserNotFound}\n\nTry Again:\`/${cmdconfig.TicketAdd} <user#0000>\``
)
.setColor(supportbot.ErrorColour);
if (!uMember) return interaction.reply({ embeds: [UserNotExist] });
interaction.channel.permissionOverwrites.edit(uMember.id, {
VIEW_CHANNEL: true,
});
const Complete = new Discord.MessageEmbed()
.setTitle("User Added!")
.setDescription(supportbot.AddedUser.replace(/%user%/g, uMember.id))
.setTimestamp()
.setColor(supportbot.EmbedColour);
interaction.reply({ embeds: [Complete] });
TicketData.tickets[ticket].subUsers.push(uMember.id);
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(TicketData, null, 4),
(err) => {
if (err) console.error(err);
}
);
},
});
-182
View File
@@ -1,182 +0,0 @@
// SupportBot | Emerald Services
// Close Ticket Command
const fs = require("fs");
const Discord = 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 Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.CloseTicket,
description: cmdconfig.CloseTicketDesc,
options: [
{
name: "reason",
description: "Ticket Close Reason",
type: "STRING",
},
],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
const { getRole, getChannel, getCategory } = interaction.client;
if (supportbot.StaffOnly) {
let SupportStaff = await getRole(supportbot.Staff, interaction.guild);
let Admin = await getRole(supportbot.Admin, interaction.guild);
if (!SupportStaff || !Admin)
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
const NoPerms = new Discord.MessageEmbed()
.setTitle("Invalid Permissions!")
.setDescription(
`${supportbot.IncorrectPerms}\n\nRole Required: \`${supportbot.Staff}\` or \`${supportbot.Admin}\``
)
.setColor(supportbot.WarningColour);
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
)
return interaction.reply({ embeds: [NoPerms] });
}
await interaction.deferReply();
let tickets = await JSON.parse(
fs.readFileSync("./Data/TicketData.json", "utf8")
).tickets.find((t) => t.id === interaction.channel.id);
let TicketData = await tickets.tickets.findIndex(
(t) => t.id == ticketChannel.id
);
if (!TicketData) {
const Exists = new Discord.MessageEmbed()
.setTitle("No Ticket Found!")
.setDescription(`${supportbot.NoValidTicket}`)
.setColor(supportbot.WarningColour);
return interaction.followUp({ embeds: [Exists] });
}
let tUser = interaction.client.users.cache.get(TicketData.user);
let transcriptChannel = await getChannel(
supportbot.TranscriptLog,
interaction.guild
);
let logChannel = await getChannel(supportbot.TicketLog, interaction.guild);
let reason =
(await interaction.options?.getString("reason")) || "No Reason Provided.";
if (!transcriptChannel || !logChannel)
return interaction.followUp("Some Channels seem to be missing!");
if (supportbot.CloseConfirmation) {
const CloseTicketRequest = new Discord.MessageEmbed()
.setTitle(`**${supportbot.ClosingTicket}**`)
.setDescription(
`Please confirm by repeating the following word.. \`${supportbot.ClosingConfirmation_Word}\` `
)
.setColor(supportbot.EmbedColour);
await interaction.followUp({ embeds: [CloseTicketRequest] });
let filter = (m) =>
m.content.toLowerCase() == supportbot.ClosingConfirmation_Word &&
m.author.id == interaction.user.id;
await interaction.channel.awaitMessages({
filter,
max: 1,
time: 20000,
errors: ["time"],
});
}
try {
tickets.tickets[TicketData].open = false;
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(tickets, null, 4),
(err) => {
if (err) console.error(err);
}
);
await interaction.followUp(`**${supportbot.ClosingTicket}**`);
const transcriptEmbed = new Discord.MessageEmbed()
.setTitle(supportbot.TranscriptTitle)
.setColor(supportbot.EmbedColour)
.setFooter(supportbot.EmbedFooter)
.addField(
"Ticket",
`${interaction.channel.name} (${interaction.channel.id})`
)
.addField(
"User",
`${tUser.username}#${tUser.discriminator} (${tUser.id})`
)
.addField("Closed By", interaction.user.tag)
.addField("Reason", reason);
const logEmbed = new Discord.MessageEmbed()
.setTitle(supportbot.TicketLog_Title)
.setColor(supportbot.EmbedColour)
.setFooter(supportbot.EmbedFooter)
.addField(
"Ticket",
`${interaction.channel.name} (${interaction.channel.id})`
)
.addField(
"User",
`${tUser.username}#${tUser.discriminator} (${tUser.id})`
)
.addField("Closed By", interaction.user.tag)
.addField("Reason", reason);
let msgs = await interaction.channel.messages.fetch();
let html = "";
msgs = msgs.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
html += `<style>* {background-color: #2c2f33;color: #fff;font-family: Arial, Helvetica, sans-serif;}</style>`;
html += `<strong>Server Name:</strong> ${interaction.guild.name}<br>`;
html += `<strong>Ticket:</strong> ${interaction.channel.name}<br>`;
html += `<strong>Message:</strong> ${msgs.size} Messages<br><br><br>`;
await msgs.forEach((msg) => {
if (msg.content) {
html += `<strong>User:</strong> ${msg.author.tag}<br>`;
html += `<strong>Message:</strong> ${msg.content}<br>`;
html += `-----<br><br>`;
}
});
await logChannel.send({ embeds: [logEmbed] }).catch((err) => {
console.error(err);
});
let file = new Discord.MessageAttachment(
Buffer.from(html),
`${interaction.channel.name}.html`
);
await transcriptChannel
.send({ embeds: [transcriptEmbed], files: [file] })
.catch(async (err) => {
console.error(err);
});
await tUser
.send({ embeds: [transcriptEmbed], files: [file] })
.catch(async (err) => {
console.error(err);
});
if (TicketData.subUsers) {
TicketData.subUsers.forEach(async (subUser) => {
interaction.client.users.cache
.get(subUser)
.send({ embeds: [transcriptEmbed], files: [file] });
});
}
await interaction.channel.delete().catch(async (error) => {
console.error(error);
if (error.code !== Discord.Constants.APIErrors.UNKNOWN_CHANNEL) {
console.error("Failed to delete the interaction:", error);
}
});
} catch (error) {
console.error(error);
}
},
});
-67
View File
@@ -1,67 +0,0 @@
// SupportBot | Emerald Services
// Embed Command
const fs = require("fs");
const Discord = 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 Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.EmbedCommand,
description: cmdconfig.EmbedCommandDesc,
options: [
{
name: "title",
description: "Embed Title",
type: "STRING",
},
{
name: "message",
description: "Embed Message",
type: "STRING",
},
],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
const { getRole, getChannel, getCategory } = interaction.client;
let SupportStaff = await getRole(supportbot.Staff, interaction.guild);
let Admin = await getRole(supportbot.Admin, interaction.guild);
if (!SupportStaff || !Admin)
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
const NoPerms = new Discord.MessageEmbed()
.setTitle("Invalid Permissions!")
.setDescription(
`${supportbot.IncorrectPerms}\n\nRole Required: \`${supportbot.Staff}\` or \`${supportbot.Admin}\``
)
.setColor(supportbot.WarningColour);
if (
interaction.member.roles.cache.has(SupportStaff.id) ||
interaction.member.roles.cache.has(Admin.id)
) {
const EmbedTitle = interaction.options.getString("title");
const EmbedSubject = interaction.options.getString("message");
const EmbedMsg = new Discord.MessageEmbed()
.setTitle(EmbedTitle)
.setDescription(EmbedSubject);
interaction.reply({
embeds: [EmbedMsg],
});
} else {
return interaction.reply({ embeds: [NoPerms] });
}
},
});
-92
View File
@@ -1,92 +0,0 @@
// SupportBot | Emerald Services
// Help Command
const fs = require("fs");
const Discord = 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 Command = require("../Structures/Command.js");
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
module.exports = new Command({
name: cmdconfig.HelpCommand,
description: cmdconfig.HelpCommandDesc,
options: [],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
const { getRole, getChannel, getCategory } = interaction.client;
let SupportStaff = await getRole(supportbot.Staff, interaction.guild);
let Admin = await getRole(supportbot.Admin, interaction.guild);
if (!SupportStaff || !Admin)
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
let botCommands = "";
botCommands += `**/${cmdconfig.HelpCommand}** ${cmdconfig.HelpCommandDesc}\n`;
botCommands += `**/${cmdconfig.InfoCommand}** ${cmdconfig.InfoCommandDesc}\n`;
botCommands += `**/${cmdconfig.PingCommand}** ${cmdconfig.PingCommandDesc}\n`;
let ticketCommands = "";
ticketCommands += `**/${cmdconfig.OpenTicket}** ${cmdconfig.OpenTicketDesc}\n`;
ticketCommands += `**/${cmdconfig.CloseTicket}** ${cmdconfig.CloseTicketDesc}\n`;
let staffCommands = "";
staffCommands += `**/${cmdconfig.TicketAdd}** ${cmdconfig.TicketAddDesc}\n`;
staffCommands += `**/${cmdconfig.TicketRemove}** ${cmdconfig.TicketRemoveDesc}\n`;
const HelpEmbed1 = new Discord.MessageEmbed()
.setTitle(supportbot.Name + " Commands")
.setThumbnail(interaction.user.displayAvatarURL())
.addFields(
{
name: "🖥️ General Commands\n",
value: `${botCommands}\n`,
inline: false,
},
{
name: "🎫 Support Commands\n",
value: `${ticketCommands}\n`,
inline: false,
}
)
.setColor(supportbot.EmbedColour)
.setFooter(supportbot.EmbedFooter, interaction.user.displayAvatarURL());
if (
interaction.member.roles.cache.has(SupportStaff.id) ||
interaction.member.roles.cache.has(Admin.id)
) {
HelpEmbed1.addFields({
name: "🔐 Staff Commands\n",
value: `${staffCommands}\n`,
inline: false,
});
}
if (supportbot.SendHelpPage === "dm") {
interaction.user.reply({
embeds: [HelpEmbed1],
});
}
if (supportbot.SendHelpPage === "channel") {
interaction.reply({
embeds: [HelpEmbed1],
});
}
},
});
-40
View File
@@ -1,40 +0,0 @@
// SupportBot | Emerald Services
// Info Command
const fs = require("fs");
const Discord = 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 Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.InfoCommand,
description: cmdconfig.InfoCommandDesc,
options: [],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
const InfoButton = new Discord.MessageButton()
.setLabel(supportbot.InfoButtonText)
.setURL(supportbot.InfoURL)
.setStyle("LINK");
const inforow = new Discord.MessageActionRow().addComponents(InfoButton);
const InfoEmbed = new Discord.MessageEmbed()
.setURL(supportbot.InfoURL)
.setTitle(supportbot.InfoTitle)
.setDescription(supportbot.AboutMe)
.setColor(supportbot.InfoColour);
interaction.reply({
embeds: [InfoEmbed],
components: [inforow],
});
},
});
-100
View File
@@ -1,100 +0,0 @@
// SupportBot | Emerald Services
// Ticket Remove Command
const fs = require("fs");
const Discord = 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 Command = require("../Structures/Command.js");
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
module.exports = new Command({
name: cmdconfig.TicketRemove,
description: cmdconfig.TicketRemoveDesc,
options: [
{
name: "user",
description: "User to remove",
type: "USER",
required: true,
},
],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
const { getRole, getChannel, getCategory } = interaction.client;
let Staff = await getRole(supportbot.Staff, interaction.guild);
let Admin = await getRole(supportbot.Admin, interaction.guild);
if (!Staff || !Admin)
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
const NoPerms = new Discord.MessageEmbed()
.setTitle("Invalid Permissions!")
.setDescription(
`${supportbot.IncorrectPerms}\n\nRole Required: \`${supportbot.Staff}\` or \`${supportbot.Admin}\``
)
.setColor(supportbot.WarningColour);
if (
!interaction.member.roles.cache.has(Staff.id) &&
!interaction.member.roles.cache.has(Admin.id)
)
return interaction.reply({ embeds: [NoPerms] });
let TicketData = await JSON.parse(
fs.readFileSync("./Data/TicketData.json", "utf8")
);
let ticket = await TicketData.tickets.findIndex(
(t) => t.id == interaction.channel.id
);
if (ticket == -1) {
const Exists = new Discord.MessageEmbed()
.setTitle("No Ticket Found!")
.setDescription(`${supportbot.NoValidTicket}`)
.setColor(supportbot.WarningColour);
return interaction.reply({ embeds: [Exists] });
}
let uMember = interaction.options.getUser("user");
const UserNotExist = new Discord.MessageEmbed()
.setTitle("User Not Found!")
.setDescription(
`${supportbot.UserNotFound}\n\nTry Again:\`/${cmdconfig.TicketRemove} <user#0000>\``
)
.setColor(supportbot.ErrorColour);
if (!uMember) return interaction.reply({ embeds: [UserNotExist] });
interaction.channel.permissionOverwrites.edit(uMember.id, {
VIEW_CHANNEL: false,
});
const Complete = new Discord.MessageEmbed()
.setTitle("User Removed!")
.setDescription(supportbot.RemovedUser.replace(/%user%/g, uMember.id))
.setTimestamp()
.setColor(supportbot.EmbedColour);
interaction.reply({ embeds: [Complete] });
TicketData.tickets[ticket].subUsers = TicketData.tickets[
ticket
].subUsers.filter((u) => u != uMember.id);
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(TicketData, null, 4),
(err) => {
if (err) console.error(err);
}
);
},
});
-66
View File
@@ -1,66 +0,0 @@
// SupportBot | Emerald Services
// Ping Command
const fs = require("fs");
const Discord = 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 Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.SuggestCommand,
description: cmdconfig.SuggestCommandDesc,
options: [
{
name: "suggestion",
description: "Create a Suggestion",
type: "STRING",
},
],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
const { getRole, getChannel, getCategory } = interaction.client;
const suggestChannel = await getChannel(
supportbot.SuggestionChannel,
interaction.guild
);
const NoChannel = new Discord.MessageEmbed()
.setTitle("Missing Channel!")
.setDescription(`${supportbot.InvalidChannel}`)
.setColor(supportbot.ErrorColour);
if (!suggestChannel) return interaction.reply({ embeds: [NoChannel] });
let suggestion = interaction.options.getString("suggestion");
const SuggestEmbed = new Discord.MessageEmbed()
.addField("Suggestion", suggestion, true)
.addField("From", `<@${interaction.user.id}>`)
.setThumbnail(interaction.user.displayAvatarURL())
.setFooter(supportbot.EmbedFooter, interaction.user.displayAvatarURL())
.setColor(supportbot.EmbedColour);
const suggestionMsg = await suggestChannel.send({ embeds: [SuggestEmbed] });
if (supportbot.SuggestionUpvote && supportbot.SuggestionDownvote) {
suggestionMsg.react(supportbot.SuggestionUpvote);
suggestionMsg.react(supportbot.SuggestionDownvote);
}
const Submitted = new Discord.MessageEmbed()
.setTitle("Suggestion Submitted!")
.setDescription(
`:white_check_mark: You have successfully submitted a suggestion.`
)
.addField("Sent to:", `<#${suggestChannel.id}>`)
.setColor(supportbot.SuccessColour);
return interaction.reply({ embeds: [Submitted] });
},
});
-321
View File
@@ -1,321 +0,0 @@
// SupportBot | Emerald Services
// Ticket Command
const fs = require("fs");
const {
MessageEmbed,
Permissions,
MessageButton,
MessageActionRow,
} = 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 Command = require("../Structures/Command.js");
const TicketNumberID = require("../Structures/TicketID.js");
module.exports = new Command({
name: cmdconfig.OpenTicket,
description: cmdconfig.OpenTicketDesc,
options: [
{
name: "reason",
description: "Ticket Reason",
type: "STRING",
},
],
permissions: ["SEND_MESSAGES"],
async run(interaction) {
let TicketData = await JSON.parse(
fs.readFileSync("./Data/TicketData.json", "utf8")
);
const { getRole, getChannel, getCategory } = interaction.client;
let User = interaction.guild.members.cache.get(interaction.user.id);
if (
supportbot.MaxAllowedTickets &&
TicketData.tickets.filter((t) => t.user == interaction.user.id && t.open)
.length >= supportbot.MaxAllowedTickets
) {
return interaction.reply({
embeds: [
{
title: "Too Many Tickets!",
description: `You can't have more than ${supportbot.MaxAllowedTickets} open tickets!`,
color: supportbot.WarningColour,
},
],
ephemeral: true,
});
}
if (
User.roles.cache.has(
getRole(supportbot.TicketBlackListRole, interaction.guild).id
)
) {
return interaction.reply({
content: supportbot.TicketBlackListMessage,
ephemeral: true,
});
}
// Ticket ID
let ticketNumberID = await TicketNumberID.pad();
// Ticket Subject
const TicketSubject =
interaction.options?.getString("reason") || supportbot.NoTicketSubject;
const TicketExists = new MessageEmbed()
.setTitle("Ticket Exists!")
.setDescription(`${supportbot.TicketExists}`)
.setColor(supportbot.WarningColour);
if (
await interaction.guild.channels.cache.find((ticketChannel) => {
ticketChannel.name === `${supportbot.TicketPrefix}${ticketNumberID}`;
})
) {
return await interaction.reply({ embeds: [TicketExists] });
}
const Staff = await getRole(supportbot.Staff, interaction.guild);
const Admin = await getRole(supportbot.Admin, interaction.guild);
const DeptRole1 = await getRole(
supportbot.DepartmentRole_1,
interaction.guild
);
const DeptRole2 = await getRole(
supportbot.DepartmentRole_2,
interaction.guild
);
const DeptRole3 = await getRole(
supportbot.DepartmentRole_3,
interaction.guild
);
if (!Staff || !Admin || !DeptRole1 || !DeptRole2 || !DeptRole3)
return interaction.reply({
content:
"Some roles seem to be missing!\nPlease check for errors when starting the bot.",
ephemeral: true,
});
const Author = interaction.user;
let TicketCategory = await getCategory(
supportbot.TicketCategory,
interaction.guild
);
const ticketChannel = await interaction.guild.channels.create(
`${supportbot.TicketPrefix}${ticketNumberID}`,
{
type: "GUILD_TEXT",
parent: TicketCategory.id,
permissionOverwrites: [
{
id: Admin.id,
allow: [Permissions.FLAGS.VIEW_CHANNEL],
},
{
id: Author.id,
allow: [Permissions.FLAGS.VIEW_CHANNEL],
deny: [Permissions.FLAGS.SEND_MESSAGES],
},
{
id: interaction.guild.id,
deny: [Permissions.FLAGS.VIEW_CHANNEL],
},
],
}
);
if (supportbot.AllowStaff) {
ticketChannel.permissionOverwrites.edit(Staff.id, {
VIEW_CHANNEL: true,
});
}
await TicketData.tickets.push({
id: ticketChannel.id,
name: ticketChannel.name,
user: Author.id,
number: ticketNumberID,
reason: TicketSubject,
open: true,
subUsers: [],
});
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(TicketData, null, 4),
(err) => {
if (err) console.error(err);
}
);
const CreatedTicket = new MessageEmbed()
.setDescription(
supportbot.TicketCreatedAlert.replace(
/%ticketauthor%/g,
interaction.user.id
)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
)
.setColor(supportbot.EmbedColour);
await interaction.reply({ embeds: [CreatedTicket], ephemeral: true });
if (supportbot.AllowTicketMentions) {
await ticketChannel.send(`${interaction.user}`);
}
const TicketMessage = new MessageEmbed()
.setTitle(
supportbot.Ticket_Title.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
)
.setDescription(
supportbot.TicketMessage.replace(/%ticketauthor%/g, interaction.user.id)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
)
.setColor(supportbot.EmbedColour);
if (supportbot.TicketSubject === "embed") {
if (TicketSubject != "No Reason Provided.") {
TicketMessage.addFields({ name: "Reason", value: TicketSubject });
}
}
if (supportbot.TicketSubject === "description") {
if (TicketSubject != "No Reason Provided.") {
await ticketChannel.setTopic(
`Reason: ${TicketSubject} - User ID: ${interaction.user.id} - Ticket: ${ticketChannel.name}`
);
}
}
if (supportbot.TicketDepartments) {
TicketMessage.addFields({
name: "Departments",
value: ` **${supportbot.DepartmentTitle_1}**\n **${supportbot.DepartmentTitle_2}**\n **${supportbot.DepartmentTitle_3}**`,
});
}
const CloseButton = new MessageButton()
.setCustomId("ticketclose")
.setLabel("Close")
.setStyle(supportbot.TicketCloseColour)
.setEmoji(supportbot.TicketCloseEmoji);
const LockButton = new MessageButton()
.setCustomId("ticketlock")
.setLabel("Lock")
.setStyle(supportbot.TicketLockColour)
.setEmoji(supportbot.TicketLockEmoji);
const row2 = new MessageActionRow().addComponents(CloseButton, LockButton);
if (supportbot.TicketDepartments) {
try {
const Department1Button = new MessageButton()
.setCustomId("Department1")
.setLabel(supportbot.DepartmentTitle_1)
.setStyle(supportbot.TicketDept1Colour)
.setEmoji(supportbot.TicketDept1Emoji);
const Department2Button = new MessageButton()
.setCustomId("Department2")
.setLabel(supportbot.DepartmentTitle_2)
.setStyle(supportbot.TicketDept2Colour)
.setEmoji(supportbot.TicketDept2Emoji);
const Department3Button = new MessageButton()
.setCustomId("Department3")
.setLabel(supportbot.DepartmentTitle_3)
.setStyle(supportbot.TicketDept3Colour)
.setEmoji(supportbot.TicketDept3Emoji);
const row = new MessageActionRow().addComponents(
Department1Button,
Department2Button,
Department3Button
);
const m = await ticketChannel.send({
embeds: [TicketMessage],
components: [row],
});
const filter = (i) => i.user.id === interaction.user.id;
let collector;
try {
collector = await m.awaitMessageComponent({
filter,
max: 1,
componentType: "BUTTON",
time: supportbot.Timeout * 60000,
});
} catch (e) {
if (e.code == "INTERACTION_COLLECTOR_ERROR")
try {
let ticket = await TicketData.tickets.findIndex(
(t) => t.id == ticketChannel.id
);
TicketData.tickets[ticket].open = false;
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(TicketData, null, 4),
(err) => {
if (err) console.error(err);
}
);
interaction.user.send(
"Your ticket has timed out. Please open a new one, and select a department."
);
} catch (e) {}
return await ticketChannel.delete();
}
let role;
let title;
if (collector.customId === "Department1") {
role = DeptRole1;
title = supportbot.DepartmentTitle_1;
}
if (collector.customId === "Department2") {
role = DeptRole2;
title = supportbot.DepartmentTitle_2;
}
if (collector.customId === "Department3") {
role = DeptRole3;
title = supportbot.DepartmentTitle_3;
}
ticketChannel.permissionOverwrites.edit(role.id, {
VIEW_CHANNEL: true,
});
ticketChannel.permissionOverwrites.edit(Author.id, {
SEND_MESSAGES: true,
});
await collector.reply({
embeds: [
{
description: `> Thank for reaching out to the **${title} Department**. Please provide us information regarding your query.`,
color: supportbot.EmbedColour,
},
],
});
if (supportbot.AllowTicketMentions) {
await ticketChannel.send("@here");
}
await collector.message.edit({
embeds: [TicketMessage],
components: [row2],
});
} catch (error) {}
} else {
await ticketChannel.send({ embeds: [TicketMessage], components: [row2] });
}
},
});
+132 -14
View File
@@ -7,29 +7,146 @@
# SupportBot created by Emerald Services
# Installed with MIT License
#
# Discord Support: https://emeraldsrv.dev/discord
# Community Resources: https://emeraldsrv.dev/resources
# Discord Support: https://discord.gg/emeraldsrv
# Official Documentation: https://docs.emeraldsrv.com
# Addons Marketplace: https://community.emeraldsrv.com
#
# Commands Configuration (commands.yml)
# -------------------------------
# Command Name Configuration
# -------------------------------
Help:
Command: "help"
Description: "Get a list of all the commands."
Permission: ["SendMessages"]
Enabled: true
TicketAdd: "add"
TicketRemove: "remove"
OpenTicket: "ticket"
CloseTicket: "close"
InfoCommand: "info"
PingCommand: "ping"
HelpCommand: "help"
EmbedCommand: "embed"
SuggestCommand: "suggest"
Info:
Command: "info"
Description: "Get information about the **ServerName**."
Permission: ["SendMessages"]
Enabled: true
TicketPanel:
Command: "ticketpanel"
Description: "Create the ticket creation panel"
Permission: ["Administrator"]
Enabled: true
Profile:
Command: "profile"
Description: "Shows the profile for a discord member."
Permission: ["SendMessages"]
# Command is automatically disabled when the TicketClaims is disabled.
OpenTicket:
Command: "ticket"
Description: "Create a support ticket."
Permission: ["SendMessages"]
Enabled: true
AddUser:
Command: "add"
Description: "Add a user to a support ticket."
Permission: ["SendMessages"]
Enabled: true
ForceAddUser:
Command: "forceadd"
Description: "Add a user to a support ticket by bypassing the invite system."
Permission: ["SendMessages"]
Enabled: true
RemoveUser:
Command: "remove"
Description: "Remove a user from a support ticket."
Permission: ["SendMessages"]
Enabled: true
CloseTicket:
Command: "close"
Description: "Close a support ticket"
Permission: ["SendMessages"]
Enabled: true
Suggestion:
Command: "suggest"
Description: "Create a suggestion."
Permission: ["SendMessages"]
Enabled: true
SuggestionAdmin:
Command: "suggestadmin"
Description: "Update a suggestion’s status."
Permission: ['SendMessages']
# Disable "Suggestion" to disable this command.
Embed:
Command: "embed"
Description: "Build a discord embed."
Permission: ["SendMessages"]
Enabled: true
Translate:
Command: "translate"
Description: "Translate something to english"
Permission: ["SendMessages"]
Enabled: false
UserInfo:
Command: "userinfo"
Description: "Get info about a user."
Permission: ["SendMessages"]
Enabled: true
Ping:
Command: "ping"
Description: "Pong."
Permission: ["SendMessages"]
Enabled: true
# ---------------------------------------
# Bot Moderator Commands
# ---------------------------------------
Mod:
Command: "mod"
Description: "Execute a moderation action."
Permission: ["BanMembers", "KickMembers"]
Enabled: true
TicketBlacklist:
Command: "ticket-blacklist"
Description: "Blacklist a user from creating Tickets."
Add:
Command: "add"
Description: "Add a user to the blacklist."
Remove:
Command: "remove"
Description: "Remove a user from the blacklist."
View:
Command: "view"
Description: "View all blacklisted users."
# ---------------------------------------
# Bot Administrator Commands
# ---------------------------------------
Settings:
Command: "settings"
Description: "Update bot settings."
Permission: ["Administrator"]
Enabled: true
# ---------------------------------------
# Command Description Configuration
# ---------------------------------------
BlacklistDesc: "Blacklist a user from creating Tickets."
TicketAddDesc: "Add a user to a support ticket."
TicketRemoveDesc: "Remove a user from a support ticket."
TranslateCommandDesc: "Translates the language provided, along with the abbreviation (2 char) long language code"
OpenTicketDesc: "Open a new support ticket."
CloseTicketDesc: "Close your support ticket."
InfoCommandDesc: "Get information about the brand."
@@ -37,3 +154,4 @@ PingCommandDesc: "Pong!"
HelpCommandDesc: "Get a list of all the commands."
EmbedCommandDesc: "Send a message as an embed."
SuggestCommandDesc: "Suggest a feature for the server."
UserInfoCommandDesc: "Get info about a user."
+323
View File
@@ -0,0 +1,323 @@
# ___ _ ___ _
# / __> _ _ ___ ___ ___ _ _ _| |_ | . > ___ _| |_
# \__ \| | || . \| . \/ . \| '_> | | | . \/ . \ | |
# <___/`___|| _/| _/\___/|_| |_| |___/\___/ |_|
# |_| |_|
#
# SupportBot created by Emerald Services
# Installed with MIT License
#
# Discord Support: https://discord.gg/emeraldsrv
# Official Documentation: https://docs.emeraldsrv.com
# Addons Marketplace: https://community.emeraldsrv.com
#
# Messages Configuration (messages.yml)
# ----------------------------
# Info Command
# ----------------------------
Info:
Title: "About Us"
Description: "SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand."
Button: "Visit Site"
URL: "https://docs.emeraldsrv.com"
Colour: "#45b463"
# ----------------------------
# Welcome/Leave Messages
# ----------------------------
Welcome:
Enabled: true
LayoutStyle: "modern"
# Available Layout Styles
# "modern" - For the new Discord v2 components
# "classic" - Use the classic Discord look.
Embed:
Colour: "#45b463"
Title: "Welcome to ServerName"
Message: ":smile: %joined_user% has just landed!\n\n🔨 **Server Rules:** <#1202667199853232129>"
Icon:
Mode: "USER" # USER | BOT | CUSTOM
CustomURL: "https://example.com/custom.png"
Image:
Enabled: false
URL: "https://example.com/banner.png"
Buttons:
Enabled: true
List:
- label: "BuiltByBit"
style: "Link"
url: "https://your-website.com/how-to-play-java"
emoji: "⌨️"
- label: "GitHub"
style: "Link"
url: "https://your-website.com/how-to-play-bedrock"
emoji: "👾"
- label: "Tebex"
style: "Link"
url: "https://your-store.tebex.io/"
emoji: "🛒"
Modern:
Colour: "#45b463"
Title: "**Welcome to ServerName**"
Body: ":smile: %joined_user% has just landed!\n\n🔨 **Server Rules:** <#1202667199853232129>"
Icon:
Mode: "BOT" # USER | BOT | CUSTOM
CustomURL: "https://example.com/custom-icon.png"
Image:
Enabled: false
URL: "https://example.com/banner.png"
Buttons:
- label: "BuiltByBit"
style: "Link"
url: "https://your-website.com/how-to-play-java"
emoji: "⌨️"
- label: "GitHub"
style: "Link"
url: "https://your-website.com/how-to-play-bedrock"
emoji: "👾"
- label: "Tebex"
style: "Link"
url: "https://your-store.tebex.io/"
emoji: "🛒"
Leave:
Embed:
Colour: "#f74343"
Title: "Goodbye!"
Message: ":slight_frown: %left_user% has recently dropped out!"
Thumbnail: "BOT"
ImageEnabled: false
ImageURL: "https://animagelink.png/"
# ----------------------------
# Suggestion Messages
# ----------------------------
Suggestions:
Sent_Title: "Suggestion Sent!"
Sent: ":white_check_mark: **You have successfully sent a suggestion.**"
NoSelfVote: ":x: **You cannot vote on your own suggestion.**"
RemoveVoteButton: "Remove Vote"
# ----------------------------
# Ticket Messages
# ----------------------------
Ticket:
# Available Placeholders:
# %ticketusername% - User who created the ticket.
# <@%ticketauthor%> - Tags the user who created the ticket.
# <#%ticketid%> - Mentions the ticket channel.
TicketAuthorTitle: "%ticketusername%'s Ticket"
TicketTitle: "Support Ticket"
TicketMessage: "Hello <@%ticketauthor%>, Thank you for creating a support ticket\nPlease wait patiently whilst a member of our team reaches out to you."
TicketThumbnail: "https://images.emojiterra.com/google/android-pie/512px/1f39f.png"
TicketCreatedAlert: ":ticket: Ticked Created: <#%ticketid%>"
TicketExists: "❌ Ticket already exists"
TicketClaim: ":white_check_mark: Ticket claimed by" #The user who claimed the ticket will be added to the end of this message
TicketUnclaim: ":white_check_mark: Ticket unclaimed by" #The user who claimed the ticket will be added to the end of this message
TicketArchived: ":white_check_mark: Ticket archived."
TicketUnarchived: ":white_check_mark: Ticket unarchived."
TicketLocked: ":lock: Ticket is locked."
DisableInvites: "🔒 Invites have been disabled for this ticket."
EnableInvites: "🔓 Invites have been enabled for this ticket."
# Available Placeholders:
# <@%user%> - Tags the user who was added to the ticket.
Blacklisted: ":x: You are not allowed to open a ticket"
Muted: ":x: You are `Muted` and therefore not allowed to open any tickets"
InvalidSubject: "No Reason Provided!"
ConfirmClose: "Please confirm closing the ticket by clicking the button below."
CloseTimeout: "Ticket close confirmation timed out."
RenamedSuccess: "🏷️ Ticket renamed successfully!"
ClaimTickets:
ClaimTitle: "Incoming Support Request!"
ClaimMessage: "A new ticket has been created by <@!%user%>. Click the button below to claim this ticket."
ClaimMessage_Edit: "<#%channel%> was claimed by <@!%user%>."
ClaimedTitle: "Ticket Claimed!"
Claimed: "You have successfully claimed this ticket. <#%channel%>"
NoPermsToClaimTitle: "Error, no permissions!"
NoPermsToClaim: ":x: **Err!** You do not permissions to claim this ticket."
ReassigningTitle: "Reassigning!..."
ReassigningMessage: "<@%user%> did not claim the ticket. **Reassigning...**"
TicketQuestions:
Details:
Title: "Ticket Details"
Description: "Questions by <@%user%>" # Use <@%user%> to display the username.
# ----------------------------
# Review Messages
# ----------------------------
ReviewSystem:
Rate:
Title: "Review us!"
Description: "Please rate our support for this ticket (1-5 stars)."
Stars:
One: "⭐ 1 Star"
Two: "⭐⭐ 2 Stars"
Three: "⭐⭐⭐ 3 Stars"
Four: "⭐⭐⭐⭐ 4 Stars"
Five: "⭐⭐⭐⭐⭐ 5 Stars"
Comment:
Title: "Write a comment."
Description: "Write a comment about your experience."
ReviewEmbed:
Title: "Ticket Review"
CommentTitle: "Comment"
RatingTitle: "Rating"
ReviewedStaffTitle: "Reviewed Staff:"
ReviewedByTitle: "Reviewed By:"
ReviewEmoji: "⭐"
Color: "#f7a943"
# ----------------------------
# Profile Messages
# ----------------------------
TicketStats:
OpenTickets: "✅ Open Tickets"
TotalTickets: "🎫 Total Tickets"
ResponseTime: "⏱️ Average Response Time"
Title: "Your Ticket Stats"
# ----------------------------
# Mod Command Messages
# ----------------------------
Mod:
TicketBlacklist:
Add:
Description: "Add a user to the ticket blacklist"
UserDescription: "User to blacklist"
AlreadyBlacklisted: ":x: {userTag} is already blacklisted."
Success: ":white_check_mark: {userTag} has been added to the blacklist. They can no longer use the ticket system."
Remove:
Description: "Remove a user from the ticket blacklist"
UserDescription: "User to remove from blacklist"
NotBlacklisted: ":x: {userTag} is not blacklisted."
Success: ":white_check_mark: {userTag} has been removed from the blacklist. They can now use the ticket system again."
View:
Description: "View the list of all blacklisted users"
NoBlacklistedUsers: ":information_source: There are currently no users on the blacklist."
# ----------------------------
# Logging Messages
# ----------------------------
TicketLog:
Title: "Ticket Log"
Colour: "#0cc559"
TicketBlacklistLog:
Title: "Ticket Blacklist Log"
Colour: "#0cc559"
# ----------------------------
# Misc Messages
# ----------------------------
AddUser:
NotInTicket_Title: "Ticket Invitation"
NotInTicket_Description: "This command can only be used within a ticket thread."
Invite_Title: "User Invited"
Invite_Description: "%username% has been invited to join the ticket."
DM_Title: "Ticket Invitation"
DM_Description: "You have been invited to join the ticket %channelname%. Do you accept?"
Accept_Label: "Accept"
Reject_Label: "Reject"
ViewTicket_Label: "Go to Ticket"
Accepted_Title: "Invitation Accepted"
Accepted_Message: "You have been added to the ticket."
AddedToTicket_Title: "Added to Ticket"
AddedToTicket_Description: "You have been added to the ticket %channel_link%."
# Added Message for inside the ticket, this informs everyone inside the ticket that the user has been added.
Added_Title: "User Added"
Added_Description: "Successfully added **%username%** to the ticket."
Declined_Title: "Invitation Declined"
Declined_Message: "You have declined the invitation."
NoResponse_Title: "No Response"
NoResponse_Message: "You did not respond in time. Please contact staff if you wish to join the ticket."
Error_Title: "Error"
Error_Adding: "There was an error adding you to the ticket. Please try again."
DM_Error_Title: "Error"
DM_Error_Description: "Unable to send a DM to %username%. Please ensure your DMs are enabled."
Unexpected_Error_Title: "Error"
Unexpected_Error_Description: "An unexpected error occurred: %error%"
RemoveUser:
NotInTicket_Title: "Error"
NotInTicket_Description: "This command can only be used in ticket threads."
Removed_Title: "User Removed"
Removed_Message: "The user has been successfully removed from the ticket."
RemovedFromTicket_Title: "Removed from Ticket"
RemovedFromTicket_Description: "You have been removed from the ticket channel %channel_link%."
Error_Title: "Error"
Error_Removing: "There was an error removing the user from the ticket."
ForceAddUser:
NotInTicket_Title: "Not a Ticket"
NotInTicket_Description: "This command can only be used within a ticket thread."
Added_Title: "User Added"
Added_Description: "Successfully added %username% to the ticket."
AddedToTicket_Title: "You have been added to a Ticket"
AddedToTicket_Description: "You have been added to the ticket channel [%channel_link%]."
Error_Title: "Error"
Error_Adding: "There was an error adding the user to the ticket."
Error:
IncorrectPermissions: ":x: **Error!** You do not have the correct permissions to use this command."
MissingChannel: ":x: **Error!** Required channel not found. Please ensure it is set up correctly!"
UserNotFound: ":x: **Error!** Specified user not found. Please check if they are in this server."
CommandNotAllowedHere: ":warning: This command cannot be used in this channel."
EntryAlreadyExists: ":warning: Cannot complete the action, as the entry already exists."
EntryDoesNotExist: ":warning: Cannot complete the action, as the entry does not exist."
ActionFailed: ":x: **Error!** An error occurred while processing your request. Please try again later."
+39
View File
@@ -0,0 +1,39 @@
# -------------------------
# SupportBot AI
#
# In order to use the SupportBot AI, You need to setup an OpenAI Account.
# this can be done at https://platform.openai.com/
# -------------------------
Enabled: true
General:
Name: "SupportBot AI"
# What would you like to call your AI
Model: "gpt-4o-mini"
# You can see a list of OpenAI Models here: https://platform.openai.com/docs/models
Tokens: 400
# Max tokens you can set is 400
# For more information about tokens, go to https://openai.com/api/pricing/
OpenAI_Key: "OPEN_AI_API_KEY"
# To create an OpenAI API key, go to https://platform.openai.com/api-keys
PastebinAPI_Key: "PASTEBIN_API_KEY"
# You can get your Pastebin API Key from https://pastebin.com
PastebinAPI_URL: "https://pastebin.com/api/api_post.php"
Channels:
AIChannel: "AI_BOT_CHANNEL"
# This is the channel where AI will respond in without needing to mention the bot.
Embed:
Color: 0x0cc559 # 0x<hexcode>
Footer: "SupportBot | Developed by Emerald Development"
Messages:
MessageTooLong: "The response was too long, so I've posted it here: {pastebin}"
ErrorResponse: "I didn't receive a proper response from OpenAI. Please try again later."
OpenAIError: "I'm having some trouble talking to SupportBot AI's Model, OpenAI. Please try again later."
AIDisabled: "I am disabled, You have enable me in `supportbot-ai.yml`"
+229 -127
View File
@@ -7,190 +7,292 @@
# SupportBot created by Emerald Services
# Installed with MIT License
#
# Discord Support: https://emeraldsrv.dev/discord
# Community Resources: https://emeraldsrv.dev/resources
# Discord Support: https://discord.gg/emeraldsrv
# Official Documentation: https://docs.emeraldsrv.dev
# Addons Marketplace: https://community.emeraldsrv.dev
#
# SupportBot Configuration (supportbot.yml)
SupportBot_Version: "7.4.0"
SupportBot_Version: "v8.4-DEV-2"
# -------------------------
# General Configuration
# General
# -------------------------
Name: "SupportBot"
Token: "BOT_TOKEN"
# Guild ID Ex: 123456789123456789
Guild: ""
General:
Name: "SupportBot"
Token: ""
Addons:
Enabled: true
# -------------------------
# Activity Configuration
# Activity
# -------------------------
BotActivity: "Need Help? /ticket"
Activity:
Status: "SupportBot v8"
Type: "Competing"
StreamingURL: "https://www.twitch.tv/YOUR_TWITCH_NAME"
StreamingURL: "https://www.twitch.tv/YOUR_TWITCH_NAME"
# Set The ActivityType to "STREAMING" for a streaming status!
# Available Types:
# Playing
# Watching
# Listening
# Competing
# Streaming (URL required)
ActivityType: "PLAYING"
# Activity status can either be PLAYING, WATCHING, LISTENING or STREAMING.
# You can also update the activity via Discord using: /settings
# -------------------------
# Embed Configuration
# Embeds
# -------------------------
EmbedColour: "#f74343"
EmbedFooter: "SupportBot | An Advanced Ticket System"
Embed:
Colours:
General: 0x0cc559
Success: 0x04b545
Error: 0xd13030
Warn: 0xfca117
ErrorColour: "#d12519"
SuccessColour: "#04b545"
WarningColour: "#fca117"
Footer: "SupportBot | Developed by Emerald Development"
# Use 0x<hexcode> to change the colour of the embeds.
# -------------------------
# Information Configuration
# Roles
# -------------------------
InfoTitle: "Information"
AboutMe: "Hi, I'm SupportBot an advanced discord ticket bot with 100% customization! [Download](https://emeraldsrv.dev/download)"
Roles:
AutoRole:
Enabled: true
Role: "1202669457751801917"
InfoButtonText: "Visit Site"
InfoURL: "https://emeraldsrv.dev"
InfoColour: "#f74343"
StaffMember:
Staff: "996420644373811371"
Admin: "996419960442208326"
Moderator: "996420644373811371"
# -------------------------
# Help Configuration
# -------------------------
Mod:
AllowSupportStaff: false
SendHelpPage: "channel" # Set to channel or dm
# -------------------------
# Role Configuration
# -------------------------
AutoRole: true
AutoRole_Role: "Member"
Staff: "Staff"
Admin: "Admin"
TicketBlackListRole: "Blacklisted"
# Allow Support Staff to use the /mod command. (More in detail customisation in commands.yml)
# ----------------------------
# Suggestions Configuration
# System Messages
# ----------------------------
SuggestionChannel: "suggestions"
SuggestionUpvote: "✅"
SuggestionDownvote: "❌"
Welcome:
Enabled: true
Channel: "1295788797065166869"
Leave:
Enabled: true
Channel: "1295788797065166869"
# ----------------------------
# Suggestions
# ----------------------------
Suggestions:
Mode: "Channel" # Channel or Forum
ForumChannel: "1402655496477081671"
Channel: "1295788900094316706"
UpvoteEmoji: "✅"
DownvoteEmoji: "❌"
UpvoteTitle: "Yes"
DownvoteTitle: "No"
OwnSuggestion: true
# If false this will not allow the user to vote for a suggestion they created
# If true this will allow the user to vote for a suggestion they created
ShowUsers: true
Threads:
Enabled: true
Title: "Suggestion-releated Thread"
Reason: "A thread releted to this suggestion"
Buttons:
Upvote: "2"
Downvote: "2"
RemoveVote: "4"
RemoveVote_Title: "Remove Vote"
# Available Button Styles
# 1 - Blurple
# 2 - Grey
# 3 - Green
# 4 - Red
Tags:
Created: "1418937323118137435"
Considering: "1418937237156139130"
Solved: "1418937168029679697"
Denied: "1418937185469464647"
# -------------------------
# Tickets Configuration
# Tickets
# -------------------------
TicketCategory: "Tickets"
TicketPrefix: "ticket-" # The prefix for ticket channel names
VoiceTickets:
Name: "%username%'s VC"
Category: "1419062819688550460"
TicketLog: "logs"
TranscriptLog: "transcripts"
Ticket:
TicketType: "threads"
TranscriptTitle: "Ticket Transcript"
TicketLog_Title: "Ticket Closed"
TicketBlackListMessage: "You are not allowed to open a ticket"
# Available TicketTypes:
# channels - Tickets will be created as channels in the TicketChannelsCategory
# threads - Tickets will be created as threads through the "TicketHome" channel.
MaxAllowedTickets: 2
TicketHome: "1295784943204700171"
TicketSubject: "description"
# "description" will displays the ticket reason within the channel description
# "embed" will displays the ticket reason within the ticket embed
# What is TicketHome?
# - Channel where the Ticket creation panel is sent.
# - Channel where ticket threads are made. (only if ticket threads is enabled above.)
AllowTicketMentions: true
# Set to true to allow the ticket to mention the author and the support team.
# Set to false to dis-allow mentions within tickets.
# For TicketType: channels
TicketChannelsCategory: "1418988408193155112"
TicketChannelsCategory2: "1418988456167870615" # This will be used if the first category has reached the max channel limit.
TicketArchiveCategory: "1418988434600759348"
# ---
# --- Creation Config ---
# --- Start ---
Channel: "ticket-" # The prefix for ticket channel names
TicketsPerUser: 100
DMTranscripts: true
TicketReason: true
# If ticket reason is set to false, It will not prompt to create a reason
# Available Placeholders:
TicketSubject: "description"
# %ticketusername% - User who created the ticket.
# <@%ticketauthor%> - Tags the user who created the ticket.
# <#%ticketid%> - Mentions the ticket channel.
# Available TicketSubjects:
# "description" will display the ticket reason within the channel description
# "embed" will display the ticket reason within the ticket embed
Ticket_Title: "Server Support | %ticketusername%"
TicketMessage: "Hello <@%ticketauthor%>, Thank you for creating a support ticket\nPlease wait patiently whilst a member of our team reaches out to you."
Timeout: 5
# Set to true to allow all staff to access the ticket
Ticket_Thumbnail: "https://images.emojiterra.com/google/android-pie/512px/1f39f.png"
NoTicketSubject: "No Reason Provided!"
Close:
StaffOnly: true
# Set to false is anyone is able to close a ticket.
TicketCreatedAlert: ":ticket: Your support ticket has been created: <#%ticketid%>"
Confirmation_Button: "Confirm Close"
Confirmation_Emoji: "🗑️"
Confirmation_Style: "2"
AddedUser: "👍 <@%user%> has been added to this ticket"
RemovedUser: "👍 <@%user%> has been removed from this ticket"
Title: "Are you sure you want to close this ticket?"
# --- Buttons Config ---
# --- Start ---
# Available Button Styles:
# 1 - Blurple
# 2 - Grey
# 3 - Green
# 4 - Red
# Discord Button Colour Selection
# PRIMARY - Blue
# SECONDARY - Grey
# DANGER - Red
# SUCCESS - Green
Invites:
StaffOnly: true
LockTicketCategory: "Archive"
ClaimTickets:
Enabled: true
Channel: "1295789389619793961"
# Channel where the ticket assignments are sent for staff to claim.
ClaimAcceptTime: 120000
# How long the assigned user has to accept the ticket before it gets re-assigned to someone else.
# 120000 = 2 Minutes
# 180000 = 3 Minutes
# 240000 = 4 Minutes
# 300000 = 5 Minutes
# Time in Mliseconds
TicketCloseEmoji: "❌"
TicketDeleteEmoji: "⛔"
TicketLockEmoji: "🔒"
ButtonTitle: "Claim Ticket"
ButtonEmoji: "🎫"
Button: "1"
TicketCloseColour: "DANGER"
TicketDeleteColour: "DANGER"
TicketLockColour: "PRIMARY"
# Available Button Styles
# 1 - Blurple
# 2 - Grey
# 3 - Green
# 4 - Red
TicketDept1Colour: "PRIMARY"
TicketDept2Colour: "PRIMARY"
TicketDept3Colour: "PRIMARY"
ReviewSystem:
Enabled: false
Channel: "1304480728884445225"
UseModal: true
TicketDept1Emoji: "1️⃣"
TicketDept2Emoji: "2️⃣"
TicketDept3Emoji: "3️⃣"
Questions:
Enabled: true # Set to false to disable ticket questions
List:
- "Please provide your account email?"
- "What is the nature of your issue?"
- "Have you tried resolving the issue yourself?"
TranscriptSavedMessage: "📝 **Transcript Saved!**"
TicketLockMessage: "❌ **Ticket Locked!**"
TicketDeleteMessage: "⚠ **Ticket Deleting!**" # Tickets deleting after 5 seconds by default.
Log:
TicketDataLog: "1339271338761650290"
TickeDataTitle: "Ticket Data"
TicketBlacklistLog: "1339271338761650290"
# --- Departments Config ---
# --- Start ---
# -------------------------
# Buttons
# -------------------------
Buttons:
General:
Delete: "🗑️"
DeleteStyle: "4"
TicketDepartments: true
# Delete the ticket if no department selected. TIME IN MINUTES
Timeout: 5
AllowStaff: true
# If set to false users with the "Staff" role will not be able to view tickets
# without a valid department role set below.
Voice:
TicketDeleteText: "Delete Voice Channel"
# Set to false to allow "Staff" to see tickets whether its a department for them or not.
Tickets:
ClaimEmoji: "🧑‍💻"
ClaimStyle: "1"
DepartmentTitle_1: "General Support"
DepartmentTitle_2: "Billing Support"
DepartmentTitle_3: "Admin Team"
# Ticket department title, This is the title for a ticket department to help users know what is available.
Unarchive: "Unarchive Ticket"
Unarchive_Emoji: "♻️"
Unarchive_Style: "2"
DepartmentRole_1: "Support"
DepartmentRole_2: "Support"
DepartmentRole_3: "Admin"
# Ticket roles are the roles that will be able to gain access to tickets depending on what department has been chosen.
Unlock: "Unlock Ticket"
Unlock_Emoji: "🔓"
Unlock_Style: "2"
# --- Closing Config ---
# --- Start ---
# Available Button Styles
# 1 - Blurple
# 2 - Grey
# 3 - Green
# 4 - Red
StaffOnly: true
ClosingTicket: ":warning: You have requested to close your support ticket."
ClosingConfirmation_Word: "confirm"
CloseConfirmation: false
# -------------------------
# Select Menus
# -------------------------
# ---------------------------
# Error / Warning Messages
# ---------------------------
SelectMenus:
Tickets:
PanelEmoji: "⚙️"
CloseEmoji: "🗑️"
ArchiveEmoji: "♻️"
LockEmoji: "🔐"
RenameEmoji: "🏷️"
VCEmoji: "🔊"
IncorrectPerms: ":x: **Err!** You do not have the correct permissions to use this command."
InvalidChannel: ":x: **Err!** Channel not found. Please create it!"
UserNotFound: ":x: **Err!** This user doesn't exist, Are they in this server?"
NoValidTicket: ":warning: You cannot execute this command here."
TicketExists: ":warning: This ticket cannot be opened, It already exists!"
EnableInvitesEmoji: "📤"
DisableInvitesEmoji: "📥"
# -------------------------
# Logging
# -------------------------
MessageDelete:
Channel: "1202671982932402246"
Colour: "#d92525"
MessageUpdate:
Channel: "1202671982932402246"
Colour: "#f2a024"
Translate:
TranslateLog: "1202671982932402246"
+24 -17
View File
@@ -7,35 +7,42 @@
# SupportBot created by Emerald Services
# Installed with MIT License
#
# Discord Support: https://emeraldsrv.dev/discord
# Community Resources: https://emeraldsrv.dev/resources
# Discord Support: https://discord.gg/emeraldsrv
# Official Documentation: https://docs.emeraldsrv.com
# Addons Marketplace: https://community.emeraldsrv.com
#
# Ticket Creation Panel Configuration (ticket-panel.yml)
# -------------------------------
# Ticket Panel Configuration
# -------------------------------
TicketPanel: true
Panel: true
LayoutStyle: modern
# ----------------------------------------
# Ticket Panel Message Configuration
# Ticket Panel Message Configuration #2b2d31
# ----------------------------------------
Channel: "create-ticket"
ButtonEmoji: "📩"
ButtonLabel: "Open Ticket"
ButtonColour: "SUCCESS"
# Colour Options:
# -> PRIMARY - Blurple
# -> SECONDARY - Grey
# -> SUCCESS - Green
# -> DANGER - Red
PanelTitle: "SupportBot"
PanelColour: "#0cc559"
PanelTitle: "Need Help? Create a Support Ticket."
Button:
Text: "Create Ticket"
Emoji: "🎫"
Color: "2"
# Available Button Styles
# 1 - Blurple
# 2 - Grey
# 3 - Red
# 4 - Green
TicketPanel_Description: true
TicketPanel_Thumbnail: false
TicketPanel_Image: false
TicketPanel_Image: true
PanelMessage: "Click the button to open a new support ticket."
PanelMessage: "Click on the button to file a `Ticket` and our team will try their best to assist you in the best way we can!"
PanelThumbnail: "https://i.imgur.com/ieeZejl.png"
PanelImage: "https://i.imgur.com/5NkcmDq.png"
PanelImage: "https://i.imgur.com/dejQvkg.png"
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
{
"blacklistedUsers": []
}
+1
View File
@@ -0,0 +1 @@
{}
-4
View File
@@ -1,4 +0,0 @@
{
"id": 0,
"tickets": []
}
-1
View File
@@ -1 +0,0 @@
{"TicketPanelID":""}
+1 -1
View File
@@ -1,5 +1,5 @@
const Event = require("../Structures/Event.js");
module.exports = new Event("guildCreate", async (client, guild) => {
module.exports = new Event("guildCreate", async (client) => {
console.log(
`\u001b[31m`,
`${client.user.username} is not in the correct server set in your config. Please leave all other servers and restart the bot.`
+1 -1
View File
@@ -1,6 +1,6 @@
const Event = require("../Structures/Event.js");
module.exports = new Event("guildDelete", async (client, guild) => {
module.exports = new Event("guildDelete", async (client) => {
console.log(
`\u001b[31m`,
`${client.user.username} is not in the correct server set in your config. Please join the server and restart the bot.`
+151
View File
@@ -0,0 +1,151 @@
const {
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
MessageFlags,
ContainerBuilder,
TextDisplayBuilder,
SectionBuilder,
ThumbnailBuilder,
AttachmentBuilder
} = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const Event = require("../Structures/Event.js");
const supportbot = yaml.load(
fs.readFileSync("./Configs/supportbot.yml", "utf8")
);
const msgconfig = yaml.load(
fs.readFileSync("./Configs/messages.yml", "utf8")
);
function getWelcomeThumbnail(client, member, iconCfg) {
switch ((iconCfg?.Mode || "").toUpperCase()) {
case "USER":
return member.user.displayAvatarURL();
case "BOT":
return client.user.displayAvatarURL();
case "CUSTOM":
return iconCfg.CustomURL;
default:
return null;
}
}
function buildModernComponents(client, member) {
const container = new ContainerBuilder().setAccentColor(
parseInt(msgconfig.Welcome.Modern.Colour.replace("#", ""), 16)
);
const section = new SectionBuilder().addTextDisplayComponents(
new TextDisplayBuilder().setContent(
msgconfig.Welcome.Modern.Title.replace(/%joined_user%/g, member.user)
),
new TextDisplayBuilder().setContent(
msgconfig.Welcome.Modern.Body.replace(/%joined_user%/g, member.user)
)
);
const thumbUrl = getWelcomeThumbnail(client, member, msgconfig.Welcome.Modern.Icon);
if (thumbUrl) {
section.setThumbnailAccessory(
new ThumbnailBuilder()
.setURL(thumbUrl)
);
}
container.addSectionComponents(section);
if (msgconfig.Welcome.Modern.Image?.Enabled && msgconfig.Welcome.Modern.Image.URL) {
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
`![image](${msgconfig.Welcome.Modern.Image.URL})`
)
);
}
if (Array.isArray(msgconfig.Welcome.Modern.Buttons)) {
const row = new ActionRowBuilder();
msgconfig.Welcome.Modern.Buttons.slice(0, 5).forEach(b => {
const btn = new ButtonBuilder()
.setLabel(b.label)
.setStyle(ButtonStyle[b.style])
.setEmoji(b.emoji || null);
if (b.style === "Link") btn.setURL(b.url);
else if (b.customId) btn.setCustomId(b.customId);
row.addComponents(btn);
});
container.addActionRowComponents(row);
}
return [container];
}
module.exports = new Event("guildMemberAdd", async (client, member) => {
if (!supportbot.Welcome.Enabled) return;
const WelcomeChannel =
member.guild.channels.cache.get(supportbot.Welcome.Channel) ||
member.guild.channels.cache.find(c => c.name === supportbot.Welcome.Channel);
if (!WelcomeChannel) return;
const style = (msgconfig.Welcome.LayoutStyle || "classic").toLowerCase();
if (style === "classic") {
const WelcomeEmbed = new EmbedBuilder()
.setColor(msgconfig.Welcome.Embed.Colour)
.setTitle(msgconfig.Welcome.Embed.Title || null)
.setDescription(
msgconfig.Welcome.Embed.Message.replace(/%joined_user%/g, member.user)
)
.setTimestamp();
const thumb = getWelcomeThumbnail(client, member, msgconfig.Welcome.Embed.Icon);
if (thumb) WelcomeEmbed.setThumbnail(thumb);
if (msgconfig.Welcome.Embed.Image?.Enabled && msgconfig.Welcome.Embed.Image.URL) {
WelcomeEmbed.setImage(msgconfig.Welcome.Embed.Image.URL);
}
const components = [];
if (
msgconfig.Welcome.Embed.Buttons?.Enabled &&
Array.isArray(msgconfig.Welcome.Embed.Buttons.List)
) {
const row = new ActionRowBuilder();
msgconfig.Welcome.Embed.Buttons.List.slice(0, 5).forEach(b => {
const btn = new ButtonBuilder()
.setLabel(b.label)
.setStyle(ButtonStyle[b.style])
.setEmoji(b.emoji || null);
if (b.style === "Link") btn.setURL(b.url);
else if (b.customId) btn.setCustomId(b.customId);
row.addComponents(btn);
});
components.push(row);
}
await WelcomeChannel.send({ embeds: [WelcomeEmbed], components });
} else if (style === "modern") {
const components = buildModernComponents(client, member);
if (components.length > 0) {
await WelcomeChannel.send({
components,
flags: MessageFlags.IsComponentsV2
});
}
}
if (supportbot.Roles.AutoRole.Enabled) {
const role =
member.guild.roles.cache.get(supportbot.Roles.AutoRole.Role) ||
member.guild.roles.cache.find(r => r.name === supportbot.Roles.AutoRole.Role);
if (role) await member.roles.add(role);
}
console.log("\u001b[32m", "[+]", "\u001b[33m", `${member.user.username} joined the server!`);
});
+40
View File
@@ -0,0 +1,40 @@
const { EmbedBuilder } = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const Event = require("../Structures/Event.js");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
module.exports = new Event("guildMemberRemove", async (client, member) => {
if (!supportbot.Leave || !supportbot.Leave.Enabled) return;
const leaveChannel =
member.guild.channels.cache.get(supportbot.Leave.Channel) ||
member.guild.channels.cache.find(c => c.name === supportbot.Leave.Channel);
if (!leaveChannel) return;
const leaveEmbed = new EmbedBuilder()
.setColor(msgconfig.Leave.Embed.Colour)
.setTitle(msgconfig.Leave.Embed.Title)
.setDescription(
msgconfig.Leave.Embed.Message.replace(/%left_user%/g, member.user.toString())
)
.setTimestamp();
if (msgconfig.Leave.Embed.Thumbnail === "BOT") {
leaveEmbed.setThumbnail(client.user.displayAvatarURL());
} else if (msgconfig.Leave.Embed.Thumbnail === "USER") {
leaveEmbed.setThumbnail(member.user.displayAvatarURL());
}
if (msgconfig.Leave.Embed.ImageEnabled) {
leaveEmbed.setImage(msgconfig.Leave.Embed.ImageURL);
}
await leaveChannel.send({ embeds: [leaveEmbed] });
console.log(`[-] ${member.user.tag} left the server`);
});
+696 -89
View File
@@ -1,126 +1,733 @@
// SupportBot | Emerald Services
// Interaction Create Event
const fs = require("fs");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(
fs.readFileSync("./Configs/supportbot.yml", "utf8")
);
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Event = require("../Structures/Event.js");
const db = require("../Structures/Database.js");
module.exports = new Event("interactionCreate", async (client, interaction) => {
if (interaction.isCommand()) {
if (interaction.type === Discord.InteractionType.ApplicationCommand) {
const command = client.commands.find(
(cmd) => cmd.name.toLowerCase() == interaction.commandName
(cmd) => cmd.name.toLowerCase() === interaction.commandName
);
if (interaction.user.bot || !interaction.guild) return;
const NotValid = new Discord.MessageEmbed()
.setDescription(`:x: \`Invalid Command\` `)
.setColor(supportbot.ErrorColour);
const NotValid = new Discord.EmbedBuilder()
.setDescription(`:x: \`Invalid Command\``)
.setColor(supportbot.Embed.Colours.Error);
if (!command)
return interaction.reply({
embeds: [NotValid],
flags: Discord.MessageFlags.Ephemeral,
});
const permission = interaction.member.permissions.has(command.permission);
const permission = interaction.member.permissions.has(command.permissions);
const ValidPerms = new Discord.MessageEmbed()
const ValidPerms = new Discord.EmbedBuilder()
.setDescription(
":x: `Invalid Permissions` Do you have the correct permissions to execute this command?"
"> :x: `Invalid Permissions` Do you have the correct permissions to execute this command?"
)
.setColor(supportbot.ErrorColour);
.setColor(supportbot.Embed.Colours.Error);
if (!permission)
return interaction.reply({
embeds: [ValidPerms],
flags: Discord.MessageFlags.Ephemeral,
});
interaction.user = interaction.user;
command.run(interaction);
}
if (interaction.isButton()) {
if (interaction.customId === "openticket") {
try {
const cmd = client.commands.get(cmdconfig.OpenTicket);
if (!cmd) return;
cmd.run(interaction);
} catch (error) {
console.error(error);
}
}
if (interaction.customId === "ticketclose") {
try {
const cmd = client.commands.get(cmdconfig.CloseTicket);
if (!cmd) return;
cmd.run(interaction);
} catch (error) {
console.error(error);
try {
if (command.name === cmdconfig.OpenTicket.Command && supportbot.Ticket.TicketReason) {
const modal = new Discord.ModalBuilder()
.setCustomId("ticketReasonModal")
.setTitle("Ticket Reason")
.addComponents(
new Discord.ActionRowBuilder().addComponents(
new Discord.TextInputBuilder()
.setCustomId("reasonInput")
.setLabel("Reason")
.setStyle(Discord.TextInputStyle.Paragraph)
.setRequired(true)
)
);
await interaction.showModal(modal);
} else {
console.log("\u001b[32m", "[Executed]", "\u001b[37;1m", `${interaction.user.username} has executed ${command.name}`);
await command.run(interaction);
}
} catch (error) {
console.error("Error executing command:", error);
await interaction.reply({
content: "An error occurred while executing the command.",
flags: Discord.MessageFlags.Ephemeral,
});
}
}
if (interaction.isModalSubmit()) {
if (interaction.customId === "ticketReasonModal") {
const reason = interaction.fields.getTextInputValue("reasonInput");
interaction.customId = `createticket-${reason}`;
const cmd = client.commands.get(cmdconfig.OpenTicket.Command);
if (cmd) {
interaction.reason = reason;
await cmd.run(interaction);
} else {
console.error("Command not found.");
}
}
if (interaction.customId === "ticketlock") {
if (interaction.channel.name.startsWith(`${supportbot.TicketPrefix}`)) {
interaction.message.fetch();
let Admin =
interaction.guild.roles.cache.find(
(AdminUser) => AdminUser.name === supportbot.Admin
) ||
interaction.guild.roles.cache.find(
(AdminUser) => AdminUser.id === supportbot.Admin
);
if (!interaction.member.roles.cache.has(Admin.id)) {
return await interaction.reply({
content: "You don't have permission to do that.",
ephemeral: true,
}
if (interaction.customId === "renameTicketModal") {
const newTicketName = interaction.fields.getTextInputValue("renameTicket");
const ticketChannelId = interaction.channel.id;
let ticketChannel;
if (supportbot.Ticket.TicketType === "threads") {
ticketChannel = interaction.guild.channels.cache.get(ticketChannelId);
} else if (supportbot.Ticket.TicketType === "channels") {
ticketChannel = interaction.guild.channels.cache.get(ticketChannelId);
}
if (!ticketChannel) {
return interaction.reply({
content: "Ticket channel not found.",
flags: Discord.MessageFlags.Ephemeral,
});
}
try {
await ticketChannel.setName(newTicketName);
const renamedsuccess = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.RenamedSuccess)
.setColor(supportbot.Embed.Colours.Success);
return interaction.reply({
embeds: [renamedsuccess],
flags: Discord.MessageFlags.Ephemeral,
});
} catch (error) {
console.error("Error renaming the ticket:", error);
return interaction.reply({
content: "There was an error renaming the ticket.",
flags: Discord.MessageFlags.Ephemeral,
});
}
}
if (interaction.isStringSelectMenu()) {
if (interaction.customId === "ticketcontrolpanel") {
const selectedOption = interaction.values[0];
const { getRole, getChannel } = interaction.client;
switch (selectedOption) {
case "ticketclose":
const closeCommand = client.commands.get(cmdconfig.CloseTicket.Command);
if (closeCommand) {
await closeCommand.run(interaction);
} else {
console.error("[X] Close Command Not Found");
}
break;
case "supportvc":
const ticketInfo = db.getTicket(interaction.channel.id);
if (!ticketInfo) {
return interaction.reply({
content: "⚠️ This channel isn’t a valid ticket.",
flags: Discord.MessageFlags.Ephemeral
});
}
const user = interaction.guild.members.cache.get(ticketInfo.user_id) || ticketInfo.user_id;
const username = user?.user?.username || "Unknown User";
const VCcategory = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.VoiceTickets.Category || c.id === supportbot.VoiceTickets.Category
);
if (!VCcategory) {
return interaction.reply({
content: "⚠️ The Support VC category does not exist!",
flags: Discord.MessageFlags.Ephemeral,
});
}
const SupportVoice = await interaction.guild.channels.create({
name: supportbot.VoiceTickets.Name.replace(/%username%/g, username),
type: Discord.ChannelType.GuildVoice,
parent: VCcategory,
permissionOverwrites: [
{
id: interaction.guild.id,
deny: [Discord.PermissionsBitField.Flags.Connect]
},
{
id: user.id || user, // works if user is a Member or just ID
allow: [
Discord.PermissionsBitField.Flags.Connect,
Discord.PermissionsBitField.Flags.Speak,
Discord.PermissionsBitField.Flags.ViewChannel,
],
},
],
});
db.updateTicketVoice(ticketInfo.ticket_id, SupportVoice.id);
const vcmadeembed = new Discord.EmbedBuilder()
.setDescription(`> **Support VC Created:** <#${SupportVoice.id}>`)
.setColor(supportbot.Embed.Colours.Success);
const VCDeleteButton = new Discord.ButtonBuilder()
.setCustomId("deleteSupportVC")
.setLabel(supportbot.Buttons.Voice.TicketDeleteText)
.setEmoji(supportbot.Buttons.General.Delete)
.setStyle(supportbot.Buttons.General.DeleteStyle);
const VCDeleteRow = new Discord.ActionRowBuilder().addComponents(VCDeleteButton);
await interaction.reply({
embeds: [vcmadeembed],
components: [VCDeleteRow],
});
break;
case "archiveticket":
const arcembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketArchived)
.setColor(supportbot.Embed.Colours.Success);
const CloseButton = new Discord.ButtonBuilder()
.setCustomId("confirmCloseTicket")
.setLabel(supportbot.Ticket.Close.Confirmation_Button)
.setEmoji(supportbot.Ticket.Close.Confirmation_Emoji)
.setStyle(supportbot.Ticket.Close.Confirmation_Style);
const unarchiveButton = new Discord.ButtonBuilder()
.setCustomId("unarchiveTicket")
.setLabel(supportbot.Buttons.Tickets.Unarchive)
.setEmoji(supportbot.Buttons.Tickets.Unarchive_Emoji)
.setStyle(supportbot.Buttons.Tickets.Unarchive_Style);
const ArchivedOptions = new Discord.ActionRowBuilder()
.addComponents(CloseButton, unarchiveButton);
await interaction.channel.send({
embeds: [arcembed],
components: [ArchivedOptions],
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setArchived(true);
}
if (supportbot.Ticket.TicketType === "channels") {
const { getRole, getChannel } = interaction.client;
await interaction.channel.setParent(supportbot.Ticket.TicketArchiveCategory);
const tickets = db.getAllTickets();
const ticketInfo = tickets.find(t => t.ticket_id === interaction.channel.id);
if (ticketInfo) {
const user = interaction.guild.members.cache.get(ticketInfo.user_id) || ticketInfo.user_id;
const claimedBy = interaction.guild.members.cache.get(ticketInfo.claimed_by) || ticketInfo.claimed_by;
const Admin = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Admin);
const Staff = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Staff);
if (user && user.id) {
await interaction.channel.permissionOverwrites.edit(user.id, {
ViewChannel: false,
SendMessages: false,
});
}
if (supportbot.ClaimTickets === true) {
if (claimedBy && claimedBy.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
}
if (supportbot.ClaimTickets === false) {
if (Staff && Staff.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
}
if (Admin && Admin.id) {
await interaction.channel.permissionOverwrites.edit(Admin.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
await interaction.channel.permissionOverwrites.edit(interaction.guild.roles.everyone.id, {
ViewChannel: false,
SendMessages: false,
ReadMessageHistory: false,
});
}
}
break;
case "lockticket":
const lockembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketLocked)
.setColor(supportbot.Embed.Colours.Success);
const unlockButton = new Discord.ButtonBuilder()
.setCustomId("unlockTicket")
.setLabel(supportbot.Buttons.Tickets.Unlock)
.setEmoji(supportbot.Buttons.Tickets.Unlock_Emoji)
.setStyle(supportbot.Buttons.Tickets.Unlock_Style);
const LockedOptions = new Discord.ActionRowBuilder()
.addComponents(unlockButton);
await interaction.channel.send({
embeds: [lockembed],
components: [LockedOptions],
ephemeral: false,
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setLocked(true);
}
break;
case "unlockTicket":
const unlockEmbed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketUnlocked)
.setColor(supportbot.Embed.Colours.Success);
await interaction.channel.send({
embeds: [unlockEmbed],
ephemeral: false,
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setLocked(false);
}
break;
case "enableinvites":
await handleInvites(interaction, true);
break;
case "disableinvites":
await handleInvites(interaction, false);
break;
case "renameticket":
const modal = new Discord.ModalBuilder()
.setCustomId("renameTicketModal")
.setTitle("Rename Channel")
.addComponents(
new Discord.ActionRowBuilder()
.addComponents(
new Discord.TextInputBuilder()
.setCustomId("renameTicket")
.setLabel("New Channel Name")
.setStyle(Discord.TextInputStyle.Short)
.setRequired(true)
)
);
await interaction.showModal(modal);
break;
}
}
}
if (interaction.customId === 'deleteSupportVC') {
const ticketData = db.getAllTickets();
const ticketInfo = ticketData.find(t => t.ticket_id === interaction.channel.id);
if (ticketInfo && ticketInfo.voiceChannelId) {
try {
const voiceChannel = await interaction.guild.channels.fetch(ticketInfo.voiceChannelId).catch(() => null);
if (voiceChannel) {
await voiceChannel.delete();
const successEmbed = new Discord.EmbedBuilder()
.setDescription('✅ Support voice channel has been successfully deleted!')
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [successEmbed],
flags: Discord.MessageFlags.Ephemeral,
});
} else {
const errorEmbed = new Discord.EmbedBuilder()
.setDescription('❌ The associated voice channel could not be found.')
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
flags: Discord.MessageFlags.Ephemeral,
});
}
let all = interaction.channel.permissionOverwrites.cache;
let parent =
interaction.guild.channels.cache.find(
(c) => c.name === `${supportbot.LockTicketCategory}`
) ||
interaction.guild.channels.cache.find(
(c) => c.id === `${supportbot.LockTicketCategory}`
);
if (parent) {
interaction.channel.setParent(parent.id);
}
all.forEach((perm) => {
interaction.channel.permissionOverwrites.delete(perm.id);
} catch (error) {
console.error('Error deleting the Voice Channel:', error);
const errorEmbed = new Discord.EmbedBuilder()
.setDescription('❌ **There was an error trying to delete this voice channel. Please try again later.**')
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
flags: Discord.MessageFlags.Ephemeral,
});
interaction.channel.permissionOverwrites.create(Admin.id, {
VIEW_CHANNEL: true,
});
interaction.channel.permissionOverwrites.create(interaction.guild.id, {
VIEW_CHANNEL: false,
});
const ticketDeleteButton = new Discord.MessageButton()
.setCustomId("ticketclose")
.setLabel("Close")
.setStyle(supportbot.TicketDeleteColour)
.setEmoji(supportbot.TicketDeleteEmoji);
const row = new Discord.MessageActionRow().addComponents(
ticketDeleteButton
);
const ArchiveEmbed = new Discord.MessageEmbed()
.setTitle("Archived")
.setDescription(`Archived ${interaction.channel.name}`)
.setColor(`${supportbot.SuccessColour}`);
interaction.reply({ embeds: [ArchiveEmbed] });
interaction.message.edit({ components: [row] });
}
} else {
const errorEmbed = new Discord.EmbedBuilder()
.setDescription('⚠️ This button is not linked to a valid voice channel.')
.setColor(supportbot.Embed.Colours.Warn);
await interaction.reply({
embeds: [errorEmbed],
flags: Discord.MessageFlags.Ephemeral,
});
}
}
if (interaction.isButton()) {
if (interaction.customId === "unarchiveTicket") {
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setArchived(false);
}
if (supportbot.Ticket.TicketType === "channels") {
const { getRole, getChannel } = interaction.client;
await interaction.channel.setParent(supportbot.Ticket.TicketChannelsCategory);
const ticketData = db.getAllTickets();
const ticketInfo = ticketData.find(t => t.ticket_id === interaction.channel.id);
if (ticketInfo) {
const user = interaction.guild.members.cache.get(ticketInfo.user_id) || ticketInfo.user_id;
const claimedBy = interaction.guild.members.cache.get(ticketInfo.claimed_by) || ticketInfo.claimed_by;
const Admin = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Admin);
const Staff = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Staff);
if (user && user.id) {
await interaction.channel.permissionOverwrites.edit(user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
if (supportbot.ClaimTickets === true) {
if (claimedBy && claimedBy.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
}
if (supportbot.ClaimTickets === false) {
if (Staff && Staff.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
}
if (Admin && Admin.id) {
await interaction.channel.permissionOverwrites.edit(Admin.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
await interaction.channel.permissionOverwrites.edit(interaction.guild.roles.everyone.id, {
ViewChannel: false,
SendMessages: false,
ReadMessageHistory: false,
});
}
}
const unrcembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketUnarchived)
.setColor(supportbot.Embed.Colours.Success);
await interaction.update({
embeds: [unrcembed],
components: [],
});
}
if (interaction.customId === "createticket") {
if (supportbot.Ticket.TicketReason) {
const modal = new Discord.ModalBuilder()
.setCustomId("ticketReasonModal")
.setTitle("Ticket Reason")
.addComponents(
new Discord.ActionRowBuilder().addComponents(
new Discord.TextInputBuilder()
.setCustomId("reasonInput")
.setLabel("Reason")
.setStyle(Discord.TextInputStyle.Paragraph)
.setRequired(true)
)
);
await interaction.showModal(modal);
} else {
try {
const cmd = client.commands.get(cmdconfig.OpenTicket.Command);
if (!cmd) return;
await cmd.run(interaction);
} catch (error) {
console.error("Error executing the ticket command:", error);
await interaction.reply({
content: "An error occurred while creating the ticket.",
flags: Discord.MessageFlags.Ephemeral,
});
}
}
}
if (interaction.customId === "openticket") {
if (supportbot.Ticket.TicketReason) {
const modal = new Discord.ModalBuilder()
.setCustomId("ticketReasonModal")
.setTitle("Ticket Reason")
.addComponents(
new Discord.ActionRowBuilder().addComponents(
new Discord.TextInputBuilder()
.setCustomId("reasonInput")
.setLabel("Reason")
.setStyle(Discord.TextInputStyle.Paragraph)
.setRequired(true)
)
);
await interaction.showModal(modal);
} else {
try {
const cmd = client.commands.get(cmdconfig.OpenTicket.Command);
if (!cmd) return;
await cmd.run(interaction);
} catch (error) {
console.error(error);
}
}
}
if (interaction.customId.startsWith("claimticket-")) {
const ticketChannelId = interaction.customId.split("-")[1];
const ticketChannel = interaction.guild.channels.cache.get(ticketChannelId);
if (!ticketChannel) {
return interaction.reply({
content: "Ticket channel not found.",
flags: Discord.MessageFlags.Ephemeral,
});
}
const { getRole } = interaction.client;
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
const RolePerms = new Discord.EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn);
if (!Staff || !Admin) {
return interaction.reply({ embeds: [RolePerms], ephemeral: true });
}
const NoPermsClaim = new Discord.EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.NoPermsToClaimTitle)
.setDescription(
msgconfig.Ticket.ClaimTickets.NoPermsToClaim.replace("%channel%", ticketChannel.name)
)
.setColor(supportbot.Embed.Colours.Error);
if (!interaction.member.roles.cache.has(Staff.id) && !interaction.member.roles.cache.has(Admin.id)) {
return interaction.reply({ embeds: [NoPermsClaim], ephemeral: true });
}
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(interaction.user.id);
} else {
await ticketChannel.permissionOverwrites.create(interaction.user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(Admin, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(interaction.guild.roles.everyone, {
ViewChannel: false,
});
}
const pingclaimedstaff = await ticketChannel.send(`<@${interaction.user.id}>`);
setTimeout(() => pingclaimedstaff.delete(), 2000);
const ticketInfo = db.getTicket(ticketChannelId);
if (ticketInfo) {
db.updateTicketStatus(ticketChannelId, ticketInfo.status);
ticketInfo.claimed_by = interaction.user.id;
}
const claimMessage = await interaction.message.fetch();
const claimedEmbed = new Discord.EmbedBuilder(claimMessage.embeds[0].data)
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimedTitle)
.setDescription(
msgconfig.Ticket.ClaimTickets.ClaimMessage_Edit
.replace("%user%", `${interaction.user.id}`)
.replace("%channel%", `${ticketChannel.id}`)
)
.setColor(supportbot.Embed.Colours.Success);
const successfullyClaimed = new Discord.EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimedTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.Claimed
.replace("%channel%", `${ticketChannel.id}`)
)
.setColor(supportbot.Embed.Colours.Success);
await claimMessage.edit({ embeds: [claimedEmbed], components: [] });
await interaction.reply({ embeds: [successfullyClaimed], ephemeral: true });
}
// SUGGESTION INTERACTIONS [START]
if (
interaction.customId === "upvote" ||
interaction.customId === "downvote" ||
interaction.customId === "removevote"
) {
const suggestion = suggestions[interaction.message.id];
if (!suggestion)
return interaction.reply({ content: "Suggestion not found.", ephemeral: true });
const userId = interaction.user.id;
const hasUpvoted = suggestion.upvotes.includes(userId);
const hasDownvoted = suggestion.downvotes.includes(userId);
if (interaction.customId === "upvote") {
if (hasUpvoted) {
suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId);
} else {
suggestion.upvotes.push(userId);
if (hasDownvoted) {
suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId);
}
}
} else if (interaction.customId === "downvote") {
if (hasDownvoted) {
suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId);
} else {
suggestion.downvotes.push(userId);
if (hasUpvoted) {
suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId);
}
}
} else if (interaction.customId === "removevote") {
suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId);
suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId);
}
fs.writeFileSync("./Data/SuggestionData.json", JSON.stringify(suggestions, null, 2));
const upvoteCount = suggestion.upvotes.length;
const downvoteCount = suggestion.downvotes.length;
const updatedEmbed = new Discord.EmbedBuilder(interaction.message.embeds[0].data).setFields([
{ name: "Suggestion", value: suggestion.suggestion, inline: true },
{ name: "From", value: `<@${suggestion.author}>` },
{
name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`,
value: `${upvoteCount}`,
inline: true,
},
{
name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`,
value: `${downvoteCount}`,
inline: true,
},
]);
await interaction.update({ embeds: [updatedEmbed] });
}
}
async function handleInvites(interaction, enable) {
if (supportbot.Ticket.Invites.StaffOnly) {
const { getRole, getChannel } = interaction.client;
const SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) {
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
}
const NoPerms = new Discord.EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn);
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
) {
return interaction.reply({ embeds: [NoPerms] });
}
}
await interaction.channel.setInvitable(enable);
const message = enable ? msgconfig.Ticket.EnableInvites : msgconfig.Ticket.DisableInvites;
const embed = new Discord.EmbedBuilder().setDescription(message).setColor(supportbot.Embed.Colours.General);
await interaction.reply({
embeds: [embed],
flags: Discord.MessageFlags.Ephemeral,
});
}
if (interaction.type === Discord.InteractionType.ModalSubmit && interaction.customId === "editProfileModal") {
const userId = interaction.user.id;
const profilePath = `./Data/Profiles/${userId}.json`;
let profileData;
if (fs.existsSync(profilePath)) {
profileData = JSON.parse(fs.readFileSync(profilePath, "utf8"));
} else {
profileData = {
bio: "",
timezone: "",
clockedIn: false,
};
}
try {
const newBio = interaction.fields.getTextInputValue("bio");
const newTimezone = interaction.fields.getTextInputValue("timezone");
profileData.bio = newBio;
profileData.timezone = newTimezone;
fs.writeFileSync(profilePath, JSON.stringify(profileData, null, 2));
const updatedProfileEmbed = new Discord.EmbedBuilder()
.setTitle(`${interaction.user.username}'s Profile`)
.setColor(supportbot.Embed.Colours.General)
.setThumbnail(interaction.user.displayAvatarURL())
.addFields(
{ name: "Bio", value: newBio || "No bio set.", inline: false },
{ name: "Timezone", value: newTimezone || "No timezone set.", inline: true }
);
if (profileData.clockedIn !== undefined) {
updatedProfileEmbed.addFields({
name: "Clocked In Status",
value: profileData.clockedIn ? "✅ Clocked In" : "❌ Clocked Out",
inline: true,
});
}
await interaction.reply({ embeds: [updatedProfileEmbed], ephemeral: true });
} catch (error) {
console.error("Failed to update profile data: ", error);
await interaction.reply({
content: "There was an error while updating your profile. Please try again later.",
flags: Discord.MessageFlags.Ephemeral,
});
}
}
});
+166
View File
@@ -0,0 +1,166 @@
const fs = require("fs");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const axios = require("axios");
const { OpenAI } = require('openai');
// Load configuration files with validation
function loadConfig(path) {
try {
return yaml.load(fs.readFileSync(path, "utf8"));
} catch (error) {
console.error(`Error loading config file at ${path}:`, error);
throw error;
}
}
const supportbotai = yaml.load(fs.readFileSync("./Configs/supportbot-ai.yml"));
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
// Validate configuration values
function validateConfig(config, keys) {
keys.forEach(key => {
if (!config[key]) {
throw new Error(`Missing configuration key: ${key}`);
}
});
}
const CHANNELS = [supportbotai.Channels.AIChannel];
// Initialize Discord client
const client = new Discord.Client({
intents: ['Guilds', 'GuildMembers', 'GuildMessages']
});
// Load custom Event structure
const Event = require("../Structures/Event.js");
// Initialize OpenAI client
const openai = new OpenAI({
apiKey: supportbotai.General.OpenAI_Key,
});
const PASTEBIN_API_KEY = supportbotai.General.PastebinAPI_Key;
const PASTEBIN_API_URL = supportbotai.General.PastebinAPI_URL;
// Function to create a paste on Pastebin
async function createPaste(content) {
try {
const response = await axios.post(PASTEBIN_API_URL, new URLSearchParams({
api_dev_key: PASTEBIN_API_KEY,
api_option: 'paste',
api_paste_code: content,
api_paste_private: 1, // 0 = public, 1 = unlisted, 2 = private
api_paste_expire_date: '1D', // Expire in 1 day
api_paste_format: 'text'
}));
return response.data;
} catch (error) {
console.error('Pastebin Error:', error);
throw error;
}
}
// Event handler for message creation
module.exports = new Event("messageCreate", async (client, message) => {
if (message.author.bot || !message.guild) return;
if (!CHANNELS.includes(message.channelId) && !message.mentions.users.has(client.user.id)) return;
if (supportbotai.Enabled) {
const sendTypingInterval = setInterval(() => {
message.channel.sendTyping();
}, 5000);
let conversation = [{
role: "system",
content: "SupportBot is your friendly neighbourhood support bot."
}];
let prevMessages = await message.channel.messages.fetch({ limit: 10 });
prevMessages = prevMessages.reverse().filter(msg => !msg.author.bot || msg.author.id === client.user.id);
prevMessages.forEach((msg) => {
const username = msg.author.username.replace(/\s+/g, '_').replace(/[^\w\s]/gi, '');
conversation.push({
role: msg.author.id === client.user.id ? "assistant" : "user",
name: username,
content: msg.content,
});
});
try {
const response = await openai.chat.completions.create({
model: supportbotai.General.Model,
messages: conversation,
max_tokens: supportbotai.General.Tokens,
});
clearInterval(sendTypingInterval);
if (response.choices && response.choices.length > 0) {
const replyContent = response.choices[0].message.content;
const SupportBotAI = new Discord.EmbedBuilder()
.setAuthor({ name: supportbotai.General.Name, iconURL: client.user.displayAvatarURL() })
.setDescription(replyContent)
.setColor(supportbotai.Embed.Color)
.setFooter({ text: supportbotai.Embed.Footer, iconURL: message.author.displayAvatarURL() })
.setTimestamp();
if (replyContent.length > 2000) {
const pasteLink = await createPaste(replyContent);
const SupportBotAIPastebin = new Discord.EmbedBuilder()
.setAuthor({ name: supportbotai.General.Name, iconURL: client.user.displayAvatarURL() })
.setDescription(supportbotai.Messages.MessageTooLong.replace("{pastebin}", pasteLink))
.setColor(supportbotai.Embed.Color)
.setFooter({ text: supportbotai.Embed.Footer, iconURL: message.author.displayAvatarURL() })
.setTimestamp();
message.reply({ embeds: [SupportBotAIPastebin] });
} else {
message.reply({ embeds: [SupportBotAI] });
}
} else {
const SupportBotAIErrorResponse = new Discord.EmbedBuilder()
.setAuthor({ name: supportbotai.General.Name, iconURL: client.user.displayAvatarURL() })
.setDescription(supportbotai.Messages.ErrorResponse)
.setColor(supportbotai.Embed.Color)
.setFooter({ text: supportbotai.Embed.Footer, iconURL: message.author.displayAvatarURL() })
.setTimestamp();
message.reply({ embeds: [SupportBotAIErrorResponse], ephemeral: true });
}
} catch (error) {
clearInterval(sendTypingInterval);
const SupportBotAIErrorResponse = new Discord.EmbedBuilder()
.setAuthor({ name: supportbotai.General.Name, iconURL: client.user.displayAvatarURL() })
.setDescription(supportbotai.Messages.OpenAIError)
.setColor(supportbot.Embed.Colours.Error)
.setFooter({ text: supportbotai.Embed.Footer, iconURL: message.author.displayAvatarURL() })
.setTimestamp();
console.error('OpenAI Error:', error);
message.reply({ embeds: [SupportBotAIErrorResponse], ephemeral: true });
}
}
if (supportbotai.Enabled === false) {
const AIDisabled = new Discord.EmbedBuilder()
.setAuthor({ name: supportbotai.General.Name, iconURL: client.user.displayAvatarURL() })
.setDescription(supportbotai.Messages.AIDisabled)
.setColor(supportbot.Embed.Colours.Error)
.setFooter({ text: supportbotai.Embed.Footer, iconURL: message.author.displayAvatarURL() })
.setTimestamp();
message.reply({ embeds: [AIDisabled], ephemeral: true })
return;
}
});
+36
View File
@@ -0,0 +1,36 @@
const { EmbedBuilder } = require('discord.js');
const fs = require("fs");
const yaml = require("js-yaml");
const Event = require("../Structures/Event.js");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
module.exports = new Event("messageDelete", async (client, message) => {
if (!message.guild) return;
const author = message.author || (message.partial ? await message.fetch().then(m => m.author).catch(() => null) : null);
if (!author || author.bot) return;
const content = message.content || (message.attachments.size > 0 ? "Contains attachment(s)" : "*No content*");
const deleteLogChannel = message.guild.channels.cache.get(supportbot.MessageDelete.Channel) ||
message.guild.channels.cache.find(c => c.name === supportbot.MessageDelete.Channel);
const deletedMessageEmbed = new EmbedBuilder()
.setColor(supportbot.MessageDelete.Colour)
.setTitle("Message Deleted")
.setDescription(`> **Channel:** <#${message.channel.id}>\n> **Message ID:** ${message.id}\n> **Message author:** ${author.tag} (${author.id})\n> **Message Created:** <t:${Math.floor(message.createdTimestamp / 1000)}:F>`)
.addFields({ name: "Deleted Content", value: content, inline: false })
.setThumbnail(author.displayAvatarURL())
.setTimestamp();
if (message.attachments.size > 0) {
const attachment = message.attachments.first();
deletedMessageEmbed.addFields({ name: "Attachment", value: `[${attachment.name}](${attachment.proxyURL})` });
}
if (deleteLogChannel) {
deleteLogChannel.send({ embeds: [deletedMessageEmbed] });
}
});
+57
View File
@@ -0,0 +1,57 @@
const { EmbedBuilder } = require('discord.js');
const fs = require("fs");
const yaml = require("js-yaml");
const Event = require("../Structures/Event.js");
// Load the configuration files
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
module.exports = new Event("messageUpdate", async (client, oldMessage, newMessage) => {
// Check if the message is from a guild and not from a bot
if (!newMessage.guild || newMessage.author.bot) return;
// Fetch old message if it's not cached (important for message content)
if (!oldMessage.content && oldMessage.partial) {
try {
oldMessage = await oldMessage.fetch();
} catch (err) {
console.error('Error fetching old message:', err);
return;
}
}
// Fallback if content is missing
const oldMessageContent = oldMessage.content || (oldMessage.attachments.size > 0 ? 'Contains attachment(s)' : '*No content*');
const newMessageContent = newMessage.content || (newMessage.attachments.size > 0 ? 'Contains attachment(s)' : '*No content*');
// Get the update log channel either by ID or by name
const updateLogChannel = newMessage.guild.channels.cache.get(supportbot.MessageUpdate.Channel) ||
newMessage.guild.channels.cache.find(channel => channel.name === supportbot.MessageUpdate.Channel);
// Create the embed for the updated message
const updatedMessageEmbed = new EmbedBuilder()
.setColor(supportbot.MessageUpdate.Colour)
.setTitle("Message Updated")
.setDescription(`> **Channel:** <#${newMessage.channel.id}>\n> **Message ID:** [${newMessage.id}](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${newMessage.id})\n> **Message author:** ${newMessage.author.tag} (${newMessage.author.id})\n> **Message Created:** <t:${Math.floor(oldMessage.createdTimestamp / 1000)}:F>`)
.addFields(
{ name: 'Old Message', value: oldMessageContent, inline: false },
{ name: 'New Message', value: newMessageContent, inline: false }
)
.setThumbnail(newMessage.author.displayAvatarURL())
.setTimestamp();
// If the new message contains an attachment, add it to the embed
if (newMessage.attachments.size > 0) {
const attachment = newMessage.attachments.first();
updatedMessageEmbed.addFields({ name: 'Attachment', value: `[${attachment.name}](${attachment.proxyURL})` });
}
// Send the embed to the update log channel if the channel is found
if (updateLogChannel) {
updateLogChannel.send({
embeds: [updatedMessageEmbed]
});
}
});
+119 -193
View File
@@ -1,202 +1,128 @@
// SupportBot | Emerald Services
// Ready Event
const fs = require("fs");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(
fs.readFileSync("./Configs/supportbot.yml", "utf8")
);
const panelconfig = yaml.load(
fs.readFileSync("./Configs/ticket-panel.yml", "utf8")
);
const Event = require("../Structures/Event.js");
const gradient = require("gradient-string");
const figlet = require("figlet");
module.exports = new Event("ready", async (client) => {
const { getRole, getChannel, getCategory } = client;
client.user.setActivity(supportbot.BotActivity, {
type: supportbot.ActivityType,
url: supportbot.StreamingURL,
});
// Load configs
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const panel = yaml.load(fs.readFileSync("./Configs/ticket-panel.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
console.log(`\u001b[32m`, `┏━━━┓╋╋╋╋╋╋╋╋╋╋╋╋╋┏┓┏━━┓╋╋╋┏┓`);
console.log(`\u001b[32m`, `┃┏━┓┃╋╋╋╋╋╋╋╋╋╋╋╋┏┛┗┫┏┓┃╋╋┏┛┗┓`);
console.log(`\u001b[32m`, `┃┗━━┳┓┏┳━━┳━━┳━━┳┻┓┏┫┗┛┗┳━┻┓┏┛`);
console.log(`\u001b[32m`, `┗━━┓┃┃┃┃┏┓┃┏┓┃┏┓┃┏┫┃┃┏━┓┃┏┓┃┃`);
console.log(`\u001b[32m`, `┃┗━┛┃┗┛┃┗┛┃┗┛┃┗┛┃┃┃┗┫┗━┛┃┗┛┃┗┓`);
console.log(`\u001b[32m`, `┗━━━┻━━┫┏━┫┏━┻━━┻┛┗━┻━━━┻━━┻━┛`);
console.log(`\u001b[32m`, `┗╋╋╋╋╋╋╋┃┃╋┃┃`);
console.log(`\u001b[32m`, `╋╋╋╋╋╋╋┗┛╋┗┛`);
console.log(` `);
console.log(`\u001b[31m`, `――――――――――――――――――――――――――――――――――――――――――――`);
console.log(` `);
console.log(
`\u001b[33m`,
`${supportbot.Name} v${supportbot.SupportBot_Version}`,
`\u001b[36m`,
`Connected to Discord`
);
console.log(
`\u001b[33m`,
`Resources:`,
`\u001b[36m`,
`https://emeraldsrv.dev/resources`
);
console.log(
`\u001b[33m`,
`Documentation:`,
`\u001b[36m`,
`https://supportbot.emeraldsrv.dev`
);
console.log(
`\u001b[33m`,
`Discord:`,
`\u001b[36m`,
`https://emeraldsrv.dev/discord`
);
console.log(
`\u001b[33m`,
`Hosting:`,
`\u001b[36m`,
`https://emeraldsrv.dev/hosting`
);
console.log(` `);
console.log(`\u001b[37m`, `SupportBot proudly created by Emerald Services`);
console.log(` `);
console.log(`\u001b[31m`, `――――――――――――――――――――――――――――――――――――――――――――`);
if (
!supportbot.Guild ||
(await client.guilds.cache.size) !== 1 ||
(await client.guilds.cache.first().id) !== supportbot.Guild
) {
console.log(
`\u001b[31m`,
`${client.user.username} is not in the correct server set in your config. Please join the server and restart the bot.`
);
console.log(`\u001b[31m`, `${client.user.username} will now exit.`);
return process.exit(1);
}
const roles = [
supportbot.Admin,
supportbot.Staff,
supportbot.TicketBlackListRole,
supportbot.DepartmentRole_1,
supportbot.DepartmentRole_2,
supportbot.DepartmentRole_3,
];
if (supportbot.AutoRole) roles.push(supportbot.AutoRole_Role);
const channels = [
supportbot.SuggestionChannel,
supportbot.TicketLog,
supportbot.TranscriptLog,
panelconfig.Channel,
];
const categories = [supportbot.TicketCategory];
const ACTIVITY_TYPES = {
PLAYING: Discord.ActivityType.Playing,
WATCHING: Discord.ActivityType.Watching,
LISTENING: Discord.ActivityType.Listening,
COMPETING: Discord.ActivityType.Competing
};
const missingC = [];
const missingR = [];
const missingCat = [];
for (let c of channels) {
const find = await getChannel(c, client.guilds.cache.first());
if (!find) missingC.push(c);
}
for (let r of roles) {
const find = await getRole(r, client.guilds.cache.first());
if (!find) missingR.push(r);
}
for (let cat of categories) {
const find = await getCategory(cat, client.guilds.cache.first());
if (!find) missingCat.push(cat);
}
if (missingR.length > 0) {
console.log(
"\u001b[33m",
`[WARN] The following Roles could not be found! Please check your configs!\n${missingR.join(
", "
)}`
);
}
if (missingC.length > 0) {
console.log(
"\u001b[33m",
`[WARN] The following Channels could not be found! Please check your configs!\n${missingC.join(
", "
)}`
);
}
if (missingCat.length > 0) {
console.log(
"\u001b[33m",
`[WARN] The following Categories could not be found! Please check your configs!\n${missingCat.join(
", "
)}`
);
}
if (panelconfig.TicketPanel) {
let chan1 = client.channels.cache.find(
(channel) => channel.name === panelconfig.Channel
);
if (!chan1) {
console.log(
"\u001b[33m",
"[WARN] Ticket reaction panel is not setup, You can do so via the configuration file!"
);
return false;
}
const panelid = JSON.parse(
fs.readFileSync("./Data/ticket-panel-id.json", "utf8")
);
chan1.messages.fetch(panelid.TicketPanelID).catch(async (r) => {
let embed = new Discord.MessageEmbed()
.setTitle(`${panelconfig.PanelTitle}`)
.setColor(supportbot.SuccessColour)
.setFooter(supportbot.EmbedFooter);
if (panelconfig.TicketPanel_Description) {
embed.setDescription(panelconfig.PanelMessage);
}
if (panelconfig.TicketPanel_Thumbnail) {
embed.setThumbnail(panelconfig.PanelThumbnail);
}
if (panelconfig.TicketPanel_Image) {
embed.setImage(panelconfig.PanelImage);
}
let button = new Discord.MessageButton()
.setLabel(panelconfig.ButtonLabel)
.setCustomId("openticket")
.setStyle(panelconfig.ButtonColour)
.setEmoji(panelconfig.ButtonEmoji);
let row = new Discord.MessageActionRow().addComponents(button);
await chan1
.send({
embeds: [embed],
components: [row],
})
.then((r) => {
let data = {
TicketPanelID: `${r.id}`,
};
fs.writeFileSync(
"./Data/ticket-panel-id.json",
JSON.stringify(data),
"utf8"
);
})
.catch((e) => {
console.log("Raw" + e);
});
const setActivity = (client, type, status) => {
const activityType = ACTIVITY_TYPES[type?.toUpperCase()];
if (activityType) {
client.user.setPresence({
activities: [{ name: status || "Online", type: activityType }],
status: type?.toLowerCase() || "online"
});
}
});
};
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const logStartup = async (client) => {
console.clear();
// Animated loading
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
for (let i = 0; i < frames.length * 2; i++) {
process.stdout.write(`\r${frames[i % frames.length]} Initializing SupportBot...`);
await sleep(100);
}
console.clear();
// Main title
console.log("\n" + gradient.pastel.multiline(figlet.textSync("SupportBot", { font: "ANSI Shadow", horizontalLayout: "fitted" })));
// Extract bot details safely
const botName = supportbot?.General?.Name || "SupportBot";
const botVersion = supportbot.SupportBot_Version;
console.log("\n╔═══════════════════════════════════════════════════════════════╗");
console.log("║ System Information ║");
console.log("╠═══════════════════════════════════════════════════════════════╣");
console.log(`║ Bot Name : ${botName.padEnd(45)} ║`);
console.log(`║ Version : ${botVersion.padEnd(45)} ║`);
console.log(`║ Node.js : ${process.version.padEnd(45)} ║`);
console.log(`║ Discord.js : v${Discord.version.padEnd(44)} ║`);
console.log("╚═══════════════════════════════════════════════════════════════╝\n");
// Links section
console.log(gradient.summer("┏━━━━━━━━━━━━━━━━━━━ Important Links ━━━━━━━━━━━━━━━━━━━┓"));
console.log(gradient.summer("┃ ┃"));
console.log(gradient.summer("┃ ") + "📚 Docs : " + gradient.cristal("https://emerald-services.gitbook.io/ ┃"));
console.log(gradient.summer("┃ ") + "🤝 Discord : " + gradient.cristal("https://dsc.gg/emerald-dev ┃"));
console.log(gradient.summer("┃ ┃"));
console.log(gradient.summer("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n"));
// Bot Invite
const inviteUrl = `https://discord.com/api/oauth2/authorize?client_id=${client.user.id}&permissions=8&scope=bot%20applications.commands`;
console.log(gradient.teen("╔════════════════════════ Bot Invite ════════════════════════╗"));
console.log(gradient.teen("║ ║"));
console.log(gradient.teen("║ ") + gradient.pastel(inviteUrl.padEnd(56)) + gradient.teen("║"));
console.log(gradient.teen("║ ║"));
console.log(gradient.teen("╚════════════════════════════════════════════════════════════╝\n"));
};
const checkConfigs = async (client) => {
const guild = client.guilds.cache.first();
if (!guild) return;
console.log(gradient.morning("┏━━━━━━━━━━━━━━━━━━━━ Config Check ━━━━━━━━━━━━━━━━━━━━━┓"));
const roles = [
supportbot?.Roles?.StaffMember?.Admin,
supportbot?.Roles?.StaffMember?.Staff,
supportbot?.Roles?.AutoRole?.Role
].filter(Boolean);
const channels = [
supportbot?.Suggestions?.Channel,
supportbot?.Ticket?.Log?.TicketLog,
supportbot?.Ticket?.Log?.TranscriptLog,
supportbot?.Ticket?.TicketHome,
supportbot?.Welcome?.Channel,
supportbot?.Leave?.Channel,
supportbot?.Translate?.Log
].filter(Boolean);
const [missingRoles, missingChannels] = await Promise.all([
Promise.all(roles.map((role) => client.getRole(role, guild))),
Promise.all(channels.map((channel) => client.getChannel(channel, guild)))
]);
const missing = {
roles: roles.filter((_, i) => !missingRoles[i]),
channels: channels.filter((_, i) => !missingChannels[i])
};
if (missing.roles.length || missing.channels.length) {
console.log(gradient.morning("┃ ⚠️ - Configuration Issues Found: ┃"));
if (missing.roles.length) {
console.log(gradient.morning("┃ • Missing Roles: ") + gradient.fruit(missing.roles.join(", ")));
}
if (missing.channels.length) {
console.log(gradient.morning("┃ • Missing Channels: ") + gradient.fruit(missing.channels.join(", ")));
}
} else {
console.log(gradient.morning("┃ ✅ - All configurations validated successfully ┃"));
}
console.log(gradient.morning("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n"));
};
module.exports = new Event("clientReady", async (client) => {
setActivity(client, supportbot?.Activity?.Type, supportbot?.Activity?.Status);
await logStartup(client);
await checkConfigs(client);
});
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Emerald Services
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+92 -9
View File
@@ -1,13 +1,96 @@
![imgur](https://i.imgur.com/TQj40Mt.png)
<h1 align="center">
<br>
<a href="https://github.com/Emerald-Services/SupportBot"><img src="https://i.imgur.com/cuadAYv.png"></a>
<br>
SupportBot
<br>
</h1>
## What is SupportBot?
<div align="center">
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/license-MIT-green?style=for-the-badge" alt="shield.png">
</a>
<a href="https://github.com/discordjs">
<img src="https://img.shields.io/badge/DISCORD.JS%20-%20v14-blue?style=for-the-badge" alt="shield.png">
</a>
<a href="https://github.com/nodejs/node">
<img src="https://img.shields.io/badge/NODE.JS%20-%20v20.x-green?style=for-the-badge" alt="shield.png">
</a>
<a href="https://github.com/Emerald-Services/SupportBot/releases">
<img src="https://img.shields.io/github/downloads/Emerald-Services/SupportBot/total?style=for-the-badge" alt="shield.png">
</a>
## Supported Versions
| SupportBot | Node.JS | Discord.JS | Supported |
|------------|---------|------------|------------|
| 8.x | 20.x | 14.x. | ✅ Yes
| 7.7.3 | 16.x | 13.x. | ✅ Yes
| 7.x | 16.x | 13.x. | ❌ No
| 6.x | 14.x | 13.x. | ❌ No
| 5.x | 12.x | 13.x. | ❌ No
| 4.x | 11.x | 13.x. | ❌ No
| Older | 10.x | 13.x. | ❌ No
</div>
<p align="center">
<a href="https://emeraldsrv.com/">Website</a>
•
<a href="https://github.com/Emerald-Services/SupportBot/wiki">Wiki</a>
<br>
<a href="#about">About</a>
•
<a href="#support">Support</a>
•
<a href="#license">License</a>
•
<a href="https://github.com/Emerald-Services/SupportBot/wiki/Third-Party-Developer">Third-Party</a>
•
<a href="#contributors">Contributors</a>
</p>
## About
SupportBot is an Open-Source discord bot built with [discord.js](https://github.com/discordjs/discord.js). It's primarily focused to help servers organise and manage their support system in a unique way with full customisation to match your brand.
## Support
<a href="https://emeraldsrv.com/discord">
<img alt="Discord" src="https://img.shields.io/discord/597779408295821323?logo=discord&logoColor=white&style=for-the-badge">
</a>
## License
Released under the [MIT](https://opensource.org/licenses/MIT) license.
## Third-Party
- [Official addons repository](https://github.com/Emerald-Services/Addons/)
- [Syphers Addons](https://github.com/SypherRed/SB_Addons_Unofficial)
- [BuiltByBit](https://builtbybit.com/supportbot)
- [Polymart](https://polymart.org/resource/supportbot-1-discord-ticket-bot.518)
## Contributors
<a href="https://github.com/Emerald-Services/SupportBot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Emerald-Services/SupportBot">
</a>
## Hosting
You can use our [Pterodactyl egg](https://github.com/Emerald-Services/Pterodactyl-Egg) to host it on your own panel. We currently don't offer hosting for you, altough we might add this in the future.
If you don't know how to install pterodactyl, you could always host it on replit:
[![Run on Repl.it](https://repl.it/badge/github/ItsNyoty/IKI-bot)]([https://repl.it/github/ItsNyoty/iki](https://repl.it/github/Emerald-Services/SupportBot
)
SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand.
## Quick Links
**|** [Discord](https://emeraldsrv.dev/discord) **|**
[Hosting](https://emeraldsrv.dev/hosting) **|**
[Documentation](https://docs.emeraldsrv.dev) **|**
[Download via Polymart](https://polymart.org/resource/supportbot-1-discord-ticket-bot.518) **|**
[Download via MC-Market](https://www.mc-market.org/resources/8940/) **|**
+20
View File
@@ -0,0 +1,20 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 7.7.2 | :white_check_mark: |
| 7.7.1 | :white_check_mark: |
| < 4.0 | :x: |
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc.
+7 -1
View File
@@ -1,6 +1,6 @@
// SupportBot | Emerald Services
// Addon Structure
class Command {
constructor(options) {
this.name = options.name;
@@ -10,7 +10,13 @@ class Command {
this.run = options.run;
this.defaultPermission = true;
}
// Method to dynamically update options
setOptions(options) {
this.options = options;
}
}
class Event {
constructor(event, run) {
this.event = event;
+158 -66
View File
@@ -1,115 +1,207 @@
// SupportBot | Emerald Services
// Client Structure
const fs = require("fs");
const path = require("path");
const Discord = require("discord.js");
const intents = new Discord.Intents(32767);
const { GatewayIntentBits, Partials } = require('discord.js');
const yaml = require("js-yaml");
const supportbot = yaml.load(
fs.readFileSync("./Configs/supportbot.yml", "utf8")
);
const { Command, Event } = require('./Addon.js');
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
function walk(dir, ext = ".js", fileList = []) {
const files = fs.readdirSync(dir);
for (const file of files) {
const full = `${dir}/${file}`;
const stat = fs.statSync(full);
if (stat.isDirectory()) {
walk(full, ext, fileList);
} else if (file.endsWith(ext)) {
fileList.push(full);
}
}
return fileList;
}
class Client extends Discord.Client {
constructor() {
super({ intents });
super({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
],
partials: [Partials.Message, Partials.Channel]
});
this.commands = new Discord.Collection();
}
async getChannel(channel, guild) {
if (!channel) return null;
return guild.channels.cache.find(
(c) =>
(c.type === "GUILD_TEXT" || c.type === "GUILD_NEWS") &&
(c.id === channel || c.name.toLowerCase() === channel.toLowerCase())
(c.type === Discord.ChannelType.GuildText || c.type === Discord.ChannelType.GuildNews) &&
(c.id === channel || (c.name && c.name.toLowerCase() === channel.toLowerCase()))
);
}
async getRole(role, guild) {
if (!role) return null;
return guild.roles.cache.find(
(r) => r.id === role || r.name.toLowerCase() === role.toLowerCase()
(r) => r.id === role || (r.name && r.name.toLowerCase() === role.toLowerCase())
);
}
async getCategory(category, guild) {
if (!category) return null;
return guild.channels.cache.find(
(c) =>
c.type === "GUILD_CATEGORY" &&
(c.id === category || c.name.toLowerCase() === category.toLowerCase())
c.type === Discord.ChannelType.GuildCategory &&
(c.id === category || (c.name && c.name.toLowerCase() === category.toLowerCase()))
);
}
start(token) {
const commandFiles = fs
.readdirSync("./Commands")
.filter((file) => file.endsWith(".js"));
async start(token) {
let tempCommandFiles = walk("./Commands");
const addonFiles = fs
.readdirSync("./Addons")
.filter((file) => file.endsWith(".js"));
if (cmdconfig.Suggestion.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "suggest.js");
}
const addons = addonFiles.map((file) => {
let addon = require(`../Addons/${file}`);
addon.name = file.split(".")[0];
return addon;
});
if (cmdconfig.Suggestion.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "suggestadmin.js");
}
const commands = commandFiles.map((file) => require(`../Commands/${file}`));
if (cmdconfig.Help.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "help.js");
}
// Commands
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬");
if (cmdconfig.Info.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "info.js");
}
if (cmdconfig.OpenTicket.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "ticket.js");
}
if (cmdconfig.CloseTicket.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "close.js");
}
if (cmdconfig.Embed.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "embed.js");
}
if (cmdconfig.Translate.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "translate.js");
}
if (cmdconfig.UserInfo.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "userinfo.js");
}
if (cmdconfig.Ping.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "Ping.js");
}
if (cmdconfig.AddUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "addUser.js");
}
if (cmdconfig.RemoveUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "removeUser.js");
}
if (cmdconfig.ForceAddUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "forceaddUser.js");
}
if (cmdconfig.Profile.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "profile.js");
}
if (cmdconfig.Settings.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "settings.js");
}
if (cmdconfig.Mod.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "mod.js");
}
const commandFiles = tempCommandFiles;
const commands = commandFiles.map((file) => require(`../${file}`));
console.log(`\u001b[33m`, "――――――――――――――――――――――――――――――――――――――――――――");
commands.forEach((cmd) => {
console.log(`\u001b[31;1m`, `${cmd.name}`, `\u001b[32;1m`, "Loaded");
console.log(`\u001b[32m`, `[CMD]`, `\u001b[37;1m`, `${cmd.name}`, `\u001b[32;1m`, "Loaded");
this.commands.set(cmd.name, cmd);
});
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬");
// Addons
console.log(" ");
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬");
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬");
addons.forEach((addon) => {
console.log(
`\u001b[33m`,
`[ADDON]`,
`\u001b[31;1m`,
`${addon.name}`,
`\u001b[32;1m`,
"Loaded"
);
addon.commands.forEach((command) => {
this.commands.set(command.name, command);
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;
});
addon.events.forEach((event) => {
this.on(event.event, (...args) => event.run(this, ...args));
console.log(" ");
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬");
addons.forEach((addon) => {
console.log(
`\u001b[32m`,
`[ADDON]`,
`\u001b[37;1m`,
`${addon.name}`,
`\u001b[32;1m`,
"Loaded"
);
if (addon instanceof Command) {
this.commands.set(addon.name, addon);
}
if (addon.events && Array.isArray(addon.events)) {
addon.events.forEach((event) => {
if (event instanceof Event) {
this.on(event.event, (...args) => event.run(this, ...args));
}
});
}
});
});
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬");
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬");
}
// Slash Commands
this.once("ready", async () => {
await this.application?.commands.set(this.commands, supportbot.Guild);
this.once("clientReady", async () => {
await this.guilds.cache.first()?.commands.set(this.commands);
console.log(
`\u001b[32;1m`,
"\u001b[32m",
`[SUCCESS]`,
`\u001b[37;1m`,
`Slash Commands Registered for ${this.guilds.cache.first().name}`
);
});
console.log(" ");
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬");
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬");
fs.readdirSync("./Events")
.filter((file) => file.endsWith(".js"))
.forEach((file) => {
const event = require(`../Events/${file}`);
console.log(`\u001b[31;1m`, `${event.event}`, `\u001b[32;1m`, "Loaded");
this.on(event.event, (...args) => event.run(this, ...args));
});
fs.readdirSync("./Events").filter((file) => file.endsWith(".js")).forEach((file) => {
const event = require(`../Events/${file}`);
console.log(`\u001b[32m`, `[EVENT]`, `\u001b[37;1m`, `${event.event}`, `\u001b[32;1m`, "Loaded");
this.on(event.event, (...args) => event.run(this, ...args));
});
console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬");
if (process.argv[2] != "test") {
this.login(token);
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬");
if (process.argv[2] !== "test") {
await this.login(token);
} else {
console.log(
`\u001b[31;1m`,
+1 -6
View File
@@ -1,19 +1,14 @@
// SupportBot | Emerald Services
// Command Structure
const fs = require("fs");
const yaml = require("js-yaml");
class Command {
constructor(options) {
this.name = options.name;
this.description = options.description;
this.permissions = options.permissions;
this.permissions = options.permissions || [];
this.options = options.options || [];
this.run = options.run;
this.defaultPermission = true;
}
}
module.exports = Command;
+221
View File
@@ -0,0 +1,221 @@
// SupportBot | Emerald Services
// Database Structure
const Database = require("better-sqlite3");
const path = require("path");
const db = new Database(path.join(__dirname, "../Data/supportbot.db"));
db.prepare(`
CREATE TABLE IF NOT EXISTS suggestions (
thread_id TEXT PRIMARY KEY,
author_id TEXT,
text TEXT,
status TEXT DEFAULT 'Created'
)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS profiles (
user_id TEXT PRIMARY KEY,
bio TEXT,
timezone TEXT,
clockedIn INTEGER DEFAULT 0
)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS tickets (
ticket_id TEXT PRIMARY KEY,
user_id TEXT,
status TEXT DEFAULT 'open',
created_at INTEGER,
updated_at INTEGER,
subject TEXT,
description TEXT
)
`).run();
try {
db.prepare("ALTER TABLE tickets ADD COLUMN voiceChannelId TEXT").run();
} catch (e) {
if (!String(e).includes("duplicate column name")) {
throw e;
}
}
db.prepare(`
CREATE TABLE IF NOT EXISTS ticket_panel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id TEXT,
channel_id TEXT,
created_at INTEGER
)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS ticket_users (
ticket_id TEXT,
user_id TEXT,
PRIMARY KEY(ticket_id, user_id)
)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY,
data TEXT
)
`).run();
module.exports = {
addSuggestion(threadId, authorId, text) {
db.prepare(`
INSERT INTO suggestions (thread_id, author_id, text)
VALUES (@threadId, @authorId, @text)
ON CONFLICT(thread_id) DO NOTHING
`).run({ threadId, authorId, text });
},
setSuggestionStatus(threadId, status) {
db.prepare(`
UPDATE suggestions SET status=@status WHERE thread_id=@threadId
`).run({ threadId, status });
},
getSuggestion(threadId) {
return db.prepare(`
SELECT * FROM suggestions WHERE thread_id=@threadId
`).get({ threadId });
},
// ----- Profiles -----
getProfile(userId) {
const row = db.prepare(`SELECT * FROM profiles WHERE user_id=?`).get(userId);
if (!row) {
db.prepare(`
INSERT INTO profiles (user_id, bio, timezone, clockedIn)
VALUES (?, '', '', 0)
`).run(userId);
return { user_id: userId, bio: "", timezone: "", clockedIn: 0 };
}
return row;
},
updateProfile(userId, data) {
const { bio = "", timezone = "", clockedIn = 0 } = data;
db.prepare(`
INSERT INTO profiles (user_id, bio, timezone, clockedIn)
VALUES (@userId, @bio, @timezone, @clockedIn)
ON CONFLICT(user_id) DO UPDATE SET
bio = excluded.bio,
timezone = excluded.timezone,
clockedIn = excluded.clockedIn
`).run({ userId, bio, timezone, clockedIn });
},
setClockedIn(userId, value) {
db.prepare(`UPDATE profiles SET clockedIn=? WHERE user_id=?`)
.run(value ? 1 : 0, userId);
},
addTicket(ticket) {
const now = Date.now();
db.prepare(`
INSERT INTO tickets (ticket_id, user_id, subject, description, created_at, updated_at)
VALUES (@ticket_id, @user_id, @subject, @description, @created_at, @updated_at)
ON CONFLICT(ticket_id) DO NOTHING
`).run({
ticket_id: ticket.id,
user_id: ticket.user,
subject: ticket.reason || "",
description: ticket.description || "",
created_at: now,
updated_at: now
});
},
updateTicketVoice(ticketId, voiceChannelId) {
db.prepare(`
UPDATE tickets SET voiceChannelId=@voiceChannelId, updated_at=@updatedAt WHERE ticket_id=@ticketId
`).run({ ticketId, voiceChannelId, updatedAt: Date.now() });
},
getTicketVoice(ticketId) {
return db.prepare(`SELECT voiceChannelId FROM tickets WHERE ticket_id=?`).get(ticketId);
},
deleteTicketVoice(ticketId) {
db.prepare(`
UPDATE tickets SET voiceChannelId=NULL, updated_at=@updatedAt WHERE ticket_id=@ticketId
`).run({ ticketId, updatedAt: Date.now() });
},
updateTicketStatus(ticketId, status) {
db.prepare(`
UPDATE tickets SET status=@status, updated_at=@updatedAt WHERE ticket_id=@ticketId
`).run({ status, updatedAt: Date.now(), ticketId });
},
getTicket(ticketId) {
return db.prepare(`
SELECT * FROM tickets WHERE ticket_id=@ticketId
`).get({ ticketId });
},
getAllTickets() {
return db.prepare(`SELECT * FROM tickets`).all();
},
saveTicketPanel(messageId, channelId) {
db.prepare(`
INSERT INTO ticket_panel (message_id, channel_id, created_at)
VALUES (@messageId, @channelId, @createdAt)
`).run({ messageId, channelId, createdAt: Date.now() });
},
getTicketPanel() {
return db.prepare(`
SELECT * FROM ticket_panel ORDER BY id DESC LIMIT 1
`).get();
},
addUserToTicket(ticketId, userId) {
db.prepare(`
INSERT INTO ticket_users (ticket_id, user_id)
VALUES (?, ?)
ON CONFLICT(ticket_id, user_id) DO NOTHING
`).run(ticketId, userId);
},
removeUserFromTicket(ticketId, userId) {
db.prepare(`
DELETE FROM ticket_users WHERE ticket_id=? AND user_id=?
`).run(ticketId, userId);
},
getTicketUsers(ticketId) {
return db.prepare(`
SELECT user_id FROM ticket_users WHERE ticket_id=?
`).all(ticketId).map(r => r.user_id);
},
getSettings() {
const row = db.prepare(`SELECT data FROM settings WHERE id=1`).get();
if (!row) return {};
try {
return JSON.parse(row.data);
} catch {
return {};
}
},
saveSettings(obj) {
const data = JSON.stringify(obj);
db.prepare(`
INSERT INTO settings (id, data)
VALUES (1, @data)
ON CONFLICT(id) DO UPDATE SET data = excluded.data
`).run({ data });
}
};
+1
View File
@@ -1,5 +1,6 @@
// SupportBot | Emerald Services
// Event Structure
class Event {
constructor(event, run) {
this.event = event;
+15 -59
View File
@@ -1,74 +1,30 @@
// SupportBot | Emerald Services
// Ticket ID Structure
const fs = require("fs");
const db = require("./Database")
class TicketNumberID {
static async pad() {
let n = this.get() + "";
let z = "0";
let width = 4;
await this.set(this.get() + 1);
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
let num = await this.get()
let n = num + ""
await this.set(num + 1)
return n.length >= 4 ? n : new Array(4 - n.length + 1).join("0") + n
}
static get() {
let ticketnumberID = JSON.parse(
fs.readFileSync("./Data/TicketData.json", "utf8")
);
if (!ticketnumberID.id) {
ticketnumberID.id = 0;
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(ticketnumberID, null, 4),
(err) => {
if (err) console.error(err);
}
);
return 0;
} else {
return ticketnumberID.id;
}
static async get() {
const row = db.getTicketPanel()
if (!row) return await this.set(0)
return row.id
}
static set(value) {
let ticketnumberID = JSON.parse(
fs.readFileSync("./Data/TicketData.json", "utf8")
);
ticketnumberID.id = value;
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(ticketnumberID, null, 4),
(err) => {
if (err) console.error(err);
}
);
return value;
static async set(value) {
db.saveTicketPanel(`panel-${value}`, "none")
return value
}
static reset() {
let ticketnumberID = JSON.parse(
fs.readFileSync("./Data/TicketData.json", "utf8")
);
ticketnumberID.id = 0;
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(ticketnumberID, null, 4),
(err) => {
if (err) console.error(err);
}
);
return 0;
static async reset() {
return await this.set(0)
}
}
module.exports = TicketNumberID;
module.exports = TicketNumberID
+63 -4
View File
@@ -8,17 +8,76 @@
// Installed with MIT License
//
// Discord Support: https://emeraldsrv.dev/discord
// Community Resources: https://emeraldsrv.dev/resources
// Community Resources: https://community.emeraldsrv.dev
const fs = require("fs");
const path = require("path");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(
fs.readFileSync("./Configs/supportbot.yml", "utf8")
);
const Client = require("./Structures/Client.js");
const client = new Client({});
const client = new Client({
intents: ['Guilds', 'GuildMembers', 'GuildMessages', 'MessageContent']
});
client.start(supportbot.General.Token);
// SupportBot - New Logging System
const logTypes = ["Output", "Warn", "Error"];
if (!fs.existsSync("./Logs")) {
fs.mkdirSync("./Logs");
}
logTypes.forEach((type) => {
const dir = `./Logs/${type}`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
});
function logToFile(type, data) {
const date = new Date().toISOString().split("T")[0];
const file = path.join(`./logs/${type}`, `${type}-${date}.log`);
fs.appendFileSync(file, `[${new Date().toISOString()}] ${data}\n`);
}
const origLog = console.log;
console.log = (...args) => {
origLog(...args);
logToFile("output", args.join(" "));
};
const origWarn = console.warn;
console.warn = (...args) => {
origWarn(...args);
logToFile("warn", args.join(" "));
};
const origError = console.error;
console.error = (...args) => {
origError(...args);
logToFile("error", args.join(" "));
};
process.on("unhandledRejection", (reason) => {
console.error("Unhandled Rejection:", reason);
});
process.on("uncaughtException", (err) => {
console.error("Uncaught Exception:", err);
});
client.start(supportbot.Token);
-555
View File
@@ -1,555 +0,0 @@
{
"name": "supportbot",
"version": "7.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "supportbot",
"version": "7.4.0",
"license": "MIT",
"dependencies": {
"discord.js": "^13.3.1",
"js-yaml": "^4.1.0"
}
},
"node_modules/@discordjs/builders": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.2.tgz",
"integrity": "sha512-/YRd11SrcluqXkKppq/FAVzLIPRVlIVmc6X8ZklspzMIHDtJ+A4W37D43SHvLdH//+NnK+SHW/WeOF4Ts54PeQ==",
"dependencies": {
"@sindresorhus/is": "^4.2.0",
"discord-api-types": "^0.24.0",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@discordjs/collection": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.3.2.tgz",
"integrity": "sha512-dMjLl60b2DMqObbH1MQZKePgWhsNe49XkKBZ0W5Acl5uVV43SN414i2QfZwRI7dXAqIn8pEWD2+XXQFn9KWxqg==",
"engines": {
"node": ">=16.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@discordjs/form-data": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@sapphire/async-queue": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.9.tgz",
"integrity": "sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ==",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz",
"integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@types/node": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.0.tgz",
"integrity": "sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw=="
},
"node_modules/@types/node-fetch": {
"version": "2.5.12",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz",
"integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==",
"dependencies": {
"@types/node": "*",
"form-data": "^3.0.0"
}
},
"node_modules/@types/ws": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz",
"integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/discord-api-types": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz",
"integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==",
"engines": {
"node": ">=12"
}
},
"node_modules/discord.js": {
"version": "13.3.1",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.3.1.tgz",
"integrity": "sha512-zn4G8tL5+tMV00+0aSsVYNYcIfMSdT2g0nudKny+Ikd+XKv7m6bqI7n3Vji0GIRqXDr5ArPaw+iYFM2I1Iw3vg==",
"dependencies": {
"@discordjs/builders": "^0.8.1",
"@discordjs/collection": "^0.3.2",
"@discordjs/form-data": "^3.0.1",
"@sapphire/async-queue": "^1.1.8",
"@types/node-fetch": "^2.5.12",
"@types/ws": "^8.2.0",
"discord-api-types": "^0.24.0",
"node-fetch": "^2.6.1",
"ws": "^8.2.3"
},
"engines": {
"node": ">=16.6.0",
"npm": ">=7.0.0"
}
},
"node_modules/dot-prop": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
"dependencies": {
"is-obj": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/form-data": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"engines": {
"node": ">=8"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"node_modules/mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"dependencies": {
"mime-db": "1.51.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-fetch": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/ow": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
"integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==",
"dependencies": {
"@sindresorhus/is": "^4.0.1",
"callsites": "^3.1.0",
"dot-prop": "^6.0.1",
"lodash.isequal": "^4.5.0",
"type-fest": "^1.2.1",
"vali-date": "^1.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"node_modules/ts-mixer": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.0.tgz",
"integrity": "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ=="
},
"node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"node_modules/type-fest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/vali-date": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
"integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/ws": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
"integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
},
"dependencies": {
"@discordjs/builders": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.2.tgz",
"integrity": "sha512-/YRd11SrcluqXkKppq/FAVzLIPRVlIVmc6X8ZklspzMIHDtJ+A4W37D43SHvLdH//+NnK+SHW/WeOF4Ts54PeQ==",
"requires": {
"@sindresorhus/is": "^4.2.0",
"discord-api-types": "^0.24.0",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
}
},
"@discordjs/collection": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.3.2.tgz",
"integrity": "sha512-dMjLl60b2DMqObbH1MQZKePgWhsNe49XkKBZ0W5Acl5uVV43SN414i2QfZwRI7dXAqIn8pEWD2+XXQFn9KWxqg=="
},
"@discordjs/form-data": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"@sapphire/async-queue": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.9.tgz",
"integrity": "sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ=="
},
"@sindresorhus/is": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz",
"integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw=="
},
"@types/node": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.0.tgz",
"integrity": "sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw=="
},
"@types/node-fetch": {
"version": "2.5.12",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz",
"integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==",
"requires": {
"@types/node": "*",
"form-data": "^3.0.0"
}
},
"@types/ws": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz",
"integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==",
"requires": {
"@types/node": "*"
}
},
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"discord-api-types": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz",
"integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A=="
},
"discord.js": {
"version": "13.3.1",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.3.1.tgz",
"integrity": "sha512-zn4G8tL5+tMV00+0aSsVYNYcIfMSdT2g0nudKny+Ikd+XKv7m6bqI7n3Vji0GIRqXDr5ArPaw+iYFM2I1Iw3vg==",
"requires": {
"@discordjs/builders": "^0.8.1",
"@discordjs/collection": "^0.3.2",
"@discordjs/form-data": "^3.0.1",
"@sapphire/async-queue": "^1.1.8",
"@types/node-fetch": "^2.5.12",
"@types/ws": "^8.2.0",
"discord-api-types": "^0.24.0",
"node-fetch": "^2.6.1",
"ws": "^8.2.3"
}
},
"dot-prop": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
"requires": {
"is-obj": "^2.0.0"
}
},
"form-data": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"requires": {
"argparse": "^2.0.1"
}
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
},
"mime-types": {
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"requires": {
"mime-db": "1.51.0"
}
},
"node-fetch": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"ow": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
"integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==",
"requires": {
"@sindresorhus/is": "^4.0.1",
"callsites": "^3.1.0",
"dot-prop": "^6.0.1",
"lodash.isequal": "^4.5.0",
"type-fest": "^1.2.1",
"vali-date": "^1.0.0"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"ts-mixer": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.0.tgz",
"integrity": "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ=="
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"type-fest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="
},
"vali-date": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
"integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"ws": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
"integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
"requires": {}
}
}
}
+14 -6
View File
@@ -1,11 +1,11 @@
{
"name": "supportbot",
"version": "7.4.0",
"version": "8.4",
"description": "SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand.",
"main": "index.js",
"scripts": {
"start": "npm i && node index.js",
"test": "node index.js test"
"setup": "npm i && node index.js",
"start": "node index.js"
},
"repository": {
"type": "git",
@@ -16,9 +16,17 @@
"bugs": {
"url": "https://github.com/Emerald-Services/SupportBot/issues"
},
"homepage": "https://github.com/Emerald-Services/SupportBot#readme",
"homepage": "https://community.emeraldsrv.dev",
"dependencies": {
"discord.js": "^13.3.1",
"js-yaml": "^4.1.0"
"axios": "^1.7.2",
"better-sqlite3": "^12.2.0",
"discord.js": "^14.13.0",
"figlet": "^1.9.3",
"gradient-string": "^3.0.0",
"js": "^0.1.0",
"js-yaml": "^4.1.0",
"moment": "^2.29.3",
"ms": "^2.1.3",
"openai": "^4.50.0"
}
}