Removed optional addons by default
This commit is contained in:
1 parent
004b638dbf
commit
ea2a8c97ae
24 files changed
-2231
No files matched your search
@@ -1,111 +0,0 @@
|
||||
Command:
|
||||
Name: "applicationsetup"
|
||||
Description: "Posts the application panel"
|
||||
Permission:
|
||||
- "ManageGuild"
|
||||
|
||||
Application:
|
||||
ReviewChannelId: "1481380207305691287"
|
||||
CooldownHours: 24
|
||||
OneOpenApplicationPerUserPerType: true
|
||||
|
||||
Panel:
|
||||
Title: "Applications"
|
||||
Description: "Choose an application type from the dropdown below."
|
||||
Placeholder: "Select an application type"
|
||||
|
||||
Review:
|
||||
Title: "New Application"
|
||||
AcceptLabel: "Accept"
|
||||
RejectLabel: "Reject"
|
||||
|
||||
Applications:
|
||||
staff:
|
||||
Name: "Staff Team"
|
||||
Description: "Apply for the staff team"
|
||||
AcceptedRoleId: "" # Leave "" blank to disable
|
||||
Questions:
|
||||
Username:
|
||||
Label: "What is your Discord username?"
|
||||
Placeholder: "ExampleUser"
|
||||
Required: true
|
||||
Style: "Short"
|
||||
MinLength: 2
|
||||
MaxLength: 50
|
||||
|
||||
Age:
|
||||
Label: "How old are you?"
|
||||
Placeholder: "18"
|
||||
Required: true
|
||||
Style: "Short"
|
||||
MinLength: 1
|
||||
MaxLength: 3
|
||||
|
||||
Timezone:
|
||||
Label: "What is your timezone?"
|
||||
Placeholder: "GMT / BST / EST"
|
||||
Required: true
|
||||
Style: "Short"
|
||||
MinLength: 2
|
||||
MaxLength: 30
|
||||
|
||||
Experience:
|
||||
Label: "What experience do you have?"
|
||||
Placeholder: "Tell us about your experience"
|
||||
Required: true
|
||||
Style: "Paragraph"
|
||||
MinLength: 10
|
||||
MaxLength: 1000
|
||||
|
||||
Reason:
|
||||
Label: "Why should we accept you?"
|
||||
Placeholder: "Tell us why"
|
||||
Required: true
|
||||
Style: "Paragraph"
|
||||
MinLength: 10
|
||||
MaxLength: 1000
|
||||
|
||||
developer:
|
||||
Name: "Developer"
|
||||
Description: "Apply for the development team"
|
||||
AcceptedRoleId: ""
|
||||
Questions:
|
||||
Username:
|
||||
Label: "What is your Discord username?"
|
||||
Placeholder: "ExampleUser"
|
||||
Required: true
|
||||
Style: "Short"
|
||||
MinLength: 2
|
||||
MaxLength: 50
|
||||
|
||||
Languages:
|
||||
Label: "What languages do you know?"
|
||||
Placeholder: "JavaScript, Python, Lua"
|
||||
Required: true
|
||||
Style: "Paragraph"
|
||||
MinLength: 3
|
||||
MaxLength: 500
|
||||
|
||||
Projects:
|
||||
Label: "What projects have you worked on?"
|
||||
Placeholder: "Describe your work"
|
||||
Required: true
|
||||
Style: "Paragraph"
|
||||
MinLength: 10
|
||||
MaxLength: 1000
|
||||
|
||||
Availability:
|
||||
Label: "How active can you be?"
|
||||
Placeholder: "How much time can you commit?"
|
||||
Required: true
|
||||
Style: "Paragraph"
|
||||
MinLength: 5
|
||||
MaxLength: 500
|
||||
|
||||
Reason:
|
||||
Label: "Why do you want this role?"
|
||||
Placeholder: "Tell us why"
|
||||
Required: true
|
||||
Style: "Paragraph"
|
||||
MinLength: 10
|
||||
MaxLength: 1000
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
# SupportBot | Emerald Services
|
||||
# Discord Stats addon | Created by Xiled Emerald
|
||||
|
||||
Guild_ID: "GUILD_ID_HERE"
|
||||
|
||||
StatsCategory: "Server Statistics"
|
||||
|
||||
UserCount_Name: "Total Members:"
|
||||
ChannelCount_Name: "Total Channels:"
|
||||
RoleCount_Name: "Total Roles:"
|
||||
@@ -1,16 +0,0 @@
|
||||
# Minecraft Server Configuration
|
||||
|
||||
Java:
|
||||
IP: "play.sapling.gg"
|
||||
Port: 25565
|
||||
DisplayName: "Java Edition"
|
||||
Bedrock:
|
||||
IP: "play.sapling.gg"
|
||||
Port: 19132
|
||||
DisplayName: "Bedrock Edition"
|
||||
|
||||
Embed:
|
||||
Color: "#57F287"
|
||||
OfflineColor: "#ED4245"
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# SupportBot Temporary Voice Channels Configuration
|
||||
# Addon Version 1.0
|
||||
|
||||
Enabled: true
|
||||
|
||||
# The ID of the voice channel that users join to create a new one
|
||||
JoinToCreateChannelID: "CHANNEL_ID"
|
||||
|
||||
# The category ID where new channels should be created (Optional)
|
||||
# If left blank, it will create it in the same category as the JoinToCreate channel
|
||||
CategoryID: "CATEGORY_ID"
|
||||
|
||||
# The name pattern for new channels
|
||||
|
||||
# Placeholders:
|
||||
# {user}: Username
|
||||
ChannelNamePattern: "🔊 {user}'s Channel"
|
||||
|
||||
# Should the channel be deleted when the last person leaves?
|
||||
AutoDelete: true
|
||||
|
||||
# Voice Channel Hub (Control Panel)
|
||||
Hub:
|
||||
Enabled: true
|
||||
SendOnCreate: true
|
||||
LayoutStyle: "classic" # classic | modern
|
||||
|
||||
# Classic Layout Settings (Standard Embed + Buttons)
|
||||
Classic:
|
||||
Title: "Voice Channel Hub"
|
||||
Description: "Manage your voice channel using the buttons below."
|
||||
Color: "#57F287"
|
||||
|
||||
# Modern Layout Settings (V2 Containers)
|
||||
Modern:
|
||||
Title: "Voice Channel Controller"
|
||||
Body: "Control temporary voice channel using this control panel."
|
||||
Color: "#57F287"
|
||||
@@ -1,28 +0,0 @@
|
||||
# +==================================================================================+
|
||||
# | |
|
||||
# | __ __ _ _ _ _ _ |
|
||||
# | \ \ / /__ _ _ ___| |__ | | ___ _ _ / \ __| | __| | ___ _ __ |
|
||||
# | \ \ / / _ \| | | |/ __| '_ \| |/ _ \ | | | / _ \ / _` |/ _` |/ _ \| '_ \ |
|
||||
# | \ V / (_) | |_| | (__| | | | | __/ |_| | / ___ \ (_| | (_| | (_) | | | | |
|
||||
# | \_/ \___/ \__,_|\___|_| |_|_|\___|\__, | /_/ \_\__,_|\__,_|\___/|_| |_| |
|
||||
# | |___/ |
|
||||
# | |
|
||||
# | Vouchley Addon for SupportBot |
|
||||
# | Created by Emerald Services |
|
||||
# | |
|
||||
# +==================================================================================+
|
||||
|
||||
Vouchley:
|
||||
API_Key: "REQUEST_API_KEY_BELOW"
|
||||
|
||||
# Vouchley API Documentation: https://www.vouchley.com/docs/api/getting-started
|
||||
# To use the API you will need an API key. To get this create a ticket in https://discord.gg/buzz
|
||||
|
||||
Embed:
|
||||
Colour: 0x6063fc # 0x<hexcode> without the #
|
||||
Ephermal: true # Make the profile searches hidden so only the interacting user can see them.
|
||||
|
||||
Command:
|
||||
Name: "vouchley" # The name of the command (/vouchley)
|
||||
Description: "Search a Vouchley Profile." # The description of the command (/vouchley)
|
||||
Permissions: ["SendMessages"]
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"applications": []
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
const Discord = require("discord.js");
|
||||
const { Command } = require("../Structures/Addon.js");
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
const axios = require("axios");
|
||||
|
||||
// Load configs
|
||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
const minecraftConfig = yaml.load(fs.readFileSync("./Addons/Configs/minecraft.yml", "utf8"));
|
||||
|
||||
module.exports = new Command({
|
||||
name: "serverip",
|
||||
description: "Display the Minecraft server IP and status",
|
||||
options: [
|
||||
{
|
||||
name: "type",
|
||||
description: "The edition of Minecraft (Java or Bedrock)",
|
||||
type: Discord.ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
choices: [
|
||||
{ name: "Java", value: "java" },
|
||||
{ name: "Bedrock", value: "bedrock" },
|
||||
],
|
||||
},
|
||||
],
|
||||
permissions: ["SendMessages"],
|
||||
|
||||
async run(interaction) {
|
||||
const type = interaction.options.getString("type");
|
||||
const config = type === "java" ? minecraftConfig.Java : minecraftConfig.Bedrock;
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
const response = await axios.get(`https://api.mcstatus.io/v2/status/${type}/${config.IP}:${config.Port}`);
|
||||
const data = response.data;
|
||||
|
||||
if (!data.online) {
|
||||
throw new Error("Server is offline");
|
||||
}
|
||||
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setTitle(`${config.DisplayName} Status`)
|
||||
.setTimestamp();
|
||||
|
||||
embed.setColor(minecraftConfig.Embed.Color || supportbot.Embed.Colours.General)
|
||||
.setDescription(`The server is currently **Online**!`)
|
||||
.addFields(
|
||||
{ name: "IP Address", value: `\`${config.IP}\``, inline: true },
|
||||
{ name: "Port", value: `\`${config.Port}\``, inline: true },
|
||||
{ name: "Players", value: `\`${data.players.online}/${data.players.max}\``, inline: true },
|
||||
{ name: "Version", value: `\`${type === "java" ? data.version.name_clean : data.version.name || "Unknown"}\``, inline: true }
|
||||
);
|
||||
|
||||
if (data.motd && data.motd.clean) {
|
||||
embed.addFields({ name: "MOTD", value: `\`\`\`${data.motd.clean}\`\`\`` });
|
||||
}
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
} catch (error) {
|
||||
console.error("Error fetching Minecraft status:", error);
|
||||
|
||||
const config = interaction.options.getString("type") === "java" ? minecraftConfig.Java : minecraftConfig.Bedrock;
|
||||
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setTitle(`${config.DisplayName} Status`)
|
||||
.setTimestamp()
|
||||
.setColor(minecraftConfig.Embed.OfflineColor || "#FF0000")
|
||||
.setDescription(`The server is currently **Offline**.`)
|
||||
.addFields(
|
||||
{ name: "IP Address", value: `\`${config.IP}\``, inline: true },
|
||||
{ name: "Port", value: `\`${config.Port}\``, inline: true }
|
||||
);
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
# SupportBot Sanction Addon
|
||||
# Created by ItsNyoty
|
||||
|
||||
LogChannel: "1275426007980965908"
|
||||
# The ID of the channel where the logs will be sent.
|
||||
@@ -1,157 +0,0 @@
|
||||
const Discord = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const { Command } = require('../Structures/Addon.js');
|
||||
const supportbot = yaml.load(fs.readFileSync('./Configs/supportbot.yml', 'utf8'));
|
||||
const sactionaddon = yaml.load(fs.readFileSync('./Addons/Configs/sactionaddon.yml', 'utf8'));
|
||||
|
||||
module.exports = new Command({
|
||||
name: 'sanction',
|
||||
description: 'Sanction a user (kick, ban, or timeout).',
|
||||
options: [
|
||||
{
|
||||
name: 'action',
|
||||
type: 3,
|
||||
description: 'The action to perform (kick, ban, timeout)',
|
||||
required: true,
|
||||
choices: [
|
||||
{ name: 'Kick', value: 'kick' },
|
||||
{ name: 'Ban', value: 'ban' },
|
||||
{ name: 'Timeout', value: 'timeout' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
type: 6,
|
||||
description: 'The user to sanction',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'reason',
|
||||
type: 3,
|
||||
description: 'Reason for the sanction',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
type: 3,
|
||||
description: 'Duration of the timeout (e.g., "10m", "2h", "1d")',
|
||||
required: false
|
||||
}
|
||||
],
|
||||
permissions: [],
|
||||
|
||||
async run(interaction) {
|
||||
const action = interaction.options.getString('action');
|
||||
const user = interaction.options.getUser('user');
|
||||
const reason = interaction.options.getString('reason') || 'No reason provided';
|
||||
const duration = interaction.options.getString('duration');
|
||||
const staffRoleId = supportbot.Roles.StaffMember.Staff;
|
||||
|
||||
const member = interaction.guild.members.cache.get(interaction.user.id);
|
||||
if (!member || !member.roles.cache.has(staffRoleId)) {
|
||||
return interaction.reply({ content: 'You do not have permission to use this command.', ephemeral: true });
|
||||
}
|
||||
|
||||
const targetMember = interaction.guild.members.cache.get(user.id);
|
||||
if (!targetMember) {
|
||||
return interaction.reply({ content: 'The user is not a member of this guild.', ephemeral: true });
|
||||
}
|
||||
|
||||
try {
|
||||
let actionMessage = '';
|
||||
let logEmbed = new Discord.EmbedBuilder().setColor(supportbot.Embed.Colours.General);
|
||||
|
||||
switch (action) {
|
||||
case 'kick':
|
||||
if (!targetMember.kickable) return interaction.reply({ content: 'I cannot kick this user.', ephemeral: true });
|
||||
await targetMember.kick(reason);
|
||||
actionMessage = `Successfully kicked ${user.tag} for: ${reason}`;
|
||||
logEmbed
|
||||
.setTitle('User Kicked')
|
||||
.setDescription(`**User:** ${user.tag}\n**Reason:** ${reason}`)
|
||||
.addFields(
|
||||
{ name: 'Action By', value: interaction.user.tag, inline: true },
|
||||
{ name: 'Action', value: 'Kick', inline: true },
|
||||
{ name: 'Timestamp', value: new Date().toISOString(), inline: true }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'ban':
|
||||
if (!targetMember.bannable) return interaction.reply({ content: 'I cannot ban this user.', ephemeral: true });
|
||||
await targetMember.ban({ reason });
|
||||
actionMessage = `Successfully banned ${user.tag} for: ${reason}`;
|
||||
logEmbed
|
||||
.setTitle('User Banned')
|
||||
.setDescription(`**User:** ${user.tag}\n**Reason:** ${reason}`)
|
||||
.addFields(
|
||||
{ name: 'Action By', value: interaction.user.tag, inline: true },
|
||||
{ name: 'Action', value: 'Ban', inline: true },
|
||||
{ name: 'Timestamp', value: new Date().toISOString(), inline: true }
|
||||
);
|
||||
break;
|
||||
|
||||
case 'timeout':
|
||||
// Ensure a valid duration is provided
|
||||
if (!duration) return interaction.reply({ content: 'You must specify a duration for the timeout.', ephemeral: true });
|
||||
|
||||
// Convert duration to milliseconds
|
||||
const parsedDuration = ms(duration);
|
||||
if (!parsedDuration) return interaction.reply({ content: 'Invalid duration format. Use "1s", "5m", "2h", or "1d".', ephemeral: true });
|
||||
|
||||
// Apply the timeout
|
||||
await targetMember.timeout(parsedDuration, reason);
|
||||
actionMessage = `Successfully timed out ${user.tag} for ${duration} with reason: ${reason}`;
|
||||
logEmbed
|
||||
.setTitle('User Timed Out')
|
||||
.setDescription(`**User:** ${user.tag}\n**Duration:** ${duration}\n**Reason:** ${reason}`)
|
||||
.addFields(
|
||||
{ name: 'Action By', value: interaction.user.tag, inline: true },
|
||||
{ name: 'Action', value: 'Timeout', inline: true },
|
||||
{ name: 'Timestamp', value: new Date().toISOString(), inline: true }
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
return interaction.reply({ content: 'Invalid action specified.', ephemeral: true });
|
||||
}
|
||||
|
||||
// Send the action message
|
||||
interaction.reply({ content: actionMessage, ephemeral: true });
|
||||
|
||||
// Log the action to the logging channel
|
||||
const logChannel = interaction.guild.channels.cache.get(sactionaddon.LogChannel);
|
||||
if (logChannel) {
|
||||
logChannel.send({ embeds: [logEmbed] });
|
||||
} else {
|
||||
console.error('Logging channel not found.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error executing sanction command:', error);
|
||||
interaction.reply({ content: 'An error occurred while executing the command.', ephemeral: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts a time duration string to milliseconds.
|
||||
* @param {string} duration - The duration string (e.g., "10m", "2h", "1d").
|
||||
* @returns {number} - The duration in milliseconds.
|
||||
*/
|
||||
function ms(duration) {
|
||||
const regex = /^(\d+)([smhd])$/;
|
||||
const match = regex.exec(duration);
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
const value = parseInt(match[1], 10);
|
||||
const unit = match[2];
|
||||
|
||||
switch (unit) {
|
||||
case 's': return value * 1000; // seconds
|
||||
case 'm': return value * 60 * 1000; // minutes
|
||||
case 'h': return value * 60 * 60 * 1000; // hours
|
||||
case 'd': return value * 24 * 60 * 60 * 1000; // days
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
{
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
const Discord = require("discord.js");
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
const { Command } = require('../Structures/Addon.js'); // Adjust the path as needed
|
||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
|
||||
module.exports = new Command({
|
||||
name: "tags",
|
||||
description: "Retrieve a saved tag.",
|
||||
options: [
|
||||
{
|
||||
name: 'name',
|
||||
type: Discord.ApplicationCommandOptionType.String,
|
||||
description: 'The name of the tag',
|
||||
required: true,
|
||||
autocomplete: true
|
||||
},
|
||||
],
|
||||
permissions: ["SendMessages"],
|
||||
|
||||
async autocomplete(interaction) {
|
||||
const tagsPath = './Addons/Data/tags.json';
|
||||
const focusedValue = interaction.options.getFocused().toLowerCase();
|
||||
|
||||
if (!fs.existsSync(tagsPath)) {
|
||||
return interaction.respond([]);
|
||||
}
|
||||
|
||||
const tags = JSON.parse(fs.readFileSync(tagsPath, 'utf8'));
|
||||
const choices = Object.keys(tags)
|
||||
.filter(tag => tag.toLowerCase().includes(focusedValue))
|
||||
.slice(0, 25) // Discord has a limit of 25 choices
|
||||
.map(tag => ({ name: tag, value: tag }));
|
||||
|
||||
return interaction.respond(choices);
|
||||
},
|
||||
|
||||
async run(interaction) {
|
||||
const tagsPath = './Addons/Data/tags.json';
|
||||
const tagName = interaction.options.getString('name').toLowerCase();
|
||||
|
||||
if (!fs.existsSync(tagsPath)) {
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription('No tags have been created yet.')
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
return interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
|
||||
const tags = JSON.parse(fs.readFileSync(tagsPath, 'utf8'));
|
||||
|
||||
if (tags[tagName]) {
|
||||
const tag = tags[tagName];
|
||||
|
||||
if (tag.embed) {
|
||||
// For embeds, Discord automatically handles line breaks correctly
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription(tag.content)
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
|
||||
if (tag.image) {
|
||||
embed.setImage(tag.image);
|
||||
}
|
||||
|
||||
return interaction.reply({ embeds: [embed], ephemeral: tag.ephemeral || false });
|
||||
} else {
|
||||
// For plain text, we need to keep the line breaks
|
||||
const response = {
|
||||
content: tag.content,
|
||||
ephemeral: tag.ephemeral || false
|
||||
};
|
||||
|
||||
if (tag.image) {
|
||||
response.files = [tag.image];
|
||||
}
|
||||
|
||||
return interaction.reply(response);
|
||||
}
|
||||
} else {
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription(`Tag "${tagName}" not found.`)
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
return interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,76 +0,0 @@
|
||||
const Discord = require("discord.js");
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
const { Command } = require('../Structures/Addon.js'); // Adjust the path as needed
|
||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
|
||||
module.exports = new Command({
|
||||
name: "tagscreate",
|
||||
description: "Create a new tag.",
|
||||
options: [
|
||||
{
|
||||
name: 'name',
|
||||
type: Discord.ApplicationCommandOptionType.String,
|
||||
description: 'The name of the tag',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: Discord.ApplicationCommandOptionType.String,
|
||||
description: 'The content of the tag (use ; or /n for new lines)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: Discord.ApplicationCommandOptionType.Attachment,
|
||||
description: 'The image URL of the tag',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'embed',
|
||||
type: Discord.ApplicationCommandOptionType.Boolean,
|
||||
description: 'Do you want this tag to send as an embed',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'ephemeral',
|
||||
type: Discord.ApplicationCommandOptionType.Boolean,
|
||||
description: 'Do you want this tag to send as ephemeral',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
permissions: ["SendMessages"],
|
||||
|
||||
async run(interaction) {
|
||||
const tagName = interaction.options.getString('name').toLowerCase();
|
||||
let tagContent = interaction.options.getString('content');
|
||||
const tagImage = interaction.options.getAttachment('image');
|
||||
const isEmbed = interaction.options.getBoolean('embed');
|
||||
const isEphemeral = interaction.options.getBoolean('ephemeral');
|
||||
const tagsPath = './Addons/Data/tags.json';
|
||||
|
||||
// Process line breaks in content - support both ; and /n notations
|
||||
tagContent = tagContent.replace(/;/g, '\n').replace(/\/n/g, '\n');
|
||||
|
||||
let tags = {};
|
||||
|
||||
if (fs.existsSync(tagsPath)) {
|
||||
tags = JSON.parse(fs.readFileSync(tagsPath, 'utf8'));
|
||||
}
|
||||
|
||||
tags[tagName] = {
|
||||
content: tagContent,
|
||||
image: tagImage ? tagImage.url : null,
|
||||
embed: isEmbed || false,
|
||||
ephemeral: isEphemeral || false,
|
||||
};
|
||||
|
||||
fs.writeFileSync(tagsPath, JSON.stringify(tags, null, 4));
|
||||
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription(`Tag "${tagName}" created successfully.`)
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
|
||||
return interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
},
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
const Discord = require("discord.js");
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
const { Command } = require('../Structures/Addon.js'); // Adjust the path as needed
|
||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
|
||||
module.exports = new Command({
|
||||
name: "tagsdelete",
|
||||
description: "Delete an existing tag.",
|
||||
options: [
|
||||
{
|
||||
name: 'name',
|
||||
type: Discord.ApplicationCommandOptionType.String,
|
||||
description: 'The name of the tag to delete',
|
||||
required: true,
|
||||
autocomplete: true
|
||||
},
|
||||
],
|
||||
permissions: ["SendMessages"],
|
||||
|
||||
async autocomplete(interaction) {
|
||||
const tagsPath = './Addons/Data/tags.json';
|
||||
const focusedValue = interaction.options.getFocused().toLowerCase();
|
||||
|
||||
if (!fs.existsSync(tagsPath)) {
|
||||
return interaction.respond([]);
|
||||
}
|
||||
|
||||
const tags = JSON.parse(fs.readFileSync(tagsPath, 'utf8'));
|
||||
const choices = Object.keys(tags)
|
||||
.filter(tag => tag.toLowerCase().includes(focusedValue))
|
||||
.slice(0, 25) // Discord has a limit of 25 choices
|
||||
.map(tag => ({ name: tag, value: tag }));
|
||||
|
||||
return interaction.respond(choices);
|
||||
},
|
||||
|
||||
async run(interaction) {
|
||||
const tagsPath = './Addons/Data/tags.json';
|
||||
const tagName = interaction.options.getString('name').toLowerCase();
|
||||
|
||||
if (!fs.existsSync(tagsPath)) {
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription('No tags have been created yet.')
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
return interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
|
||||
let tags = JSON.parse(fs.readFileSync(tagsPath, 'utf8'));
|
||||
|
||||
if (!tags[tagName]) {
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription(`Tag "${tagName}" not found.`)
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
return interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
|
||||
delete tags[tagName];
|
||||
|
||||
fs.writeFileSync(tagsPath, JSON.stringify(tags, null, 4));
|
||||
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setDescription(`Tag "${tagName}" deleted successfully.`)
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
|
||||
return interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
PayPal:
|
||||
ClientID: "clientID_Here"
|
||||
ClientSecret: "ClientSecret_Here"
|
||||
Mode: "live" # or "live"
|
||||
Email: "Email_Here" # Add your PayPal email here
|
||||
Embed:
|
||||
Colours:
|
||||
General: "#0099ff"
|
||||
Success: "#00ff00" # Add more colors if needed
|
||||
Error: "#ff0000" # Add more colors if needed
|
||||
Footer: "PayPal Invoice" # Add footer text here
|
||||
Logo: "https://your-logo-url.com/logo.png" # Add logo URL if used in invoice
|
||||
Note: "Thank you for your business!" # Add a note if used in invoice
|
||||
TOS: "Please review our terms and conditions. https://domain.com/tos" # Add terms of service if used in invoice
|
||||
@@ -1,146 +0,0 @@
|
||||
const { EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationCommandOptionType } = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const paypal = require('paypal-rest-sdk');
|
||||
const { Command } = require('../Structures/Addon.js'); // Adjust the path as needed
|
||||
|
||||
// Load the PayPal configuration
|
||||
const config = yaml.load(fs.readFileSync("./Addons/Configs/paypal.yml", "utf8"));
|
||||
const supportbot = config;
|
||||
|
||||
// Configure PayPal SDK
|
||||
paypal.configure({
|
||||
mode: supportbot.PayPal.Mode, // sandbox or live
|
||||
client_id: supportbot.PayPal.ClientID,
|
||||
client_secret: supportbot.PayPal.ClientSecret
|
||||
});
|
||||
|
||||
module.exports = new Command({
|
||||
name: "invoice",
|
||||
description: "Generate a PayPal invoice for a user.",
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
description: "The user to notify.",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "price",
|
||||
description: "The amount to invoice.",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "service",
|
||||
description: "The service provided.",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
description: "The email to invoice.",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
permissions: ["Administrator"],
|
||||
|
||||
async run(interaction) {
|
||||
const user = interaction.options.getUser("user");
|
||||
const price = interaction.options.getString("price");
|
||||
const service = interaction.options.getString("service");
|
||||
const email = interaction.options.getString("email");
|
||||
|
||||
// Validate the price format
|
||||
if (isNaN(price) || parseFloat(price) <= 0) {
|
||||
const errorEmbed = new EmbedBuilder()
|
||||
.setDescription(`The price provided is invalid.`)
|
||||
.setColor(supportbot.Embed.Colours.Error)
|
||||
.setFooter({ text: supportbot.Embed.Footer });
|
||||
return interaction.reply({ embeds: [errorEmbed], ephemeral: true });
|
||||
}
|
||||
|
||||
const invoiceData = {
|
||||
"merchant_info": {
|
||||
"email": supportbot.PayPal.Email,
|
||||
"business_name": interaction.guild.name
|
||||
},
|
||||
"billing_info": [{
|
||||
"email": email
|
||||
}],
|
||||
"items": [{
|
||||
"name": service,
|
||||
"quantity": 1,
|
||||
"unit_price": {
|
||||
"currency": "USD",
|
||||
"value": price
|
||||
}
|
||||
}],
|
||||
"logo_url": supportbot.PayPal.Logo,
|
||||
"note": supportbot.PayPal.Note,
|
||||
"terms": supportbot.PayPal.TOS,
|
||||
"payment_term": {
|
||||
"term_type": "NET_45"
|
||||
},
|
||||
"tax_inclusive": false,
|
||||
"total_amount": {
|
||||
"currency": "USD",
|
||||
"value": price
|
||||
}
|
||||
};
|
||||
|
||||
paypal.invoice.create(invoiceData, function (error, invoice) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
const errorEmbed = new EmbedBuilder()
|
||||
.setTitle(`PayPal Error`)
|
||||
.setDescription(`There was an error creating the invoice. Please check your configuration and try again.`)
|
||||
.setColor(supportbot.Embed.Colours.Error)
|
||||
.setFooter({ text: supportbot.Embed.Footer });
|
||||
return interaction.reply({ embeds: [errorEmbed], ephemeral: true });
|
||||
}
|
||||
|
||||
const invoiceId = invoice.id;
|
||||
paypal.invoice.send(invoiceId, function (error) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
const errorEmbed = new EmbedBuilder()
|
||||
.setTitle(`PayPal Error`)
|
||||
.setDescription(`There was an error sending the invoice. Please check your configuration and try again.`)
|
||||
.setColor(supportbot.Embed.Colours.Error)
|
||||
.setFooter({ text: supportbot.Embed.Footer });
|
||||
return interaction.reply({ embeds: [errorEmbed], ephemeral: true });
|
||||
}
|
||||
|
||||
const invoiceEmbed = new EmbedBuilder()
|
||||
.setTitle(`Payment System | ${interaction.guild.name}`)
|
||||
.addFields(
|
||||
{ name: 'Service', value: service, inline: true },
|
||||
{ name: 'Amount Due', value: `$${price}`, inline: true },
|
||||
{ name: 'Currency', value: 'USD', inline: true },
|
||||
{ name: 'Invoice ID', value: `\`${invoiceId}\``, inline: false }
|
||||
)
|
||||
.setColor(supportbot.Embed.Colours.General)
|
||||
.setFooter({ text: supportbot.Embed.Footer })
|
||||
.setTimestamp();
|
||||
|
||||
// Create a button with a link to the invoice
|
||||
const invoiceButton = new ButtonBuilder()
|
||||
.setLabel('View Invoice')
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(`https://www.paypal.com/invoice/payerView/details/${invoiceId}`);
|
||||
|
||||
// Create the action row
|
||||
const actionRow = new ActionRowBuilder()
|
||||
.addComponents(invoiceButton);
|
||||
|
||||
// Send the embed with the button
|
||||
interaction.reply({
|
||||
embeds: [invoiceEmbed],
|
||||
components: [actionRow]
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,227 +0,0 @@
|
||||
const Discord = require("discord.js");
|
||||
const { Event } = require("../Structures/Addon.js");
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
const tempVCConfig = yaml.load(fs.readFileSync("./Addons/Configs/tempvc.yml", "utf8"));
|
||||
|
||||
const tempChannels = new Map();
|
||||
|
||||
const voiceStateUpdate = new Event("voiceStateUpdate", async (client, oldState, newState) => {
|
||||
if (!tempVCConfig.Enabled) return;
|
||||
|
||||
const member = newState.member;
|
||||
const guild = newState.guild;
|
||||
|
||||
if (newState.channelId === tempVCConfig.JoinToCreateChannelID) {
|
||||
console.log(`[TempVC] ${member.user.tag} joined the Join-to-Create channel.`);
|
||||
}
|
||||
|
||||
if (newState.channelId === tempVCConfig.JoinToCreateChannelID) {
|
||||
try {
|
||||
const parentCategory = tempVCConfig.CategoryID || newState.channel.parentId;
|
||||
const channelName = tempVCConfig.ChannelNamePattern.replace("{user}", member.user.username);
|
||||
|
||||
const newChannel = await guild.channels.create({
|
||||
name: channelName,
|
||||
type: Discord.ChannelType.GuildVoice,
|
||||
parent: parentCategory,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: member.id,
|
||||
allow: [Discord.PermissionFlagsBits.ManageChannels, Discord.PermissionFlagsBits.MoveMembers],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
tempChannels.set(newChannel.id, member.id);
|
||||
await member.voice.setChannel(newChannel);
|
||||
|
||||
console.log(`[TempVC] Created channel "${channelName}" for ${member.user.tag}`);
|
||||
|
||||
if (tempVCConfig.Hub.Enabled && tempVCConfig.Hub.SendOnCreate) {
|
||||
try {
|
||||
const { ActionRowBuilder, ButtonBuilder, EmbedBuilder, MessageFlags, ButtonStyle } = Discord;
|
||||
const layoutStyle = tempVCConfig.Hub.LayoutStyle || "classic";
|
||||
|
||||
if (layoutStyle === "classic") {
|
||||
const config = tempVCConfig.Hub.Classic;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(config.Title)
|
||||
.setDescription(config.Description)
|
||||
.setColor(config.Color);
|
||||
|
||||
const buttons = new ActionRowBuilder().addComponents(
|
||||
new ButtonBuilder().setCustomId("tempvc_lock").setLabel("Lock").setEmoji("🔒").setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder().setCustomId("tempvc_unlock").setLabel("Unlock").setEmoji("🔓").setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder().setCustomId("tempvc_rename").setLabel("Rename").setEmoji("✏️").setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder().setCustomId("tempvc_limit").setLabel("Limit").setEmoji("👥").setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder().setCustomId("tempvc_claim").setLabel("Claim Ownership").setEmoji("👑").setStyle(ButtonStyle.Primary)
|
||||
);
|
||||
|
||||
await newChannel.send({ embeds: [embed], components: [buttons] });
|
||||
} else {
|
||||
const config = tempVCConfig.Hub.Modern;
|
||||
const colorInt = parseInt(config.Color.replace("#", ""), 16);
|
||||
|
||||
const container = {
|
||||
type: 11,
|
||||
accent_color: colorInt,
|
||||
children: [
|
||||
{
|
||||
type: 12,
|
||||
children: [
|
||||
{ type: 13, content: `## ${config.Title}` },
|
||||
{ type: 13, content: config.Body }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 15,
|
||||
divider: true
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
components: [
|
||||
{ type: 2, custom_id: "tempvc_lock", label: "Lock", emoji: { name: "🔒" }, style: 2 },
|
||||
{ type: 2, custom_id: "tempvc_unlock", label: "Unlock", emoji: { name: "🔓" }, style: 2 },
|
||||
{ type: 2, custom_id: "tempvc_rename", label: "Rename", emoji: { name: "✏️" }, style: 2 },
|
||||
{ type: 2, custom_id: "tempvc_limit", label: "Limit", emoji: { name: "👥" }, style: 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
components: [
|
||||
{ type: 2, custom_id: "tempvc_claim", label: "Claim Ownership", emoji: { name: "👑" }, style: 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
await newChannel.send({
|
||||
flags: MessageFlags.IsComponentsV2,
|
||||
components: [container]
|
||||
});
|
||||
}
|
||||
} catch (hubError) {
|
||||
console.error("[TempVC] Error sending Hub:", hubError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[TempVC] Error creating temporary channel:", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldState.channelId && tempChannels.has(oldState.channelId)) {
|
||||
const oldChannel = oldState.channel;
|
||||
|
||||
if (oldChannel && oldChannel.members.size === 0) {
|
||||
try {
|
||||
await oldChannel.delete();
|
||||
tempChannels.delete(oldState.channelId);
|
||||
console.log(`[TempVC] Deleted empty temporary channel "${oldChannel.name}"`);
|
||||
} catch (error) {
|
||||
if (error.code !== 10003) {
|
||||
console.error("[TempVC] Error deleting temporary channel:", error);
|
||||
}
|
||||
tempChannels.delete(oldState.channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const interactionEvent = new Event("interactionCreate", async (client, interaction) => {
|
||||
if (!interaction.isButton() && !interaction.isModalSubmit()) return;
|
||||
if (!interaction.customId.startsWith("tempvc_")) return;
|
||||
|
||||
const channel = interaction.channel;
|
||||
if (!tempChannels.has(channel.id)) {
|
||||
return interaction.reply({ content: "This channel is no longer a temporary voice channel.", flags: Discord.MessageFlags.Ephemeral });
|
||||
}
|
||||
|
||||
const ownerId = tempChannels.get(channel.id);
|
||||
const isOwner = interaction.user.id === ownerId || interaction.member.permissions.has(Discord.PermissionFlagsBits.Administrator);
|
||||
|
||||
if (interaction.customId === "tempvc_claim") {
|
||||
const currentOwnerInVC = channel.members.has(ownerId);
|
||||
if (currentOwnerInVC && interaction.user.id !== ownerId) {
|
||||
return interaction.reply({ content: "The owner is still in the voice channel!", flags: Discord.MessageFlags.Ephemeral });
|
||||
}
|
||||
|
||||
tempChannels.set(channel.id, interaction.user.id);
|
||||
await channel.permissionOverwrites.edit(interaction.user.id, {
|
||||
ManageChannels: true,
|
||||
MoveMembers: true,
|
||||
});
|
||||
|
||||
return interaction.reply({ content: "You are now the owner of this voice channel!", flags: Discord.MessageFlags.Ephemeral });
|
||||
}
|
||||
|
||||
if (!isOwner) {
|
||||
return interaction.reply({ content: "Only the channel owner can use these controls.", flags: Discord.MessageFlags.Ephemeral });
|
||||
}
|
||||
|
||||
try {
|
||||
switch (interaction.customId) {
|
||||
case "tempvc_lock":
|
||||
await channel.permissionOverwrites.edit(interaction.guild.id, { Connect: false });
|
||||
await interaction.reply({ content: "Channel locked! 🔒", flags: Discord.MessageFlags.Ephemeral });
|
||||
break;
|
||||
case "tempvc_unlock":
|
||||
await channel.permissionOverwrites.edit(interaction.guild.id, { Connect: null });
|
||||
await interaction.reply({ content: "Channel unlocked! 🔓", flags: Discord.MessageFlags.Ephemeral });
|
||||
break;
|
||||
case "tempvc_rename":
|
||||
const modal = new Discord.ModalBuilder()
|
||||
.setCustomId("tempvc_modal_rename")
|
||||
.setTitle("Rename Channel")
|
||||
.addComponents(
|
||||
new Discord.ActionRowBuilder().addComponents(
|
||||
new Discord.TextInputBuilder()
|
||||
.setCustomId("name")
|
||||
.setLabel("New Name")
|
||||
.setStyle(Discord.TextInputStyle.Short)
|
||||
.setPlaceholder(channel.name)
|
||||
.setRequired(true)
|
||||
)
|
||||
);
|
||||
await interaction.showModal(modal);
|
||||
break;
|
||||
case "tempvc_limit":
|
||||
const limitModal = new Discord.ModalBuilder()
|
||||
.setCustomId("tempvc_modal_limit")
|
||||
.setTitle("Set User Limit")
|
||||
.addComponents(
|
||||
new Discord.ActionRowBuilder().addComponents(
|
||||
new Discord.TextInputBuilder()
|
||||
.setCustomId("limit")
|
||||
.setLabel("Limit (0-99)")
|
||||
.setStyle(Discord.TextInputStyle.Short)
|
||||
.setPlaceholder("0 for no limit")
|
||||
.setRequired(true)
|
||||
)
|
||||
);
|
||||
await interaction.showModal(limitModal);
|
||||
break;
|
||||
}
|
||||
|
||||
if (interaction.isModalSubmit()) {
|
||||
if (interaction.customId === "tempvc_modal_rename") {
|
||||
const newName = interaction.fields.getTextInputValue("name");
|
||||
await channel.setName(newName);
|
||||
await interaction.reply({ content: `Channel renamed to **${newName}**!`, flags: Discord.MessageFlags.Ephemeral });
|
||||
} else if (interaction.customId === "tempvc_modal_limit") {
|
||||
const limit = parseInt(interaction.fields.getTextInputValue("limit"));
|
||||
if (isNaN(limit) || limit < 0 || limit > 99) {
|
||||
return interaction.reply({ content: "Please enter a valid number between 0 and 99.", flags: Discord.MessageFlags.Ephemeral });
|
||||
}
|
||||
await channel.setUserLimit(limit);
|
||||
await interaction.reply({ content: `User limit set to **${limit === 0 ? "Unlimited" : limit}**!`, flags: Discord.MessageFlags.Ephemeral });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[TempVC] Error handling interaction:", error);
|
||||
if (!interaction.replied) await interaction.reply({ content: "An error occurred.", flags: Discord.MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = [voiceStateUpdate, interactionEvent];
|
||||
@@ -1,652 +0,0 @@
|
||||
const Discord = require("discord.js");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
const { Command } = require("../Structures/Addon.js");
|
||||
|
||||
// Main bot config
|
||||
const supportbot = yaml.load(
|
||||
fs.readFileSync("./Configs/supportbot.yml", "utf8")
|
||||
);
|
||||
|
||||
// Addon config
|
||||
const addonConfig = yaml.load(
|
||||
fs.readFileSync("./Addons/Configs/applicationsystem.yml", "utf8")
|
||||
);
|
||||
|
||||
// Data file
|
||||
const dataPath = path.join(__dirname, "Data/applicationsystem.json");
|
||||
|
||||
function ensureDataFile() {
|
||||
const dir = path.dirname(dataPath);
|
||||
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(dataPath)) {
|
||||
fs.writeFileSync(
|
||||
dataPath,
|
||||
JSON.stringify({ applications: [] }, null, 2),
|
||||
"utf8"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function readData() {
|
||||
ensureDataFile();
|
||||
|
||||
try {
|
||||
const raw = fs.readFileSync(dataPath, "utf8");
|
||||
const parsed = JSON.parse(raw);
|
||||
|
||||
if (!parsed || !Array.isArray(parsed.applications)) {
|
||||
return { applications: [] };
|
||||
}
|
||||
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
console.error("[ApplicationSystem] Failed to read data file:", error);
|
||||
return { applications: [] };
|
||||
}
|
||||
}
|
||||
|
||||
function writeData(data) {
|
||||
ensureDataFile();
|
||||
fs.writeFileSync(dataPath, JSON.stringify(data, null, 2), "utf8");
|
||||
}
|
||||
|
||||
function now() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function createId() {
|
||||
return `app_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
|
||||
}
|
||||
|
||||
function parseColor(value, fallback = 0x2b2d31) {
|
||||
if (typeof value === "number") return value;
|
||||
|
||||
if (typeof value === "string") {
|
||||
const cleaned = value.trim().replace("#", "");
|
||||
if (/^[0-9A-Fa-f]{6}$/.test(cleaned)) {
|
||||
return parseInt(cleaned, 16);
|
||||
}
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function getGeneralColour() {
|
||||
return parseColor(supportbot?.Embed?.Colours?.General, 0x2b2d31);
|
||||
}
|
||||
|
||||
function buildNoticeContainer(title, description, colour = 0x5865f2) {
|
||||
return new Discord.ContainerBuilder()
|
||||
.setAccentColor(colour)
|
||||
.addTextDisplayComponents(
|
||||
new Discord.TextDisplayBuilder().setContent(`## ${title}`)
|
||||
)
|
||||
.addSeparatorComponents(new Discord.SeparatorBuilder())
|
||||
.addTextDisplayComponents(
|
||||
new Discord.TextDisplayBuilder().setContent(description)
|
||||
);
|
||||
}
|
||||
|
||||
function buildPanelContainer() {
|
||||
const options = Object.entries(addonConfig.Applications || {})
|
||||
.slice(0, 25)
|
||||
.map(([key, app]) =>
|
||||
new Discord.StringSelectMenuOptionBuilder()
|
||||
.setLabel(String(app.Name || key).slice(0, 100))
|
||||
.setDescription(
|
||||
String(app.Description || "Apply for this position").slice(0, 100)
|
||||
)
|
||||
.setValue(key)
|
||||
);
|
||||
|
||||
return new Discord.ContainerBuilder()
|
||||
.setAccentColor(getGeneralColour())
|
||||
.addTextDisplayComponents(
|
||||
new Discord.TextDisplayBuilder().setContent(
|
||||
`## ${addonConfig.Panel?.Title || "Applications"}`
|
||||
)
|
||||
)
|
||||
.addSeparatorComponents(new Discord.SeparatorBuilder())
|
||||
.addTextDisplayComponents(
|
||||
new Discord.TextDisplayBuilder().setContent(
|
||||
addonConfig.Panel?.Description ||
|
||||
"Choose an application from the dropdown below."
|
||||
)
|
||||
)
|
||||
.addActionRowComponents(
|
||||
new Discord.ActionRowBuilder().addComponents(
|
||||
new Discord.StringSelectMenuBuilder()
|
||||
.setCustomId("appsystem_select")
|
||||
.setPlaceholder(
|
||||
addonConfig.Panel?.Placeholder || "Select an application type"
|
||||
)
|
||||
.addOptions(options)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function textInputStyle(style) {
|
||||
return style === "Paragraph"
|
||||
? Discord.TextInputStyle.Paragraph
|
||||
: Discord.TextInputStyle.Short;
|
||||
}
|
||||
|
||||
function sanitizeQuestionKey(key) {
|
||||
return String(key).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
||||
}
|
||||
|
||||
function formatQuestionTitle(key) {
|
||||
return String(key)
|
||||
.replace(/_/g, " ")
|
||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
||||
}
|
||||
|
||||
function buildApplicationModal(typeKey) {
|
||||
const applicationType = addonConfig.Applications?.[typeKey];
|
||||
if (!applicationType) return null;
|
||||
|
||||
const modal = new Discord.ModalBuilder()
|
||||
.setCustomId(`appsystem_modal_${typeKey}`)
|
||||
.setTitle(String(applicationType.Name || typeKey).slice(0, 45));
|
||||
|
||||
const questionEntries = Object.entries(applicationType.Questions || {}).slice(0, 5);
|
||||
|
||||
for (const [key, question] of questionEntries) {
|
||||
const input = new Discord.TextInputBuilder()
|
||||
.setCustomId(sanitizeQuestionKey(key))
|
||||
.setLabel(String(question.Label || key).slice(0, 45))
|
||||
.setPlaceholder(String(question.Placeholder || "Enter your answer").slice(0, 100))
|
||||
.setRequired(Boolean(question.Required))
|
||||
.setStyle(textInputStyle(question.Style))
|
||||
.setMinLength(Number(question.MinLength || 1))
|
||||
.setMaxLength(Number(question.MaxLength || 1000));
|
||||
|
||||
modal.addComponents(
|
||||
new Discord.ActionRowBuilder().addComponents(input)
|
||||
);
|
||||
}
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
function buildReviewContainer(application, reviewerText = null, locked = false) {
|
||||
const statusText =
|
||||
application.status === "accepted"
|
||||
? "Accepted"
|
||||
: application.status === "rejected"
|
||||
? "Rejected"
|
||||
: "Pending";
|
||||
|
||||
const answerLines = Object.entries(application.answers || {})
|
||||
.map(([key, value]) => `### ${formatQuestionTitle(key)}\n${value}`)
|
||||
.join("\n\n");
|
||||
|
||||
const body = [
|
||||
`**Applicant:** <@${application.userId}>`,
|
||||
`**Tag:** ${application.userTag}`,
|
||||
`**Application Type:** ${application.typeName}`,
|
||||
`**Application ID:** \`${application.id}\``,
|
||||
`**Status:** ${statusText}`,
|
||||
"",
|
||||
answerLines || "*No answers provided.*",
|
||||
].join("\n");
|
||||
|
||||
const container = new Discord.ContainerBuilder()
|
||||
.setAccentColor(getGeneralColour())
|
||||
.addTextDisplayComponents(
|
||||
new Discord.TextDisplayBuilder().setContent(
|
||||
`## ${addonConfig.Review?.Title || "New Application"}`
|
||||
)
|
||||
)
|
||||
.addSeparatorComponents(new Discord.SeparatorBuilder())
|
||||
.addTextDisplayComponents(
|
||||
new Discord.TextDisplayBuilder().setContent(body)
|
||||
);
|
||||
|
||||
if (reviewerText) {
|
||||
container
|
||||
.addSeparatorComponents(new Discord.SeparatorBuilder())
|
||||
.addTextDisplayComponents(
|
||||
new Discord.TextDisplayBuilder().setContent(reviewerText)
|
||||
);
|
||||
}
|
||||
|
||||
container.addActionRowComponents(
|
||||
new Discord.ActionRowBuilder().addComponents(
|
||||
new Discord.ButtonBuilder()
|
||||
.setCustomId(`appsystem_accept_${application.id}`)
|
||||
.setLabel(addonConfig.Review?.AcceptLabel || "Accept")
|
||||
.setStyle(Discord.ButtonStyle.Success)
|
||||
.setDisabled(locked),
|
||||
new Discord.ButtonBuilder()
|
||||
.setCustomId(`appsystem_reject_${application.id}`)
|
||||
.setLabel(addonConfig.Review?.RejectLabel || "Reject")
|
||||
.setStyle(Discord.ButtonStyle.Danger)
|
||||
.setDisabled(locked)
|
||||
)
|
||||
);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
function getPendingApplication(data, guildId, userId, type) {
|
||||
return data.applications.find(
|
||||
(app) =>
|
||||
app.guildId === guildId &&
|
||||
app.userId === userId &&
|
||||
app.type === type &&
|
||||
app.status === "pending"
|
||||
);
|
||||
}
|
||||
|
||||
function getCooldownApplication(data, guildId, userId, type) {
|
||||
const cooldownMs =
|
||||
Number(addonConfig.Application?.CooldownHours || 24) * 60 * 60 * 1000;
|
||||
|
||||
return data.applications.find(
|
||||
(app) =>
|
||||
app.guildId === guildId &&
|
||||
app.userId === userId &&
|
||||
app.type === type &&
|
||||
now() - Number(app.createdAt || 0) < cooldownMs
|
||||
);
|
||||
}
|
||||
|
||||
function memberCanReview(member) {
|
||||
if (!member) return false;
|
||||
|
||||
if (member.permissions?.has(Discord.PermissionFlagsBits.ManageGuild)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function registerAddonInteractions(client) {
|
||||
if (client.__applicationSystemRegistered) return;
|
||||
client.__applicationSystemRegistered = true;
|
||||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
try {
|
||||
if (!interaction.guild) return;
|
||||
|
||||
// Application dropdown select
|
||||
if (
|
||||
interaction.isStringSelectMenu() &&
|
||||
interaction.customId === "appsystem_select"
|
||||
) {
|
||||
const typeKey = interaction.values[0];
|
||||
const applicationType = addonConfig.Applications?.[typeKey];
|
||||
|
||||
if (!applicationType) {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Invalid Application",
|
||||
"That application type does not exist.",
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const data = readData();
|
||||
|
||||
if (addonConfig.Application?.OneOpenApplicationPerUserPerType) {
|
||||
const existing = getPendingApplication(
|
||||
data,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
typeKey
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Application Already Open",
|
||||
`You already have a pending ${applicationType.Name} application.`,
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const cooldownHit = getCooldownApplication(
|
||||
data,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
typeKey
|
||||
);
|
||||
|
||||
if (cooldownHit && cooldownHit.status !== "pending") {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Please Wait",
|
||||
`You must wait ${addonConfig.Application?.CooldownHours || 24} hours before applying again for ${applicationType.Name}.`,
|
||||
0xfee75c
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const modal = buildApplicationModal(typeKey);
|
||||
|
||||
if (!modal) {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Modal Error",
|
||||
"The application form could not be created.",
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return interaction.showModal(modal);
|
||||
}
|
||||
|
||||
// Application modal submit
|
||||
if (
|
||||
interaction.isModalSubmit() &&
|
||||
interaction.customId.startsWith("appsystem_modal_")
|
||||
) {
|
||||
const typeKey = interaction.customId.replace("appsystem_modal_", "");
|
||||
const applicationType = addonConfig.Applications?.[typeKey];
|
||||
|
||||
if (!applicationType) {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Invalid Application",
|
||||
"That application type no longer exists.",
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const reviewChannel = interaction.guild.channels.cache.get(
|
||||
addonConfig.Application?.ReviewChannelId
|
||||
);
|
||||
|
||||
if (!reviewChannel) {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Configuration Error",
|
||||
"The review channel was not found. Please contact staff.",
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const data = readData();
|
||||
|
||||
if (addonConfig.Application?.OneOpenApplicationPerUserPerType) {
|
||||
const existing = getPendingApplication(
|
||||
data,
|
||||
interaction.guild.id,
|
||||
interaction.user.id,
|
||||
typeKey
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Application Already Open",
|
||||
`You already have a pending ${applicationType.Name} application.`,
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const answers = {};
|
||||
const questionEntries = Object.entries(applicationType.Questions || {}).slice(0, 5);
|
||||
|
||||
for (const [key] of questionEntries) {
|
||||
const inputKey = sanitizeQuestionKey(key);
|
||||
answers[inputKey] = interaction.fields.getTextInputValue(inputKey);
|
||||
}
|
||||
|
||||
const application = {
|
||||
id: createId(),
|
||||
type: typeKey,
|
||||
typeName: applicationType.Name || typeKey,
|
||||
guildId: interaction.guild.id,
|
||||
userId: interaction.user.id,
|
||||
userTag: interaction.user.tag || interaction.user.username,
|
||||
status: "pending",
|
||||
createdAt: now(),
|
||||
reviewerId: null,
|
||||
reviewedAt: null,
|
||||
reviewMessageId: null,
|
||||
answers,
|
||||
};
|
||||
|
||||
const reviewMessage = await reviewChannel.send({
|
||||
flags: Discord.MessageFlags.IsComponentsV2,
|
||||
components: [buildReviewContainer(application, null, false)],
|
||||
});
|
||||
|
||||
application.reviewMessageId = reviewMessage.id;
|
||||
data.applications.push(application);
|
||||
writeData(data);
|
||||
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Application Sent",
|
||||
`Your ${applicationType.Name} application has been submitted successfully.`,
|
||||
0x57f287
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Review buttons
|
||||
if (
|
||||
interaction.isButton() &&
|
||||
(interaction.customId.startsWith("appsystem_accept_") ||
|
||||
interaction.customId.startsWith("appsystem_reject_"))
|
||||
) {
|
||||
if (!memberCanReview(interaction.member)) {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"No Permission",
|
||||
"You do not have permission to review applications.",
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const isAccept = interaction.customId.startsWith("appsystem_accept_");
|
||||
const applicationId = interaction.customId.replace(
|
||||
isAccept ? "appsystem_accept_" : "appsystem_reject_",
|
||||
""
|
||||
);
|
||||
|
||||
const data = readData();
|
||||
const application = data.applications.find((app) => app.id === applicationId);
|
||||
|
||||
if (!application) {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Not Found",
|
||||
"This application no longer exists in storage.",
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (application.status !== "pending") {
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Already Reviewed",
|
||||
`This application has already been ${application.status}.`,
|
||||
0xfee75c
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
application.status = isAccept ? "accepted" : "rejected";
|
||||
application.reviewerId = interaction.user.id;
|
||||
application.reviewedAt = now();
|
||||
writeData(data);
|
||||
|
||||
const reviewerText = [
|
||||
`### Review Result`,
|
||||
`**Decision:** ${isAccept ? "Accepted" : "Rejected"}`,
|
||||
`**Reviewer:** <@${interaction.user.id}>`,
|
||||
`**Reviewed At:** <t:${Math.floor(application.reviewedAt / 1000)}:F>`,
|
||||
].join("\n");
|
||||
|
||||
await interaction.update({
|
||||
flags: Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildReviewContainer(application, reviewerText, true),
|
||||
],
|
||||
});
|
||||
|
||||
const applicationType = addonConfig.Applications?.[application.type];
|
||||
|
||||
if (isAccept && applicationType?.AcceptedRoleId) {
|
||||
const member = await interaction.guild.members
|
||||
.fetch(application.userId)
|
||||
.catch(() => null);
|
||||
|
||||
if (member) {
|
||||
await member.roles.add(applicationType.AcceptedRoleId).catch(() => null);
|
||||
}
|
||||
}
|
||||
|
||||
const applicantUser = await interaction.client.users
|
||||
.fetch(application.userId)
|
||||
.catch(() => null);
|
||||
|
||||
if (applicantUser) {
|
||||
await applicantUser
|
||||
.send({
|
||||
flags: Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
isAccept ? "Application Accepted" : "Application Rejected",
|
||||
isAccept
|
||||
? `Congratulations! Your ${application.typeName} application in **${interaction.guild.name}** has been accepted.`
|
||||
: `Your ${application.typeName} application in **${interaction.guild.name}** has been rejected.`,
|
||||
isAccept ? 0x57f287 : 0xed4245
|
||||
),
|
||||
],
|
||||
})
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[ApplicationSystem] Interaction error:", error);
|
||||
|
||||
if (interaction.deferred || interaction.replied) {
|
||||
return;
|
||||
}
|
||||
|
||||
return interaction
|
||||
.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Error",
|
||||
"Something went wrong while handling that application interaction.",
|
||||
0xed4245
|
||||
),
|
||||
],
|
||||
})
|
||||
.catch(() => null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = new Command({
|
||||
name: addonConfig.Command?.Name || "applicationsetup",
|
||||
description:
|
||||
addonConfig.Command?.Description || "Posts the application panel",
|
||||
options: [],
|
||||
permissions: addonConfig.Command?.Permission || ["ManageGuild"],
|
||||
|
||||
async run(interaction) {
|
||||
registerAddonInteractions(interaction.client);
|
||||
|
||||
await interaction.channel.send({
|
||||
flags: Discord.MessageFlags.IsComponentsV2,
|
||||
components: [buildPanelContainer()],
|
||||
});
|
||||
|
||||
return interaction.reply({
|
||||
flags:
|
||||
Discord.MessageFlags.Ephemeral |
|
||||
Discord.MessageFlags.IsComponentsV2,
|
||||
components: [
|
||||
buildNoticeContainer(
|
||||
"Application Panel Posted",
|
||||
"The application panel has been posted in this channel.",
|
||||
0x57f287
|
||||
),
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
const Discord = require("discord.js");
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
const { Command } = require('../Structures/Addon.js'); // Adjust the path as needed
|
||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
|
||||
module.exports = new Command({
|
||||
name: "calculator",
|
||||
description: "Use a calculator to solve your math problems.",
|
||||
options: [],
|
||||
permissions: ["SendMessages"],
|
||||
|
||||
async run(interaction) {
|
||||
let expression = "";
|
||||
let history = "";
|
||||
|
||||
const createButton = (label, customId) => {
|
||||
return new Discord.ButtonBuilder()
|
||||
.setLabel(label)
|
||||
.setCustomId(customId)
|
||||
.setStyle(Discord.ButtonStyle.Secondary);
|
||||
};
|
||||
|
||||
const createRow = (buttons) => {
|
||||
return new Discord.ActionRowBuilder().addComponents(buttons);
|
||||
};
|
||||
|
||||
const rows = [
|
||||
createRow([createButton('7', '7'), createButton('8', '8'), createButton('9', '9'), createButton('/', '/')]),
|
||||
createRow([createButton('4', '4'), createButton('5', '5'), createButton('6', '6'), createButton('*', '*')]),
|
||||
createRow([createButton('1', '1'), createButton('2', '2'), createButton('3', '3'), createButton('-', '-')]),
|
||||
createRow([createButton('0', '0'), createButton('.', '.'), createButton('=', '='), createButton('+', '+')]),
|
||||
createRow([createButton('Clear', 'clear')])
|
||||
];
|
||||
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setTitle('Calculator')
|
||||
.setDescription('Calculator')
|
||||
.setColor(supportbot.Embed.Colours.General);
|
||||
|
||||
const message = await interaction.reply({
|
||||
embeds: [embed],
|
||||
components: rows,
|
||||
fetchReply: true
|
||||
});
|
||||
|
||||
const filter = i => i.user.id === interaction.user.id;
|
||||
const collector = message.createMessageComponentCollector({ filter, time: 60000 });
|
||||
|
||||
collector.on('collect', async i => {
|
||||
if (i.customId === 'clear') {
|
||||
expression = "";
|
||||
history = "";
|
||||
} else if (i.customId === '=') {
|
||||
try {
|
||||
const result = eval(expression).toString();
|
||||
history += `${expression} = ${result}\n`;
|
||||
expression = result;
|
||||
} catch {
|
||||
expression = "Error";
|
||||
}
|
||||
} else {
|
||||
if (history.endsWith(`${expression} = ${expression}\n`)) {
|
||||
history += `\n`;
|
||||
expression = i.customId;
|
||||
} else {
|
||||
expression += i.customId;
|
||||
}
|
||||
}
|
||||
|
||||
await i.update({
|
||||
embeds: [new Discord.EmbedBuilder().setTitle('Calculator').setDescription(`\`\`\`${history}${expression}\`\`\``).setColor(supportbot.Embed.Colours.General)],
|
||||
components: rows
|
||||
});
|
||||
});
|
||||
|
||||
collector.on('end', collected => {
|
||||
message.edit({
|
||||
components: []
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,139 +0,0 @@
|
||||
// SupportBot | Emerald Services
|
||||
// Discord Stats addon | Created by Xiled Emerald
|
||||
|
||||
const fs = require("fs");
|
||||
|
||||
const Discord = require("discord.js");
|
||||
const yaml = require("js-yaml");
|
||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
|
||||
// If you need to read something from the main config!
|
||||
const cmdconfig = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
|
||||
// This is for your Addon Configuration
|
||||
const config = yaml.load(fs.readFileSync('./Addons/Configs/discord-stats.yml', 'utf8'));
|
||||
|
||||
const { Command, Event } = require("../Structures/Addon");
|
||||
|
||||
module.exports.commands = [
|
||||
|
||||
new Command({
|
||||
name: "stats",
|
||||
description: "Create the discord statistic channels",
|
||||
options: [],
|
||||
permissions: ["SEND_MESSAGES"],
|
||||
async run(interaction) {
|
||||
const guild = interaction.guild;
|
||||
const statsCategory = guild.channels.cache.find(
|
||||
(category) =>
|
||||
category.name === config.StatsCategory ||
|
||||
category.id === config.StatsCategory
|
||||
);
|
||||
|
||||
if (!statsCategory) {
|
||||
const errorEmbed = new Discord.MessageEmbed()
|
||||
.setTitle("Invalid Category")
|
||||
.setDescription(
|
||||
`${supportbot.InvalidChannel}\n\n**Category Required:** \`${config.StatsCategory}\``
|
||||
)
|
||||
.setColor(supportbot.ErrorColour);
|
||||
|
||||
interaction.reply({
|
||||
embeds: [errorEmbed],
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const guildName = guild.name;
|
||||
const userCount = guild.memberCount;
|
||||
const roleCount = guild.roles.cache.size;
|
||||
const channelCount = guild.channels.cache.size;
|
||||
|
||||
try {
|
||||
const guildNameChannel = await guild.channels.create(guildName, {
|
||||
type: "GUILD_VOICE",
|
||||
});
|
||||
|
||||
const userCountChannel = await guild.channels.create(
|
||||
`${config.UserCount_Name} ${userCount}`,
|
||||
{
|
||||
type: "GUILD_VOICE",
|
||||
}
|
||||
);
|
||||
|
||||
const channelCountChannel = await guild.channels.create(
|
||||
`${config.ChannelCount_Name} ${channelCount}`,
|
||||
{
|
||||
type: "GUILD_VOICE",
|
||||
}
|
||||
);
|
||||
|
||||
const roleCountChannel = await guild.channels.create(
|
||||
`${config.RoleCount_Name} ${roleCount}`,
|
||||
{
|
||||
type: "GUILD_VOICE",
|
||||
}
|
||||
);
|
||||
|
||||
guildNameChannel.permissionOverwrites.edit(guild.id, {
|
||||
VIEW_CHANNEL: true,
|
||||
CONNECT: false,
|
||||
});
|
||||
|
||||
userCountChannel.permissionOverwrites.edit(guild.id, {
|
||||
VIEW_CHANNEL: true,
|
||||
CONNECT: false,
|
||||
});
|
||||
|
||||
channelCountChannel.permissionOverwrites.edit(guild.id, {
|
||||
VIEW_CHANNEL: true,
|
||||
CONNECT: false,
|
||||
});
|
||||
|
||||
roleCountChannel.permissionOverwrites.edit(guild.id, {
|
||||
VIEW_CHANNEL: true,
|
||||
CONNECT: false,
|
||||
});
|
||||
|
||||
userCountChannel.setParent(statsCategory.id);
|
||||
roleCountChannel.setParent(statsCategory.id);
|
||||
guildNameChannel.setParent(statsCategory.id);
|
||||
channelCountChannel.setParent(statsCategory.id);
|
||||
|
||||
const successEmbed = new Discord.MessageEmbed()
|
||||
.setDescription(":white_check_mark: **[DISCORD STATS]** Channels created successfully!")
|
||||
.setColor(supportbot.EmbedColour);
|
||||
|
||||
interaction.reply({
|
||||
embeds: [successEmbed],
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
userCountChannel.setName(
|
||||
`${config.UserCount_Name} ${userCount}`
|
||||
);
|
||||
channelCountChannel.setName(
|
||||
`${config.ChannelCount_Name} ${channelCount}`
|
||||
);
|
||||
roleCountChannel.setName(
|
||||
`${config.RoleCount_Name} ${roleCount}`
|
||||
);
|
||||
}, 30000);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const errorEmbed = new Discord.MessageEmbed()
|
||||
.setTitle("Error")
|
||||
.setDescription("An error occurred while creating the stats channels.")
|
||||
.setColor(supportbot.ErrorColour);
|
||||
|
||||
interaction.reply({
|
||||
embeds: [errorEmbed],
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
];
|
||||
@@ -1,292 +0,0 @@
|
||||
const Discord = require("discord.js");
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
const fetch = require("node-fetch");
|
||||
const { Command } = require('../Structures/Addon.js');
|
||||
|
||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||
const vouchleyaddon = yaml.load(fs.readFileSync("./Addons/Configs/vouchley.yml", "utf8"));
|
||||
|
||||
function formatURL(url) {
|
||||
if (!url) return 'https://www.vouchley.com';
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
return `https://${url}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function formatDate(timestamp) {
|
||||
return `<t:${Math.floor(new Date(timestamp).getTime() / 1000)}:D>`;
|
||||
}
|
||||
|
||||
function createReviewEmbed(review, username, currentPage, totalReviews) {
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setTitle(`Review for ${username}`)
|
||||
.setColor(vouchleyaddon.Embed.Colour);
|
||||
|
||||
const fields = [
|
||||
{
|
||||
name: "Rating",
|
||||
value: "⭐".repeat(Math.min(Math.max(review.rating, 1), 5)) || "No rating",
|
||||
inline: false
|
||||
},
|
||||
{
|
||||
name: "Platform",
|
||||
value: review.platform?.toString() || "Not specified",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Date",
|
||||
value: formatDate(review.time_sent),
|
||||
inline: true
|
||||
}
|
||||
];
|
||||
|
||||
if (review.message && review.message.trim()) {
|
||||
fields.push({
|
||||
name: "Review",
|
||||
value: `\`\`\`${review.message.substring(0, 1024)}\`\`\``,
|
||||
inline: false
|
||||
});
|
||||
}
|
||||
|
||||
embed.addFields(fields);
|
||||
embed.setFooter({ text: `Review ${currentPage + 1} of ${totalReviews}` });
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
function createReviewNavigation(currentPage, totalReviews) {
|
||||
return new Discord.ActionRowBuilder()
|
||||
.addComponents(
|
||||
new Discord.ButtonBuilder()
|
||||
.setCustomId('prev_review')
|
||||
.setLabel('Previous')
|
||||
.setStyle(Discord.ButtonStyle.Primary)
|
||||
.setDisabled(currentPage === 0),
|
||||
new Discord.ButtonBuilder()
|
||||
.setCustomId('next_review')
|
||||
.setLabel('Next')
|
||||
.setStyle(Discord.ButtonStyle.Primary)
|
||||
.setDisabled(currentPage === totalReviews - 1),
|
||||
new Discord.ButtonBuilder()
|
||||
.setCustomId('back_to_profile')
|
||||
.setLabel('Back to Profile')
|
||||
.setStyle(Discord.ButtonStyle.Secondary)
|
||||
);
|
||||
}
|
||||
|
||||
const activeReviews = new Map();
|
||||
|
||||
module.exports = new Command({
|
||||
name: vouchleyaddon.Command.Name,
|
||||
description: vouchleyaddon.Command.Description,
|
||||
options: [
|
||||
{
|
||||
name: "search_type",
|
||||
description: "How do you want to search for the user?",
|
||||
type: 3,
|
||||
required: true,
|
||||
choices: [
|
||||
{ name: "Vouchley ID", value: "id" },
|
||||
{ name: "Vouchley Username", value: "username" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "query",
|
||||
description: "Enter the ID, username, or Discord ID to search",
|
||||
type: 3,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
permissions: vouchleyaddon.Command.Permissions,
|
||||
|
||||
async run(interaction) {
|
||||
const searchType = interaction.options.getString("search_type");
|
||||
const query = interaction.options.getString("query");
|
||||
|
||||
try {
|
||||
let apiUrl;
|
||||
switch (searchType) {
|
||||
case "id":
|
||||
apiUrl = `https://www.vouchley.com/api/v1/user?id=${query}`;
|
||||
break;
|
||||
case "username":
|
||||
apiUrl = `https://www.vouchley.com/api/v1/user?username=${query}`;
|
||||
break;
|
||||
}
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${vouchleyaddon.Vouchley.API_Key}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || (data.error && data.error.code !== 200)) {
|
||||
return interaction.reply({
|
||||
content: `Error: ${data.error?.message || 'Failed to fetch user data'}`,
|
||||
ephemeral: vouchleyaddon.Embed.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
const user = data.user;
|
||||
|
||||
const profileEmbed = new Discord.EmbedBuilder()
|
||||
.setTitle(`${user.username} - ${String(user.title || "N/A")}`)
|
||||
.setColor(vouchleyaddon.Embed.Colour)
|
||||
.setThumbnail(user.avatar_url)
|
||||
.addFields([
|
||||
{
|
||||
name: "Discord",
|
||||
value: `<@${String(user.discord_id)}> (\`${String(user.discord_id)}\`)`,
|
||||
inline: false
|
||||
},
|
||||
{
|
||||
name: "Average Rating",
|
||||
value: `${String(user.average_rating)} (\`${String(user.reviews.length)} Reviews\`)`,
|
||||
inline: false
|
||||
}
|
||||
]);
|
||||
|
||||
const profileButtons = [
|
||||
new Discord.ButtonBuilder()
|
||||
.setLabel(`Review ${user.username}`)
|
||||
.setURL(`https://www.vouchley.com/review?user=${user.username}`)
|
||||
.setStyle(Discord.ButtonStyle.Link)
|
||||
];
|
||||
|
||||
if (user.website) {
|
||||
profileButtons.push(
|
||||
new Discord.ButtonBuilder()
|
||||
.setLabel('Visit Website')
|
||||
.setURL(formatURL(user.website))
|
||||
.setStyle(Discord.ButtonStyle.Link)
|
||||
);
|
||||
}
|
||||
|
||||
if (user.reviews && user.reviews.length > 0) {
|
||||
profileButtons.push(
|
||||
new Discord.ButtonBuilder()
|
||||
.setCustomId('seereviews')
|
||||
.setLabel('See Reviews')
|
||||
.setEmoji("⭐")
|
||||
.setStyle(Discord.ButtonStyle.Secondary)
|
||||
);
|
||||
}
|
||||
|
||||
const profileRow = new Discord.ActionRowBuilder()
|
||||
.addComponents(profileButtons);
|
||||
|
||||
const message = await interaction.reply({
|
||||
embeds: [profileEmbed],
|
||||
components: [profileRow],
|
||||
fetchReply: true,
|
||||
ephemeral: vouchleyaddon.Embed.Ephemeral
|
||||
});
|
||||
|
||||
if (user.reviews && user.reviews.length > 0) {
|
||||
activeReviews.set(message.id, {
|
||||
reviews: user.reviews,
|
||||
currentPage: 0,
|
||||
username: user.username,
|
||||
profileEmbed,
|
||||
profileRow
|
||||
});
|
||||
|
||||
const collector = message.createMessageComponentCollector({
|
||||
time: 600000
|
||||
});
|
||||
|
||||
collector.on('collect', async i => {
|
||||
const reviewData = activeReviews.get(message.id);
|
||||
if (!reviewData) return;
|
||||
|
||||
switch (i.customId) {
|
||||
case 'seereviews': {
|
||||
const review = reviewData.reviews[reviewData.currentPage];
|
||||
const reviewEmbed = createReviewEmbed(
|
||||
review,
|
||||
reviewData.username,
|
||||
reviewData.currentPage,
|
||||
reviewData.reviews.length
|
||||
);
|
||||
const reviewNav = createReviewNavigation(
|
||||
reviewData.currentPage,
|
||||
reviewData.reviews.length
|
||||
);
|
||||
await i.update({
|
||||
embeds: [reviewEmbed],
|
||||
components: [reviewNav],
|
||||
ephemeral: vouchleyaddon.Embed.Ephemeral
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'prev_review': {
|
||||
if (reviewData.currentPage > 0) {
|
||||
reviewData.currentPage--;
|
||||
const review = reviewData.reviews[reviewData.currentPage];
|
||||
const reviewEmbed = createReviewEmbed(
|
||||
review,
|
||||
reviewData.username,
|
||||
reviewData.currentPage,
|
||||
reviewData.reviews.length
|
||||
);
|
||||
const reviewNav = createReviewNavigation(
|
||||
reviewData.currentPage,
|
||||
reviewData.reviews.length
|
||||
);
|
||||
await i.update({
|
||||
embeds: [reviewEmbed],
|
||||
components: [reviewNav],
|
||||
ephemeral: vouchleyaddon.Embed.Ephemeral
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'next_review': {
|
||||
if (reviewData.currentPage < reviewData.reviews.length - 1) {
|
||||
reviewData.currentPage++;
|
||||
const review = reviewData.reviews[reviewData.currentPage];
|
||||
const reviewEmbed = createReviewEmbed(
|
||||
review,
|
||||
reviewData.username,
|
||||
reviewData.currentPage,
|
||||
reviewData.reviews.length
|
||||
);
|
||||
const reviewNav = createReviewNavigation(
|
||||
reviewData.currentPage,
|
||||
reviewData.reviews.length
|
||||
);
|
||||
await i.update({ embeds: [reviewEmbed], components: [reviewNav] });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'back_to_profile': {
|
||||
await i.update({
|
||||
embeds: [reviewData.profileEmbed],
|
||||
components: [reviewData.profileRow]
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
collector.on('end', () => {
|
||||
activeReviews.delete(message.id);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (!interaction.replied) {
|
||||
await interaction.reply({
|
||||
content: "There was an error fetching Vouchley user data.",
|
||||
ephemeral: vouchleyaddon.Embed.Ephemeral
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user