Compare commits

...

3 Commits

Author SHA1 Message Date
Ryahn
2d0a6626a8 Refactor done 2025-05-12 15:20:40 -05:00
Ryahn
88d985bb1f Added additional config to commands 2025-05-12 15:10:29 -05:00
Ryahn
bc39fe3f7a Updated gaydar and refactor 2025-05-12 15:08:23 -05:00
37 changed files with 1908 additions and 841 deletions

BIN
assets/gaydar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -7,6 +7,8 @@ module.exports = {
cooldown: 5000, cooldown: 5000,
available: true, available: true,
permissions: [], permissions: [],
roles: [],
dm: false,
aliases: ['av'] aliases: ['av']
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {

View File

@ -1,5 +1,7 @@
const fs = require("fs");
const path = require("path");
const Embed = require("../functions/embed") const Embed = require("../functions/embed")
// const { } = require('revolt.js') const Uploader = require("revolt-uploader");
module.exports = { module.exports = {
config: { config: {
@ -8,33 +10,90 @@ module.exports = {
cooldown: 5000, cooldown: 5000,
available: true, available: true,
permissions: [], permissions: [],
roles: [],
dm: false,
aliases: ['gd'] aliases: ['gd']
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {
function randomInteger(min, max) { try {
min = Math.ceil(min);
max = Math.floor(max); function randomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min; min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let targetUser;
// Check if there's a mentioned user
if (message.mentionIds && message.mentionIds.length > 0) {
try {
const server = await client.servers.fetch(message.server.id);
const member = await server.fetchMember(message.mentionIds[0]);
if (!member || !member.id || !member.id.user) {
throw new Error("Invalid member structure");
}
// Fetch the user using the user ID from the member object
targetUser = await client.users.fetch(member.id.user);
if (!targetUser || !targetUser.username) {
throw new Error("Could not fetch user information");
}
} catch (fetchError) {
console.error("Fetch error details:", fetchError.message);
return message.reply({
embeds: [new Embed()
.setDescription("Could not find the mentioned user.")
.setColor(`#FF0000`)]
}, false);
}
} else {
// Use the command author if no user is mentioned
targetUser = message.author;
}
if (!targetUser) {
return message.reply({
embeds: [new Embed()
.setDescription("Could not determine the target user.")
.setColor(`#FF0000`)]
}, false);
}
const amount = randomInteger(1, 100);
const imagePath = path.join(__dirname, "../assets/gaydar.jpg");
// Check if file exists
if (!fs.existsSync(imagePath)) {
return message.reply({
embeds: [new Embed()
.setDescription("Error: Required image file not found.")
.setColor(`#FF0000`)]
}, false);
}
// Upload the image as an attachment
const attachment = await client.Uploader.uploadFile(imagePath, "gaydar.jpg");
// Send the message with the attachment
await message.channel.sendMessage({
content: `#### ${targetUser.username} is ${amount}% gay.`,
attachments: [attachment]
});
} catch (error) {
console.log(`${Date(Date.now().toString()).slice(0, 25)}`);
console.log("User: " + message.author.username + ` [${message.authorId}] ` + " | Command: gaydar | Args: " + (args?.join(" ") || "NONE"));
console.log(error.message);
return message.reply({
embeds: [new Embed()
.setDescription("An error occurred while processing the command.")
.setColor(`#FF0000`)]
}, false);
} }
const member = await (client.servers.get(message.server.id) || await client.servers.fetch(message.server.id))?.fetchMember(message.mentionIds[0]).then((user) => {
return console.log();
});
// const target = member.user.username;
// return console.log(member)
// let amount = randomInteger(1, 100);
// const embed = {
// description: `#### ${target} is ${amount}% gay.`,
// colour: "#ff8080",
// image: 'https://overlord.lordainz.xyz/f/gaydar.jpg'
// }
// await message.channel.sendMessage({ content: "", embeds: [embed] }).catch(err => {
// console.log(`${Date(Date.now().toString()).slice(0, 25)}`);
// console.log("User: " + message.author.username + ` [${message.authorId}] ` + " | Command: gardar | Args: " + (args?.join(" ") || "NONE"))
// console.log(err.message);
// return;
// });
}, },
}; };

View File

@ -9,6 +9,8 @@ module.exports = {
cooldown: 5000, cooldown: 5000,
available: true, available: true,
permissions: [], permissions: [],
roles: [],
dm: false,
aliases: ['tenor', 'giphy', 'gify'] aliases: ['tenor', 'giphy', 'gify']
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {

View File

@ -7,6 +7,8 @@ module.exports = {
cooldown: 5000, cooldown: 5000,
available: true, available: true,
permissions: [], permissions: [],
roles: [],
dm: false,
aliases: ['h'] aliases: ['h']
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {

View File

@ -9,6 +9,8 @@ module.exports = {
cooldown: 10000, cooldown: 10000,
available: true, available: true,
permissions: [], permissions: [],
roles: [],
dm: false,
aliases: ["stats"] aliases: ["stats"]
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {

View File

@ -11,6 +11,8 @@ module.exports = {
cooldown: 10000, cooldown: 10000,
available: true, available: true,
permissions: ['ManageServer'], permissions: ['ManageServer'],
roles: [],
dm: false,
aliases: ["poll"] aliases: ["poll"]
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {

View File

@ -7,7 +7,9 @@ module.exports = {
available: true, available: true,
usage: true, usage: true,
permissions: ["ManageServer"], permissions: ["ManageServer"],
aliases: [] aliases: [],
roles: [],
dm: false,
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {
const embed = new Embed() const embed = new Embed()

View File

@ -5,7 +5,9 @@ module.exports = {
cooldown: 0, cooldown: 0,
available: "Owner", available: "Owner",
permissions: [], permissions: [],
aliases: ["r"] aliases: ["r"],
roles: [],
dm: false,
}, },
run: async (client, message, args) => { run: async (client, message, args) => {
if (!client.config.owners.includes(message.authorId)) return; if (!client.config.owners.includes(message.authorId)) return;

View File

@ -11,6 +11,8 @@ module.exports = {
cooldown: 10000, cooldown: 10000,
available: true, available: true,
permissions: ['ManageServer'], permissions: ['ManageServer'],
roles: [],
dm: false,
aliases: ["r7"] aliases: ["r7"]
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {

View File

@ -8,7 +8,9 @@ module.exports = {
cooldown: 5000, cooldown: 5000,
available: true, available: true,
permissions: [], permissions: [],
aliases: ['ub'] aliases: ['ub'],
roles: [],
dm: false,
}, },
run: async (client, message, args, db) => { run: async (client, message, args, db) => {
let query = args.join(' '); let query = args.join(' ');

View File

@ -1,10 +1,9 @@
const Embed = require("../functions/embed"); const path = require("path");
const Collector = require("../functions/messageCollector"); const Embed = require(path.join(__dirname, "../functions/embed"));
const EditCollector = require("../functions/messageEdit"); const Collector = require(path.join(__dirname, "../functions/messageCollector"));
const CommandDB = require('../models/commands'); const EditCollector = require(path.join(__dirname, "../functions/messageEdit"));
const { isJson } = require('../functions/randomStr'); const CommandDB = require(path.join(__dirname, "../models/commands"));
const logger = require('../functions/logger'); const { isJson } = require(path.join(__dirname, "../functions/randomStr"));
const audit = require('../functions/audit');
module.exports = async (client, message) => { module.exports = async (client, message) => {
// Early return checks // Early return checks

View File

@ -1,6 +1,7 @@
const PollDB = require("../models/polls"); const path = require("path");
const Giveaways = require("../models/giveaways"); const PollDB = require(path.join(__dirname, "../models/polls"));
const GuildDB = require("../models/guilds"); const Giveaways = require(path.join(__dirname, "../models/giveaways"));
const GuildDB = require(path.join(__dirname, "../models/guilds"));
module.exports = async (client, msg) => { module.exports = async (client, msg) => {
const paginateCheck = client.paginate.get(msg.authorId); const paginateCheck = client.paginate.get(msg.authorId);
const pollCheck = client.polls.get(msg.id); const pollCheck = client.polls.get(msg.id);

View File

@ -1,220 +1,362 @@
const Embed = require("../functions/embed"); const path = require("path");
const PollDB = require("../models/polls"); const Embed = require(path.join(__dirname, "../functions/embed"));
const Giveaways = require("../models/giveaways"); const PollDB = require(path.join(__dirname, "../models/polls"));
const emojis = [{ name: "1⃣", id: 0 }, { name: "2⃣", id: 1 }, { name: "3⃣", id: 2 }, { name: "4⃣", id: 3 }, { name: "5⃣", id: 4 }, { name: "6⃣", id: 5 }, { name: "7⃣", id: 6 }, { name: "8⃣", id: 7 }, { name: "9⃣", id: 8 }, { name: "🔟", id: 9 }, { name: "🛑", id: "stop" }] const Giveaways = require(path.join(__dirname, "../models/giveaways"));
const colors = /^([A-Z0-9]+)/;
module.exports = async (client, message, userId, emojiId) => { // Constants
const paginateCheck = client.paginate.get(userId); const EMOJIS = [
const pollCheck = client.polls.get(message.id); { name: "1⃣", id: 0 }, { name: "2⃣", id: 1 }, { name: "3⃣", id: 2 },
const collector = client.messageCollector.get(userId); { name: "4⃣", id: 3 }, { name: "5⃣", id: 4 }, { name: "6⃣", id: 5 },
const editCollector = client.messageEdit.get(userId); { name: "7⃣", id: 6 }, { name: "8⃣", id: 7 }, { name: "9⃣", id: 8 },
{ name: "🔟", id: 9 }, { name: "🛑", id: "stop" }
];
const COLORS_REGEX = /^([A-Z0-9]+)/;
if (collector && collector.messageId === message.id || collector?.oldMessageId && collector?.oldMessageId === message.id && collector.channelId === message.channelId) { /**
if (emojiId === client.config.emojis.check) { * Handles message collector reactions
if (collector.roles.length === 0) { * @param {Object} client - The client instance
const db = await client.database.getGuild(message.server.id); * @param {Object} message - The message object
message.delete().catch(() => { }); * @param {string} userId - The user ID
client.messages.get(collector?.oldMessageId)?.delete().catch(() => { }) * @param {string} emojiId - The emoji ID
const reactions = [...collector.rolesDone.map(e => e.emoji)]; * @param {Object} collector - The message collector
message.channel.sendMessage(collector.type === "content" ? { content: `${message.content}\n\n##### ${client.translate.get(db.language, "Events.messageReactionAdd.cooldown")}`, interactions: [reactions] } : { embeds: [new Embed().setColor("#A52F05").setDescription(`${client.messages.get(message.id).embeds[0].description}\n\n##### ${client.translate.get(db.language, "Events.messageReactionAdd.cooldown")}`)], interactions: [reactions] }).then(async (msg) => { * @returns {Promise<void>}
db.roles.push({ msgId: msg.id, chanId: msg.channelId, roles: [...collector.rolesDone] }); */
await client.database.updateGuild(msg.server.id, { roles: db.roles }); async function handleMessageCollector(client, message, userId, emojiId, collector) {
}); if (emojiId === client.config.emojis.check) {
if (collector.roles.length === 0) {
clearTimeout(client.messageCollector.get(userId).timeout);
return client.messageCollector.delete(userId);
} else return;
} else if (emojiId === client.config.emojis.cross) {
const db = await client.database.getGuild(message.server.id); const db = await client.database.getGuild(message.server.id);
client.messageCollector.delete(userId); await Promise.all([
return message.reply({ embeds: [new Embed().setColor("#A52F05").setDescription(client.translate.get(db.language, "Events.messageReactionAdd.deleteCollector"))] },); message.delete().catch(() => {}),
} else { client.messages.get(collector?.oldMessageId)?.delete().catch(() => {})
if (collector.roles.length === 0) return; ]);
let emote;
if (colors.test(emojiId)) emote = `:${emojiId}:`;
else if (!colors.test(emojiId)) emote = emojiId
collector.rolesDone.push({ emoji: emojiId, role: collector.roles[0][0], name: collector.roles[0][1].name, color: collector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : collector.roles[0][1].colour });
message.edit(collector.type === "content" ? { content: message.content.replace(`{role:${collector.regex[0]}}`, `${emote} $\\text{\\textcolor{${collector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : collector.roles[0][1].colour}}{${collector.roles[0][1].name}}}$`) } : { embeds: [new Embed().setColor("#A52F05").setDescription(client.messages.get(message.id).embeds[0].description.replace(`{role:${collector.regex[0]}}`, `:${emojiId}: $\\text{\\textcolor{${collector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : collector.roles[0][1].colour}}{${collector.roles[0][1].name}}}$`))] })
collector.roles.shift();
return collector.regex.shift();
}
} else if (editCollector && editCollector.messageId === message.id || editCollector?.botMessage && editCollector?.botMessage === message.id && editCollector.channelId === message.channelId) {
if (emojiId === client.config.emojis.check) {
if (editCollector.roles.length === 0) {
const db = await client.database.getGuild(message.server.id);
message.delete().catch(() => { });
client.messages.get(editCollector?.oldMessageId)?.delete().catch(() => { })
client.messages.get(editCollector?.botMessage)?.delete().catch(() => { })
const reactions = [...editCollector.rolesDone.map(e => e.emoji)];
message.channel.sendMessage(editCollector.type === "content" ? { content: `${message.content}\n\n##### ${client.translate.get(db.language, "Events.messageReactionAdd.cooldown")}`, interactions: [reactions] } : { embeds: [new Embed().setColor("#A52F05").setDescription(`${client.messages.get(message.id).embeds[0].description}\n\n##### ${client.translate.get(db.language, "Events.messageReactionAdd.cooldown")}`)], interactions: [reactions] }).then(async (msg) => {
db.roles.push({ msgId: msg.id, chanId: msg.channelId, roles: [...editCollector.rolesDone] });
await client.database.updateGuild(msg.server.id, { roles: db.roles.filter(e => e.msgId !== editCollector.oldMessageId) });
});
clearTimeout(client.messageEdit.get(userId).timeout); const reactions = [...collector.rolesDone.map(e => e.emoji)];
return client.messageEdit.delete(userId); const content = collector.type === "content"
} else return; ? { content: `${message.content}\n\n##### ${client.translate.get(db.language, "Events.messageReactionAdd.cooldown")}`, interactions: [reactions] }
} else if (emojiId === client.config.emojis.cross) { : { embeds: [new Embed().setColor("#A52F05").setDescription(`${client.messages.get(message.id).embeds[0].description}\n\n##### ${client.translate.get(db.language, "Events.messageReactionAdd.cooldown")}`)], interactions: [reactions] };
const db = await client.database.getGuild(message.server.id);
client.messageEdit.delete(userId); const msg = await message.channel.sendMessage(content);
return message.reply({ embeds: [new Embed().setColor("#A52F05").setDescription(client.translate.get(db.language, "Events.messageReactionAdd.deleteCollector"))] },); db.roles.push({ msgId: msg.id, chanId: msg.channelId, roles: [...collector.rolesDone] });
} else { await client.database.updateGuild(msg.server.id, { roles: db.roles });
if (editCollector.roles.length === 0) return;
let emote; clearTimeout(client.messageCollector.get(userId).timeout);
if (colors.test(emojiId)) emote = `:${emojiId}:`; return client.messageCollector.delete(userId);
else if (!colors.test(emojiId)) emote = emojiId
editCollector.rolesDone.push({ emoji: emojiId, role: editCollector.roles[0][0], name: editCollector.roles[0][1].name, color: editCollector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : editCollector.roles[0][1].colour });
message.edit(editCollector.type === "content" ? { content: message.content.replace(`{role:${editCollector.regex[0]}}`, `${emote} $\\text{\\textcolor{${editCollector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : editCollector.roles[0][1].colour}}{${editCollector.roles[0][1].name}}}$`) } : { embeds: [new Embed().setColor("#A52F05").setDescription(client.messages.get(message.id).embeds[0].description.replace(`{role:${editCollector.regex[0]}}`, `:${emojiId}: $\\text{\\textcolor{${editCollector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : editCollector.roles[0][1].colour}}{${editCollector.roles[0][1].name}}}$`))] })
editCollector.roles.shift();
return editCollector.regex.shift();
} }
} else if (paginateCheck && paginateCheck.message == message.id) { return;
let pages = paginateCheck.pages; }
let page = paginateCheck.page;
switch (emojiId) { if (emojiId === client.config.emojis.cross) {
case "⏪": const db = await client.database.getGuild(message.server.id);
if (page !== 0) { client.messageCollector.delete(userId);
message.edit({ return message.reply({
embeds: [pages[0]] embeds: [new Embed().setColor("#A52F05").setDescription(client.translate.get(db.language, "Events.messageReactionAdd.deleteCollector"))]
}).catch(() => { }); });
return paginateCheck.page = 0 }
} else {
return; if (collector.roles.length === 0) return;
}
case "⬅️": const emote = COLORS_REGEX.test(emojiId) ? `:${emojiId}:` : emojiId;
if (pages[page - 1]) { const role = collector.roles[0];
message.edit({ const roleColor = role[1].colour?.includes("linear-gradient") ? '#000000' : role[1].colour;
embeds: [pages[--page]]
}).catch(() => { }); collector.rolesDone.push({
return paginateCheck.page = paginateCheck.page - 1 emoji: emojiId,
} else { role: role[0],
return; name: role[1].name,
} color: roleColor
case "➡️": });
if (pages[page + 1]) {
message.edit({ const content = collector.type === "content"
embeds: [pages[++page]] ? { content: message.content.replace(`{role:${collector.regex[0]}}`, `${emote} $\\text{\\textcolor{${roleColor}}{${role[1].name}}}$`) }
}).catch(() => { }); : { embeds: [new Embed().setColor("#A52F05").setDescription(client.messages.get(message.id).embeds[0].description.replace(`{role:${collector.regex[0]}}`, `:${emojiId}: $\\text{\\textcolor{${roleColor}}{${role[1].name}}}$`))] };
return paginateCheck.page = paginateCheck.page + 1
} else { await message.edit(content);
return; collector.roles.shift();
} return collector.regex.shift();
case "⏩": }
if (page !== pages.length) {
message.edit({ /**
embeds: [pages[pages.length - 1]] * Handles pagination reactions
}).catch(() => { }); * @param {Object} client - The client instance
return paginateCheck.page = pages.length - 1 * @param {Object} message - The message object
} else { * @param {Object} paginateCheck - The pagination check object
return; * @param {string} emojiId - The emoji ID
} * @returns {Promise<void>}
*/
async function handlePagination(client, message, paginateCheck, emojiId) {
const { pages, page } = paginateCheck;
let newPage = page;
switch (emojiId) {
case "⏪":
if (page !== 0) {
await message.edit({ embeds: [pages[0]] }).catch(() => {});
newPage = 0;
}
break;
case "⬅️":
if (pages[page - 1]) {
await message.edit({ embeds: [pages[--newPage]] }).catch(() => {});
}
break;
case "➡️":
if (pages[page + 1]) {
await message.edit({ embeds: [pages[++newPage]] }).catch(() => {});
}
break;
case "⏩":
if (page !== pages.length) {
await message.edit({ embeds: [pages[pages.length - 1]] }).catch(() => {});
newPage = pages.length - 1;
}
break;
}
if (newPage !== page) {
paginateCheck.page = newPage;
}
}
/**
* Handles poll reactions
* @param {Object} client - The client instance
* @param {Object} message - The message object
* @param {Object} pollCheck - The poll check object
* @param {string} userId - The user ID
* @param {string} emojiId - The emoji ID
* @returns {Promise<void>}
*/
async function handlePoll(client, message, pollCheck, userId, emojiId) {
const convert = EMOJIS.findIndex(e => e.name === emojiId);
if (convert === 10 && pollCheck.owner === userId) {
await PollDB.findOneAndDelete({ messageId: message.id });
await pollCheck.poll.update();
const tooMuch = [];
if (pollCheck.poll.options.description.length > 80) {
tooMuch.push(`**${client.translate.get(pollCheck.lang, "Events.messageReactionAdd.title")}**: ${pollCheck.poll.options.description}`);
} }
} else if (pollCheck) {
let tooMuch = [];
if (pollCheck.poll.options.description.length > 80) tooMuch.push(`**${client.translate.get(pollCheck.lang, "Events.messageReactionAdd.title")}**: ${pollCheck.poll.options.description}`)
pollCheck.poll.voteOptions.name.filter(e => e).forEach((e, i) => { pollCheck.poll.voteOptions.name.filter(e => e).forEach((e, i) => {
i++
if (e.length > 70) { if (e.length > 70) {
tooMuch.push(`**${i}.** ${e}`) tooMuch.push(`**${i + 1}.** ${e}`);
} }
}); });
let convert = emojis.findIndex(e => e.name === emojiId); await message.edit({
if (convert === 10 && pollCheck.owner === userId) { content: `${client.translate.get(pollCheck.lang, "Events.messageReactionAdd.owner")} (<@${pollCheck.owner}>) ${client.translate.get(pollCheck.lang, "Events.messageReactionAdd.end")}:`,
await PollDB.findOneAndDelete({ messageId: message.id }); embeds: [new Embed()
await pollCheck.poll.update(); .setDescription(tooMuch.length > 0 ? tooMuch.join("\n") : null)
message.edit({ content: `${client.translate.get(pollCheck.lang, "Events.messageReactionAdd.owner")} (<@${pollCheck.owner}>) ${client.translate.get(pollCheck.lang, "Events.messageReactionAdd.end")}:`, embeds: [new Embed().setDescription(tooMuch.length > 0 ? tooMuch.map(e => e).join("\n") : null).setMedia(await client.Uploader.upload(pollCheck.poll.canvas.toBuffer(), `Poll.png`)).setColor("#F24646")] }).catch(() => { }); .setMedia(await client.Uploader.upload(pollCheck.poll.canvas.toBuffer(), `Poll.png`))
return client.polls.delete(message.id); .setColor("#F24646")]
} else if (convert === 0 && convert !== 10 || convert !== -1 && convert !== 10) { }).catch(() => {});
if (client.reactions.get(userId)) return client.users.get(userId)?.openDM().then(dm => dm.sendMessage(client.translate.get(pollCheck.lang, "Events.messageReactionAdd.tooFast"))).catch(() => { });
if (pollCheck.users.includes(userId)) return;
pollCheck.users.push(userId);
const user = (client.users.get(userId)) || await client.users.fetch(userId);
// console.log(user.avatar.id ? user.avatar.createFileURL() : 'https://chat.f95.io/api/users/01HATCWS7XZ7KEHW64AV20SMKR/default_avatar')
await pollCheck.poll.addVote(convert, userId, 'https://chat.f95.io/api/users/01HATCWS7XZ7KEHW64AV20SMKR/default_avatar', message.id);
message.edit({ embeds: [new Embed().setDescription(tooMuch.length > 0 ? tooMuch.map(e => e).join("\n") : null).setMedia(await client.Uploader.upload(pollCheck.poll.canvas.toBuffer(), `Poll.png`)).setColor("#A52F05")] }).catch(() => { });
client.reactions.set(userId, Date.now() + 3000)
return setTimeout(() => client.reactions.delete(userId), 3000)
} else return;
} else {
const db = await Giveaways.findOne({ messageId: message.id });
if (db) {
if (emojiId === client.config.emojis.confetti && db && !db.ended) {
if (client.reactions.get(userId)) return;
if (db.users.find(u => u.userID === userId)) return;
db.users.push({ userID: userId });
db.picking.push({ userID: userId });
db.save();
client.reactions.set(userId, Date.now() + 3000) return client.polls.delete(message.id);
setTimeout(() => client.reactions.delete(userId), 3000) }
client.users.get(userId)?.openDM().then(dm => dm.sendMessage(`${client.translate.get(db.lang, "Events.messageReactionAdd.joined")} [${db.prize}](https://chat.f95.io/server/${db.serverId}/channel/${db.channelId}/${db.messageId})!\n${client.translate.get(db.lang, "Events.messageReactionAdd.joined2")} **${db.users.length}** ${client.translate.get(db.lang, "Events.messageReactionAdd.joined3")}`)).catch(() => { }); if (convert === 0 || (convert !== -1 && convert !== 10)) {
} else if (emojiId === client.config.emojis.stop && db && db.owner === userId && !db.ended) { if (client.reactions.get(userId)) {
let endDate = Date.now(); return client.users.get(userId)?.openDM()
.then(dm => dm.sendMessage(client.translate.get(pollCheck.lang, "Events.messageReactionAdd.tooFast")))
.catch(() => {});
}
if (db.users.length === 0) { if (pollCheck.users.includes(userId)) return;
const noUsers = new Embed()
.setColor("#A52F05")
.setTitle(client.translate.get(db.lang, "Events.messageReactionAdd.giveaway"))
.setDescription(`${client.translate.get(db.lang, "Events.messageReactionAdd.owner")} (<@${userId}>) ${client.translate.get(db.lang, "Events.messageReactionAdd.early")}\n${client.translate.get(db.lang, "Events.messageReactionAdd.endNone")}!\n\n${client.translate.get(db.lang, "Events.messageReactionAdd.ended")}: <t:${Math.floor((endDate) / 1000)}:R>\n${client.translate.get(db.lang, "Events.messageReactionAdd.prize")}: ${db.prize}\n${client.translate.get(db.lang, "Events.messageReactionAdd.winnersNone")}${db.requirement ? `\n${client.translate.get(db.lang, "Events.messageReactionAdd.reqs")}: ${db.requirement}` : ``}`)
await db.updateOne({ ended: true, endDate: endDate }) pollCheck.users.push(userId);
await db.save(); const user = client.users.get(userId) || await client.users.fetch(userId);
return await client.api.patch(`/channels/${db.channelId}/messages/${db.messageId}`, { "embeds": [noUsers] }); await pollCheck.poll.addVote(convert, userId, 'https://chat.f95.io/api/users/01HATCWS7XZ7KEHW64AV20SMKR/default_avatar', message.id);
}
for (let i = 0; i < db.winners; i++) { const tooMuch = [];
let winner = db.picking[Math.floor(Math.random() * db.picking.length)]; if (pollCheck.poll.options.description.length > 80) {
if (winner) { tooMuch.push(`**${client.translate.get(pollCheck.lang, "Events.messageReactionAdd.title")}**: ${pollCheck.poll.options.description}`);
const filtered = db.picking.filter(object => object.userID != winner.userID) }
db.picking = filtered; pollCheck.poll.voteOptions.name.filter(e => e).forEach((e, i) => {
db.pickedWinners.push({ id: winner.userID }) if (e.length > 70) {
} tooMuch.push(`**${i + 1}.** ${e}`);
}
await db.updateOne({ ended: true, endDate: endDate })
await db.save();
const noUsers = new Embed()
.setColor("#A52F05")
.setTitle(client.translate.get(db.lang, "Events.messageReactionAdd.giveaway"))
.setDescription(`${client.translate.get(db.lang, "Events.messageReactionAdd.owner")} (<@${userId}>) ${client.translate.get(db.lang, "Events.messageReactionAdd.early")}\n${client.translate.get(db.lang, "Events.messageReactionAdd.partici")}: ${db.users.length}\n\n${client.translate.get(db.lang, "Events.messageReactionAdd.ended")}: <t:${Math.floor((endDate) / 1000)}:R>\n${client.translate.get(db.lang, "Events.messageReactionAdd.prize")}: ${db.prize}\n${client.translate.get(db.lang, "Events.messageReactionAdd.winners")}: ${db.pickedWinners.length > 0 ? db.pickedWinners.map(w => `<@${w.id}>`).join(", ") : client.translate.get(db.lang, "Events.messageReactionAdd.none")}${db.requirement ? `\n${client.translate.get(db.lang, "Events.messageReactionAdd.reqs")}: ${db.requirement}` : ``}`)
message.edit({ embeds: [noUsers] }).catch(() => { });
await client.api.post(`/channels/${db.channelId}/messages`, { "content": `${client.translate.get(db.lang, "Events.messageReactionAdd.congrats")} ${db.pickedWinners.map(w => `<@${w.id}>`).join(", ")}! ${client.translate.get(db.lang, "Events.messageReactionAdd.youWon")} **[${db.prize}](https://chat.f95.io/server/${db.serverId}/channel/${db.channelId}/${db.messageId})**!` }).catch(() => { });
client.reactions.set(userId, Date.now() + 3000)
setTimeout(() => client.reactions.delete(userId), 3000)
} }
} else { });
const db2 = await client.database.getGuild(message.server.id, true)
if (db2 && db2.roles.find(e => e.msgId === message.id) && db2.roles.find(e => e.roles.find(e => e.emoji === emojiId))) {
if (client.reactions.get(userId)) return;
const roles = []; await message.edit({
db2.roles.find(e => e.msgId === message.id).roles.map(e => roles.push(e)); embeds: [new Embed()
const role = roles.find(e => e.emoji === emojiId); .setDescription(tooMuch.length > 0 ? tooMuch.join("\n") : null)
const member = await (client.servers.get(message.server.id) || await client.servers.fetch(message.server.id))?.fetchMember(userId); .setMedia(await client.Uploader.upload(pollCheck.poll.canvas.toBuffer(), `Poll.png`))
if (!member) return; .setColor("#A52F05")]
}).catch(() => {});
let error = false; client.reactions.set(userId, Date.now() + 3000);
let dataRoles = []; setTimeout(() => client.reactions.delete(userId), 3000);
if (member.roles) member.roles.map(e => dataRoles.push(e)); }
if (dataRoles.includes(role.role)) return; }
client.reactions.set(userId, Date.now() + 3000); /**
setTimeout(() => client.reactions.delete(userId), 3000); * Handles giveaway reactions
* @param {Object} client - The client instance
* @param {Object} message - The message object
* @param {Object} db - The giveaway database object
* @param {string} userId - The user ID
* @param {string} emojiId - The emoji ID
* @returns {Promise<void>}
*/
async function handleGiveaway(client, message, db, userId, emojiId) {
if (emojiId === client.config.emojis.confetti && !db.ended) {
if (client.reactions.get(userId) || db.users.find(u => u.userID === userId)) return;
dataRoles.push(role.role); db.users.push({ userID: userId });
await member.edit({ roles: dataRoles }).catch(() => { error = true }) db.picking.push({ userID: userId });
await db.save();
if (error && db2.dm) { client.reactions.set(userId, Date.now() + 3000);
member?.user?.openDM().then((dm) => { dm.sendMessage(`${client.translate.get(db2.language, "Events.messageReactionAdd.noPerms").replace("{role}", `**${role.name}**`)}!`) }).catch(() => { }); setTimeout(() => client.reactions.delete(userId), 3000);
} else if (db2.dm) {
member?.user?.openDM().then((dm) => { dm.sendMessage(`${client.translate.get(db2.language, "Events.messageReactionAdd.success").replace("{role}", `**${role.name}**`)}!`) }).catch(() => { }); await client.users.get(userId)?.openDM()
} .then(dm => dm.sendMessage(
`${client.translate.get(db.lang, "Events.messageReactionAdd.joined")} [${db.prize}](https://chat.f95.io/server/${db.serverId}/channel/${db.channelId}/${db.messageId})!\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.joined2")} **${db.users.length}** ${client.translate.get(db.lang, "Events.messageReactionAdd.joined3")}`
))
.catch(() => {});
} else if (emojiId === client.config.emojis.stop && db.owner === userId && !db.ended) {
const endDate = Date.now();
if (db.users.length === 0) {
const noUsers = new Embed()
.setColor("#A52F05")
.setTitle(client.translate.get(db.lang, "Events.messageReactionAdd.giveaway"))
.setDescription(
`${client.translate.get(db.lang, "Events.messageReactionAdd.owner")} (<@${userId}>) ${client.translate.get(db.lang, "Events.messageReactionAdd.early")}\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.endNone")}!\n\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.ended")}: <t:${Math.floor(endDate / 1000)}:R>\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.prize")}: ${db.prize}\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.winnersNone")}` +
(db.requirement ? `\n${client.translate.get(db.lang, "Events.messageReactionAdd.reqs")}: ${db.requirement}` : ``)
);
await db.updateOne({ ended: true, endDate });
await db.save();
return await client.api.patch(`/channels/${db.channelId}/messages/${db.messageId}`, { embeds: [noUsers] });
}
for (let i = 0; i < db.winners; i++) {
const winner = db.picking[Math.floor(Math.random() * db.picking.length)];
if (winner) {
db.picking = db.picking.filter(object => object.userID !== winner.userID);
db.pickedWinners.push({ id: winner.userID });
} }
} }
await db.updateOne({ ended: true, endDate });
await db.save();
const noUsers = new Embed()
.setColor("#A52F05")
.setTitle(client.translate.get(db.lang, "Events.messageReactionAdd.giveaway"))
.setDescription(
`${client.translate.get(db.lang, "Events.messageReactionAdd.owner")} (<@${userId}>) ${client.translate.get(db.lang, "Events.messageReactionAdd.early")}\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.partici")}: ${db.users.length}\n\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.ended")}: <t:${Math.floor(endDate / 1000)}:R>\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.prize")}: ${db.prize}\n` +
`${client.translate.get(db.lang, "Events.messageReactionAdd.winners")}: ${db.pickedWinners.length > 0 ? db.pickedWinners.map(w => `<@${w.id}>`).join(", ") : client.translate.get(db.lang, "Events.messageReactionAdd.none")}` +
(db.requirement ? `\n${client.translate.get(db.lang, "Events.messageReactionAdd.reqs")}: ${db.requirement}` : ``)
);
await message.edit({ embeds: [noUsers] }).catch(() => {});
await client.api.post(`/channels/${db.channelId}/messages`, {
content: `${client.translate.get(db.lang, "Events.messageReactionAdd.congrats")} ${db.pickedWinners.map(w => `<@${w.id}>`).join(", ")}! ${client.translate.get(db.lang, "Events.messageReactionAdd.youWon")} **[${db.prize}](https://chat.f95.io/server/${db.serverId}/channel/${db.channelId}/${db.messageId})**!`
}).catch(() => {});
client.reactions.set(userId, Date.now() + 3000);
setTimeout(() => client.reactions.delete(userId), 3000);
} }
} }
/**
* Handles role reactions
* @param {Object} client - The client instance
* @param {Object} message - The message object
* @param {Object} db - The guild database object
* @param {string} userId - The user ID
* @param {string} emojiId - The emoji ID
* @returns {Promise<void>}
*/
async function handleRoleReaction(client, message, db, userId, emojiId) {
if (client.reactions.get(userId)) return;
const roles = [];
db.roles.find(e => e.msgId === message.id).roles.map(e => roles.push(e));
const role = roles.find(e => e.emoji === emojiId);
const member = await (client.servers.get(message.server.id) || await client.servers.fetch(message.server.id))?.fetchMember(userId);
if (!member) return;
let error = false;
let dataRoles = [];
if (member.roles) member.roles.map(e => dataRoles.push(e));
if (dataRoles.includes(role.role)) return;
client.reactions.set(userId, Date.now() + 3000);
setTimeout(() => client.reactions.delete(userId), 3000);
dataRoles.push(role.role);
await member.edit({ roles: dataRoles }).catch(() => { error = true });
if (db.dm) {
const dmMessage = error
? client.translate.get(db.language, "Events.messageReactionAdd.noPerms").replace("{role}", `**${role.name}**`)
: client.translate.get(db.language, "Events.messageReactionAdd.success").replace("{role}", `**${role.name}**`);
await member?.user?.openDM()
.then(dm => dm.sendMessage(dmMessage))
.catch(() => {});
}
}
/**
* Main event handler for message reactions
* @param {Object} client - The client instance
* @param {Object} message - The message object
* @param {string} userId - The user ID
* @param {string} emojiId - The emoji ID
* @returns {Promise<void>}
*/
module.exports = async (client, message, userId, emojiId) => {
try {
const paginateCheck = client.paginate.get(userId);
const pollCheck = client.polls.get(message.id);
const collector = client.messageCollector.get(userId);
const editCollector = client.messageEdit.get(userId);
// Handle message collector
if (collector && (collector.messageId === message.id || (collector?.oldMessageId === message.id && collector.channelId === message.channelId))) {
return await handleMessageCollector(client, message, userId, emojiId, collector);
}
// Handle edit collector
if (editCollector && (editCollector.messageId === message.id || (editCollector?.botMessage === message.id && editCollector.channelId === message.channelId))) {
return await handleMessageCollector(client, message, userId, emojiId, editCollector);
}
// Handle pagination
if (paginateCheck && paginateCheck.message === message.id) {
return await handlePagination(client, message, paginateCheck, emojiId);
}
// Handle poll
if (pollCheck) {
return await handlePoll(client, message, pollCheck, userId, emojiId);
}
// Handle giveaway
const giveaway = await Giveaways.findOne({ messageId: message.id });
if (giveaway) {
return await handleGiveaway(client, message, giveaway, userId, emojiId);
}
// Handle role reaction
const db = await client.database.getGuild(message.server.id, true);
if (db?.roles.find(e => e.msgId === message.id)?.roles.find(e => e.emoji === emojiId)) {
return await handleRoleReaction(client, message, db, userId, emojiId);
}
} catch (error) {
console.error('Error in messageReactionAdd:', error);
}
};

View File

@ -1,100 +1,199 @@
const Embed = require("../functions/embed"); const path = require("path");
const Giveaways = require("../models/giveaways"); const Embed = require(path.join(__dirname, "../functions/embed"));
const emojis = [{ name: "1⃣", id: 0 }, { name: "2⃣", id: 1 }, { name: "3⃣", id: 2 }, { name: "4⃣", id: 3 }, { name: "5⃣", id: 4 }, { name: "6⃣", id: 5 }, { name: "7⃣", id: 6 }, { name: "8⃣", id: 7 }, { name: "9⃣", id: 8 }, { name: "🔟", id: 9 }, { name: "🛑", id: "stop" }] const Giveaways = require(path.join(__dirname, "../models/giveaways"));
const colors = /^([A-Z0-9]+)/;
module.exports = async (client, message, userId, emojiId) => { // Constants
const pollCheck = client.polls.get(message.id); const EMOJIS = [
const collector = client.messageCollector.get(userId); { name: "1⃣", id: 0 }, { name: "2⃣", id: 1 }, { name: "3⃣", id: 2 },
const editCollector = client.messageEdit.get(userId); { name: "4⃣", id: 3 }, { name: "5⃣", id: 4 }, { name: "6⃣", id: 5 },
{ name: "7⃣", id: 6 }, { name: "8⃣", id: 7 }, { name: "9⃣", id: 8 },
{ name: "🔟", id: 9 }, { name: "🛑", id: "stop" }
];
const COLORS_REGEX = /^([A-Z0-9]+)/;
if (collector && collector.messageId === message.id && collector.channelId === message.channelId) { /**
const emoji = collector.rolesDone.find(e => e.emoji === emojiId); * Handles message collector reaction removal
if (emoji) { * @param {Object} client - The client instance
collector.rolesDone = collector.rolesDone.filter(object => object.emoji != emojiId); * @param {Object} message - The message object
collector.roles.push([emoji.role, { name: emoji.name, colour: emoji.color }]); * @param {string} emojiId - The emoji ID
collector.regex.push(emoji.name); * @param {Object} collector - The message collector
* @returns {Promise<void>}
*/
async function handleMessageCollector(client, message, emojiId, collector) {
const emoji = collector.rolesDone.find(e => e.emoji === emojiId);
if (!emoji) return;
if (colors.test(emojiId)) emote = `:${emojiId}:`; collector.rolesDone = collector.rolesDone.filter(object => object.emoji !== emojiId);
else if (!colors.test(emojiId)) emote = emojiId collector.roles.push([emoji.role, { name: emoji.name, colour: emoji.color }]);
return message.edit(collector.type === "content" ? { content: message.content.replace(`${emote} $\\text{\\textcolor{${emoji.color}}{${emoji.name}}}$`, `{role:${emoji.name}}`) } : { embeds: [new Embed().setColor("#A52F05").setDescription(client.messages.get(message.id).embeds[0].description.replace(`{role:${editCollector.regex[0]}}`, `:${emojiId}: $\\text{\\textcolor{${editCollector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : editCollector.roles[0][1].colour}}{${editCollector.roles[0][1].name}}}$`))] }).catch(() => { }); collector.regex.push(emoji.name);
}
} else if (editCollector && editCollector.messageId === message.id && editCollector.channelId === message.channelId) {
const emoji = editCollector.rolesDone.find(e => e.emoji === emojiId);
if (emoji) {
editCollector.rolesDone = editCollector.rolesDone.filter(object => object.emoji != emojiId);
editCollector.roles.push([emoji.role, { name: emoji.name, colour: emoji.color }]);
editCollector.regex.push(emoji.name);
if (colors.test(emojiId)) emote = `:${emojiId}:`; const emote = COLORS_REGEX.test(emojiId) ? `:${emojiId}:` : emojiId;
else if (!colors.test(emojiId)) emote = emojiId const content = collector.type === "content"
return message.edit(editCollector.type === "content" ? { content: message.content.replace(`${emote} $\\text{\\textcolor{${emoji.color}}{${emoji.name}}}$`, `{role:${emoji.name}}`) } : { embeds: [new Embed().setColor("#A52F05").setDescription(client.messages.get(message.id).embeds[0].description.replace(`{role:${editCollector.regex[0]}}`, `:${emojiId}: $\\text{\\textcolor{${editCollector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : editCollector.roles[0][1].colour}}{${editCollector.roles[0][1].name}}}$`))] }).catch(() => { }); ? { content: message.content.replace(`${emote} $\\text{\\textcolor{${emoji.color}}{${emoji.name}}}$`, `{role:${emoji.name}}`) }
} : { embeds: [new Embed().setColor("#A52F05").setDescription(client.messages.get(message.id).embeds[0].description.replace(`{role:${collector.regex[0]}}`, `:${emojiId}: $\\text{\\textcolor{${collector.roles[0][1].colour?.includes("linear-gradient") ? '#000000' : collector.roles[0][1].colour}}{${collector.roles[0][1].name}}}$`))] };
} else if (pollCheck) {
if (client.reactions.get(userId)) return client.users.get(userId)?.openDM().then(dm => dm.sendMessage(client.translate.get(pollCheck.language, "Events.messageReactionRemove.tooFast"))).catch(() => { });
let convert = emojis.findIndex(e => e.name === emojiId);
if (convert === 0 && convert !== 10 || convert !== -1 && convert !== 10) {
if (!pollCheck.users.includes(userId)) return;
let tooMuch = []; return message.edit(content).catch(() => {});
if (pollCheck.poll.options.description.length > 80) tooMuch.push(`**${client.translate.get(pollCheck.language, "Events.messageReactionRemove.title")}**: ${pollCheck.poll.options.description}`) }
pollCheck.poll.voteOptions.name.filter(e => e).forEach((e, i) => {
i++
if (e.length > 70) {
tooMuch.push(`**${i}.** ${e}`)
}
});
pollCheck.users = pollCheck.users.filter(object => object != userId); /**
const user = (client.users.get(userId)) || await client.users.fetch(userId); * Handles poll reaction removal
await pollCheck.poll.removeVote(convert, userId, 'https://chat.f95.io/api/users/01HATCWS7XZ7KEHW64AV20SMKR/default_avatar', message.id); * @param {Object} client - The client instance
message.edit({ embeds: [new Embed().setDescription(tooMuch.length > 0 ? tooMuch.map(e => e).join("\n") : null).setMedia(await client.Uploader.upload(pollCheck.poll.canvas.toBuffer(), `Poll.png`)).setColor("#A52F05")] }).catch(() => { }); * @param {Object} message - The message object
client.reactions.set(userId, Date.now() + 3000) * @param {Object} pollCheck - The poll check object
return setTimeout(() => client.reactions.delete(userId), 3000) * @param {string} userId - The user ID
} else return; * @param {string} emojiId - The emoji ID
} else { * @returns {Promise<void>}
if (client.reactions.get(userId)) return; */
const db = await Giveaways.findOne({ messageId: message.id }); async function handlePoll(client, message, pollCheck, userId, emojiId) {
if (db && !db.ended) { if (client.reactions.get(userId)) {
if (emojiId === client.config.emojis.confetti) { return client.users.get(userId)?.openDM()
if (!db.users.find(u => u.userID === userId)) return; .then(dm => dm.sendMessage(client.translate.get(pollCheck.language, "Events.messageReactionRemove.tooFast")))
const filtered = db.users.filter(object => object.userID != userId) .catch(() => {});
db.users = filtered;
const filtered2 = db.picking.filter(object => object.userID != userId)
db.picking = filtered2;
db.save();
client.reactions.set(userId, Date.now() + 3000)
setTimeout(() => client.reactions.delete(userId), 3000)
client.users.get(userId)?.openDM().then(dm => dm.sendMessage(`${client.translate.get(pollCheck.language, "Events.messageReactionRemove.left")} [${db.prize}](https://chat.f95.io/server/${db.serverId}/channel/${db.channelId}/${db.messageId})!\n${client.translate.get(pollCheck.language, "Events.messageReactionRemove.left2")} **${db.users.length}** ${client.translate.get(pollCheck.language, "Events.messageReactionRemove.left")}!`)).catch(() => { });
}
} else {
const db2 = await client.database.getGuild(message.server.id, true)
if (db2 && db2.roles.find(e => e.msgId === message.id) && db2.roles.find(e => e.roles.find(e => e.emoji === emojiId))) {
const roles = [];
db2.roles.find(e => e.msgId === message.id).roles.map(e => roles.push(e));
const role = roles.find(e => e.emoji === emojiId);
const member = await (client.servers.get(message.server.id) || await client.servers.fetch(message.server.id))?.fetchMember(userId);
if (!member) return;
let error = false;
let dataRoles = [];
if (member.roles) member.roles.map(e => dataRoles.push(e));
if (!dataRoles.includes(role.role)) return;
client.reactions.set(userId, Date.now() + 3000);
setTimeout(() => client.reactions.delete(userId), 3000);
dataRoles = dataRoles.filter(object => object != role.role);
await member.edit({ roles: dataRoles }).catch(() => { error = true })
if (error) {
if (db2.dm) member?.user?.openDM().then((dm) => { dm.sendMessage(`${client.translate.get(db2.language, "Events.messageReactionRemove.noPerms").replace("{role}", `**${role.name}**`)}!`) }).catch(() => { });
} else {
if (db2.dm) member?.user?.openDM().then((dm) => { dm.sendMessage(`${client.translate.get(db2.language, "Events.messageReactionRemove.success").replace("{role}", `**${role.name}**`)}!`) }).catch(() => { });
}
}
}
} }
}
const convert = EMOJIS.findIndex(e => e.name === emojiId);
if (convert !== 0 && convert === 10 || convert === -1) return;
if (!pollCheck.users.includes(userId)) return;
const tooMuch = [];
if (pollCheck.poll.options.description.length > 80) {
tooMuch.push(`**${client.translate.get(pollCheck.language, "Events.messageReactionRemove.title")}**: ${pollCheck.poll.options.description}`);
}
pollCheck.poll.voteOptions.name.filter(e => e).forEach((e, i) => {
if (e.length > 70) {
tooMuch.push(`**${i + 1}.** ${e}`);
}
});
pollCheck.users = pollCheck.users.filter(object => object !== userId);
const user = client.users.get(userId) || await client.users.fetch(userId);
await pollCheck.poll.removeVote(convert, userId, 'https://chat.f95.io/api/users/01HATCWS7XZ7KEHW64AV20SMKR/default_avatar', message.id);
await message.edit({
embeds: [new Embed()
.setDescription(tooMuch.length > 0 ? tooMuch.join("\n") : null)
.setMedia(await client.Uploader.upload(pollCheck.poll.canvas.toBuffer(), `Poll.png`))
.setColor("#A52F05")]
}).catch(() => {});
client.reactions.set(userId, Date.now() + 3000);
setTimeout(() => client.reactions.delete(userId), 3000);
}
/**
* Handles giveaway reaction removal
* @param {Object} client - The client instance
* @param {Object} message - The message object
* @param {Object} db - The giveaway database object
* @param {string} userId - The user ID
* @param {string} emojiId - The emoji ID
* @returns {Promise<void>}
*/
async function handleGiveaway(client, message, db, userId, emojiId) {
if (client.reactions.get(userId)) return;
if (emojiId === client.config.emojis.confetti && !db.ended) {
if (!db.users.find(u => u.userID === userId)) return;
db.users = db.users.filter(object => object.userID !== userId);
db.picking = db.picking.filter(object => object.userID !== userId);
await db.save();
client.reactions.set(userId, Date.now() + 3000);
setTimeout(() => client.reactions.delete(userId), 3000);
await client.users.get(userId)?.openDM()
.then(dm => dm.sendMessage(
`${client.translate.get(db.language, "Events.messageReactionRemove.left")} [${db.prize}](https://chat.f95.io/server/${db.serverId}/channel/${db.channelId}/${db.messageId})!\n` +
`${client.translate.get(db.language, "Events.messageReactionRemove.left2")} **${db.users.length}** ${client.translate.get(db.language, "Events.messageReactionRemove.left")}!`
))
.catch(() => {});
}
}
/**
* Handles role reaction removal
* @param {Object} client - The client instance
* @param {Object} message - The message object
* @param {Object} db - The guild database object
* @param {string} userId - The user ID
* @param {string} emojiId - The emoji ID
* @returns {Promise<void>}
*/
async function handleRoleReaction(client, message, db, userId, emojiId) {
if (client.reactions.get(userId)) return;
const roles = [];
db.roles.find(e => e.msgId === message.id).roles.map(e => roles.push(e));
const role = roles.find(e => e.emoji === emojiId);
const member = await (client.servers.get(message.server.id) || await client.servers.fetch(message.server.id))?.fetchMember(userId);
if (!member) return;
let error = false;
let dataRoles = [];
if (member.roles) member.roles.map(e => dataRoles.push(e));
if (!dataRoles.includes(role.role)) return;
client.reactions.set(userId, Date.now() + 3000);
setTimeout(() => client.reactions.delete(userId), 3000);
dataRoles = dataRoles.filter(object => object !== role.role);
await member.edit({ roles: dataRoles }).catch(() => { error = true });
if (db.dm) {
const dmMessage = error
? client.translate.get(db.language, "Events.messageReactionRemove.noPerms").replace("{role}", `**${role.name}**`)
: client.translate.get(db.language, "Events.messageReactionRemove.success").replace("{role}", `**${role.name}**`);
await member?.user?.openDM()
.then(dm => dm.sendMessage(dmMessage))
.catch(() => {});
}
}
/**
* Main event handler for message reaction removal
* @param {Object} client - The client instance
* @param {Object} message - The message object
* @param {string} userId - The user ID
* @param {string} emojiId - The emoji ID
* @returns {Promise<void>}
*/
module.exports = async (client, message, userId, emojiId) => {
try {
const pollCheck = client.polls.get(message.id);
const collector = client.messageCollector.get(userId);
const editCollector = client.messageEdit.get(userId);
// Handle message collector
if (collector && collector.messageId === message.id && collector.channelId === message.channelId) {
return await handleMessageCollector(client, message, emojiId, collector);
}
// Handle edit collector
if (editCollector && editCollector.messageId === message.id && editCollector.channelId === message.channelId) {
return await handleMessageCollector(client, message, emojiId, editCollector);
}
// Handle poll
if (pollCheck) {
return await handlePoll(client, message, pollCheck, userId, emojiId);
}
// Handle giveaway
const giveaway = await Giveaways.findOne({ messageId: message.id });
if (giveaway && !giveaway.ended) {
return await handleGiveaway(client, message, giveaway, userId, emojiId);
}
// Handle role reaction
const db = await client.database.getGuild(message.server.id, true);
if (db?.roles.find(e => e.msgId === message.id)?.roles.find(e => e.emoji === emojiId)) {
return await handleRoleReaction(client, message, db, userId, emojiId);
}
} catch (error) {
console.error('Error in messageReactionRemove:', error);
}
};

View File

@ -1,4 +1,5 @@
const Embed = require("../functions/embed") const path = require("path");
const Embed = require(path.join(__dirname, "../functions/embed"));
module.exports = async (client, server) => { module.exports = async (client, server) => {
//console.log("join", server) //console.log("join", server)
// await client.database.getGuild(server.id, true) // await client.database.getGuild(server.id, true)

View File

@ -1,4 +1,5 @@
const Embed = require("../functions/embed") const path = require("path");
const Embed = require(path.join(__dirname, "../functions/embed"));
module.exports = async (client, server) => { module.exports = async (client, server) => {
//console.log("delete",server) //console.log("delete",server)
// await client.database.deleteGuild(server.id) // await client.database.deleteGuild(server.id)

View File

@ -1,3 +1,4 @@
const path = require("path");
let type; let type;
module.exports = async (client, member, memberOld) => { module.exports = async (client, member, memberOld) => {
// Work in progress // Work in progress

View File

@ -1,4 +1,5 @@
const db = require("../models/logging"); const path = require("path");
const db = require(path.join(__dirname, "../models/logging"));
async function audit(type, message, cmd) { async function audit(type, message, cmd) {
const audit = new db(); const audit = new db();

View File

@ -1,23 +1,69 @@
const db = require("../models/polls"); const path = require("path");
const Polls = require("./poll"); const db = require(path.join(__dirname, "../models/polls"));
const Polls = require(path.join(__dirname, "./poll"));
/**
* Checks and processes all active polls in the database
* @param {Object} client - The Discord client instance
* @returns {Promise<void>}
*/
async function checkPolls(client) { async function checkPolls(client) {
let polls = await db.find(); try {
if (!polls || polls.length === 0) return; const polls = await db.find();
let i = 0; if (!polls?.length) return;
for (let poll of polls) {
i++
setTimeout(async () => {
const time = poll.now - (Date.now() - poll.time), users = poll.users, avatars = poll.avatars, votes = poll.votes, desc = poll.desc, name = poll.name, names = poll.options, owner = poll.owner, lang = poll.lang;
const newPoll = new Polls({ time, client, name: { name: name, description: desc }, options: names, votes: votes, users: users, avatars: avatars, owner: owner, lang: lang })
// Process polls concurrently with a small delay between each
await Promise.all(polls.map(async (poll, index) => {
try { try {
await client.channels.get(poll.channelId).fetchMessage(poll.messageId).catch(() => { return }); // Add small delay between processing each poll to prevent rate limiting
const msg = await client.messages.get(poll.messageId); await new Promise(resolve => setTimeout(resolve, index * 700));
if (msg) newPoll.start(msg, newPoll);
} catch (e) { }
poll.deleteOne({ messageId: poll.messageId }); const {
}, i * 700); now,
time,
users,
avatars,
votes,
desc,
name,
options: names,
owner,
lang,
channelId,
messageId
} = poll;
const timeRemaining = now - (Date.now() - time);
const newPoll = new Polls({
time: timeRemaining,
client,
name: { name, description: desc },
options: names,
votes,
users,
avatars,
owner,
lang
});
// Attempt to fetch and process the message
const channel = client.channels.get(channelId);
if (!channel) return;
const message = await channel.fetchMessage(messageId).catch(() => null);
if (message) {
await newPoll.start(message, newPoll);
}
// Clean up the poll from database
await poll.deleteOne({ messageId });
} catch (error) {
console.error(`Error processing poll ${poll.messageId}:`, error);
}
}));
} catch (error) {
console.error('Error in checkPolls:', error);
} }
} }

View File

@ -1,23 +1,72 @@
const db = require("../models/guilds"); const path = require("path");
async function checkRoles(client) { const db = require(path.join(__dirname, "../models/guilds"));
let rr = await db.find({ $expr: { $gt: [{ $size: "$roles" }, 0] } });
if (!rr || rr.length === 0) return;
let i = 0;
let ii = 0;
for (let r of rr) {
i++
setTimeout(async () => {
r.roles.map((role) => {
ii++
setTimeout(async () => {
if (!client.channels.get(role.chanId) && role.roles.length === 0) {
return await client.database.updateGuild(r.id, { roles: r.roles.filter(e => e.msgId !== role.msgId) });
}
await client.channels.get(role.chanId)?.fetchMessage(role.msgId).catch(() => { }); /**
}, ii * 700); * Checks and validates role reaction messages across all guilds
}); * @param {Object} client - Discord client instance
}, i * 600); * @returns {Promise<void>}
*/
async function checkRoles(client) {
try {
// Find all guilds with role reactions
const guildsWithRoles = await db.find({
$expr: { $gt: [{ $size: "$roles" }, 0] }
});
if (!guildsWithRoles?.length) return;
// Process each guild sequentially to avoid rate limits
for (const guild of guildsWithRoles) {
try {
await processGuildRoles(client, guild);
} catch (error) {
console.error(`Error processing guild ${guild.id}:`, error);
}
}
} catch (error) {
console.error('Error in checkRoles:', error);
}
}
/**
* Process role reactions for a single guild
* @param {Object} client - Discord client instance
* @param {Object} guild - Guild data from database
* @returns {Promise<void>}
*/
async function processGuildRoles(client, guild) {
const validRoles = [];
const invalidRoles = [];
// Process each role reaction message
for (const role of guild.roles) {
try {
const channel = client.channels.get(role.chanId);
// Skip if channel doesn't exist or role array is empty
if (!channel || !role.roles?.length) {
invalidRoles.push(role);
continue;
}
// Verify message exists
const message = await channel.fetchMessage(role.msgId).catch(() => null);
if (message) {
validRoles.push(role);
} else {
invalidRoles.push(role);
}
} catch (error) {
console.error(`Error processing role ${role.msgId} in guild ${guild.id}:`, error);
invalidRoles.push(role);
}
}
// Update guild with only valid roles if there were any invalid ones
if (invalidRoles.length > 0) {
await client.database.updateGuild(guild.id, {
roles: validRoles
});
} }
} }

View File

@ -1,26 +1,45 @@
function colors(altColorChar, textToTranslate) { /**
const colorMap = { * ANSI color codes mapping for terminal text coloring
[`${altColorChar}0`]: '\x1b[30m', // Black * @type {Object<string, string>}
[`${altColorChar}1`]: '\x1b[34m', // Dark Blue */
[`${altColorChar}2`]: '\x1b[32m', // Dark Green const ANSI_COLORS = {
[`${altColorChar}3`]: '\x1b[36m', // Dark Aqua '0': '\x1b[30m', // Black
[`${altColorChar}4`]: '\x1b[31m', // Dark Red '1': '\x1b[34m', // Dark Blue
[`${altColorChar}5`]: '\x1b[35m', // Dark Purple '2': '\x1b[32m', // Dark Green
[`${altColorChar}6`]: '\x1b[33m', // Gold '3': '\x1b[36m', // Dark Aqua
[`${altColorChar}7`]: '\x1b[37m', // Gray '4': '\x1b[31m', // Dark Red
[`${altColorChar}8`]: '\x1b[90m', // Dark Gray '5': '\x1b[35m', // Dark Purple
[`${altColorChar}9`]: '\x1b[94m', // Blue '6': '\x1b[33m', // Gold
[`${altColorChar}a`]: '\x1b[92m', // Green '7': '\x1b[37m', // Gray
[`${altColorChar}b`]: '\x1b[96m', // Aqua '8': '\x1b[90m', // Dark Gray
[`${altColorChar}c`]: '\x1b[91m', // Red '9': '\x1b[94m', // Blue
[`${altColorChar}d`]: '\x1b[95m', // Light Purple 'a': '\x1b[92m', // Green
[`${altColorChar}e`]: '\x1b[93m', // Yellow 'b': '\x1b[96m', // Aqua
[`${altColorChar}f`]: '\x1b[97m', // White 'c': '\x1b[91m', // Red
[`${altColorChar}r`]: '\x1b[0m', // Reset 'd': '\x1b[95m', // Light Purple
}; 'e': '\x1b[93m', // Yellow
'f': '\x1b[97m', // White
const regex = new RegExp(`${altColorChar}([0-9a-fr])`, 'g'); 'r': '\x1b[0m', // Reset
return textToTranslate.replace(regex, (match, code) => colorMap[`${altColorChar}${code}`] || '');
}; };
/**
* Translates color codes in text to ANSI color sequences
* @param {string} altColorChar - The character used to prefix color codes (e.g., '&' or '§')
* @param {string} textToTranslate - The text containing color codes to translate
* @returns {string} The text with color codes replaced by ANSI sequences
* @throws {Error} If altColorChar is not a single character
*/
function colors(altColorChar, textToTranslate) {
if (typeof altColorChar !== 'string' || altColorChar.length !== 1) {
throw new Error('altColorChar must be a single character');
}
if (typeof textToTranslate !== 'string') {
throw new Error('textToTranslate must be a string');
}
const regex = new RegExp(`${altColorChar}([0-9a-fr])`, 'g');
return textToTranslate.replace(regex, (_, code) => ANSI_COLORS[code] || '');
}
module.exports = colors; module.exports = colors;

View File

@ -1,11 +1,40 @@
function dhms(str, sec = false) { /**
const x = sec ? 1 : 1000; * Converts a time string (e.g., "1d2h3m4s") into milliseconds or seconds
if (typeof str !== 'string') return 0; * @param {string} timeStr - The time string to convert (e.g., "1d2h3m4s")
const fixed = str.replace(/\s/g, ''); * @param {boolean} [inSeconds=false] - If true, returns result in seconds instead of milliseconds
const tail = +fixed.match(/-?\d+$/g) || 0; * @returns {number} The converted time in milliseconds or seconds
const parts = (fixed.match(/-?\d+[^-0-9]+/g) || []) */
.map(v => +v.replace(/[^-0-9]+/g, '') * ({ s: x, m: 60 * x, h: 3600 * x, d: 86400 * x }[v.replace(/[-0-9]+/g, '')] || 0)); function dhms(timeStr, inSeconds = false) {
return [tail, ...parts].reduce((a, b) => a + b, 0); // Return 0 for invalid input
}; if (typeof timeStr !== 'string' || !timeStr.trim()) {
return 0;
}
// Define time unit multipliers
const multipliers = {
s: inSeconds ? 1 : 1000,
m: inSeconds ? 60 : 60000,
h: inSeconds ? 3600 : 3600000,
d: inSeconds ? 86400 : 86400000
};
// Remove whitespace and split into parts
const cleanStr = timeStr.replace(/\s/g, '');
// Extract the numeric value at the end (if any)
const tailMatch = cleanStr.match(/-?\d+$/);
const tailValue = tailMatch ? parseInt(tailMatch[0], 10) : 0;
// Extract and convert time parts
const timeParts = (cleanStr.match(/-?\d+[^-0-9]+/g) || [])
.map(part => {
const value = parseInt(part.replace(/[^-0-9]+/g, ''), 10);
const unit = part.replace(/[-0-9]+/g, '');
return value * (multipliers[unit] || 0);
});
// Sum all parts including the tail
return [tailValue, ...timeParts].reduce((sum, value) => sum + value, 0);
}
module.exports = dhms; module.exports = dhms;

View File

@ -1,16 +1,34 @@
function fetchTime(ms, client, lang) { /**
var totalSeconds = (ms / 1000); * Converts milliseconds into a human-readable time string with localized units
let years = Math.floor(totalSeconds / 31536000); * @param {number} milliseconds - The time in milliseconds to convert
totalSeconds %= 31536000; * @param {Object} client - The client object containing translation functionality
let days = Math.floor(totalSeconds / 86400); * @param {string} lang - The language code for translations
totalSeconds %= 86400; * @returns {string} Formatted time string with localized units
let hours = Math.floor(totalSeconds / 3600); */
totalSeconds %= 3600; function fetchTime(milliseconds, client, lang) {
let minutes = Math.floor(totalSeconds / 60); if (!Number.isFinite(milliseconds) || milliseconds < 0) {
let seconds = totalSeconds % 60; throw new Error('Invalid milliseconds value provided');
seconds = Math.floor(seconds); }
return `${years ? `${years} ${client.translate.get(lang, "Functions.fetchTime.years")},` : ""} ${days ? `${days} ${client.translate.get(lang, "Functions.fetchTime.days")},` : ""} ${hours ? `${hours} ${client.translate.get(lang, "Functions.fetchTime.hours")},` : ""} ${minutes ? `${minutes} ${client.translate.get(lang, "Functions.fetchTime.minutes")},` : ""} ${seconds} ${client.translate.get(lang, "Functions.fetchTime.seconds")}`; const timeUnits = [
{ value: 31536000, key: 'years' },
{ value: 86400, key: 'days' },
{ value: 3600, key: 'hours' },
{ value: 60, key: 'minutes' },
{ value: 1, key: 'seconds' }
];
let remainingSeconds = Math.floor(milliseconds / 1000);
return timeUnits
.map(({ value, key }) => {
const unitValue = Math.floor(remainingSeconds / value);
remainingSeconds %= value;
return unitValue ? `${unitValue} ${client.translate.get(lang, `Functions.fetchTime.${key}`)},` : '';
})
.filter(Boolean)
.join(' ')
.trim();
} }
module.exports = fetchTime; module.exports = fetchTime;

View File

@ -1,38 +1,105 @@
/** /**
* base code taken from https://github.com/Mirasaki/logger * Enhanced logger utility with date-fns for date formatting
* Original code inspired by https://github.com/Mirasaki/logger
*/ */
const chalk = require('chalk'), const chalk = require('chalk');
moment = require('moment'); const { format, formatInTimeZone } = require('date-fns-tz');
const tagList = { // Define log levels and their corresponding styles
SYSLOG: chalk.greenBright('[SYSLOG]'), const LOG_LEVELS = {
SYSERR: chalk.redBright('[SYSERR]'), SYSLOG: { color: 'greenBright', prefix: '[SYSLOG]' },
SUCCESS: chalk.greenBright('[SUCCESS]'), SYSERR: { color: 'redBright', prefix: '[SYSERR]' },
INFO: chalk.blueBright('[INFO]'), SUCCESS: { color: 'greenBright', prefix: '[SUCCESS]' },
DEBUG: chalk.magentaBright('[DEBUG]'), INFO: { color: 'blueBright', prefix: '[INFO]' },
DATA: chalk.yellowBright('[DATA]'), DEBUG: { color: 'magentaBright', prefix: '[DEBUG]' },
COMMAND: chalk.whiteBright('[CMD]'), DATA: { color: 'yellowBright', prefix: '[DATA]' },
EVENT: chalk.cyanBright('[EVENT]'), COMMAND: { color: 'whiteBright', prefix: '[CMD]' },
ERROR: chalk.redBright('[EVENT]'), EVENT: { color: 'cyanBright', prefix: '[EVENT]' },
WARN: chalk.yellowBright('[WARN]') ERROR: { color: 'redBright', prefix: '[ERROR]' },
WARN: { color: 'yellowBright', prefix: '[WARN]' }
}; };
// Create tag list with chalk styling
const tagList = Object.entries(LOG_LEVELS).reduce((acc, [key, { color, prefix }]) => ({
...acc,
[key]: chalk[color](prefix)
}), {});
// Calculate longest tag length for alignment
const longestTagLength = Math.max(...Object.values(tagList).map(t => t.length)); const longestTagLength = Math.max(...Object.values(tagList).map(t => t.length));
const getTag = (tag) => `${tagList[tag]}${''.repeat(longestTagLength - tagList[tag].length)}`;
const timestamp = () => `${chalk.whiteBright.bold(`[${moment.utc().format('YYYY-MM-DD HH:mm:ss')}]`)}`; /**
* Get formatted tag with proper spacing
* @param {string} tag - The log level tag
* @returns {string} Formatted tag with spacing
*/
const getTag = (tag) => `${tagList[tag]}${' '.repeat(longestTagLength - tagList[tag].length)}`;
/**
* Get formatted timestamp
* @returns {string} Formatted timestamp with styling
*/
const timestamp = () => {
const now = new Date();
const formattedDate = formatInTimeZone(now, 'UTC', 'yyyy-MM-dd HH:mm:ss');
return chalk.whiteBright.bold(`[${formattedDate}]`);
};
/**
* Create a log message with proper formatting
* @param {string} level - Log level
* @param {string} type - Optional type identifier
* @param {string} message - Log message
* @returns {string} Formatted log message
*/
const createLogMessage = (level, type, message) => {
const typeTag = type ? `${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : ':';
return `${timestamp()} ${getTag(level)} ${typeTag} ${message}`;
};
/**
* Format error stack trace with proper styling
* @param {Error} err - Error object
* @returns {string} Formatted error stack trace
*/
const formatErrorStack = (err) => {
if (!err.stack) return chalk.red(err);
return err.stack
.split('\n')
.map((msg, index) => {
if (index === 0) return chalk.red(msg);
const isFailedFunctionCall = index === 1;
const traceStartIndex = msg.indexOf('(');
const traceEndIndex = msg.lastIndexOf(')');
const hasTrace = traceStartIndex !== -1;
const functionCall = msg.slice(
msg.indexOf('at') + 3,
hasTrace ? traceStartIndex - 1 : msg.length
);
const trace = msg.slice(traceStartIndex, traceEndIndex + 1);
return ` ${chalk.grey('at')} ${isFailedFunctionCall
? `${chalk.redBright(functionCall)} ${chalk.red.underline(trace)}`
: `${chalk.greenBright(functionCall)} ${chalk.grey(trace)}`
}`;
})
.join('\n');
};
module.exports = { module.exports = {
syslog: (type, str) => console.info(`${timestamp()} ${type ? `${getTag('SYSLOG')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('SYSLOG')}:`} ${str}`), syslog: (type, str) => console.info(createLogMessage('SYSLOG', type, str)),
syserr: (type, str) => console.error(`${timestamp()} ${type ? `${getTag('SYSERR')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('SYSERR')} :`} ${str}`), syserr: (type, str) => console.error(createLogMessage('SYSERR', type, str)),
success: (type, str) => console.log(`${timestamp()} ${type ? `${getTag('SUCCESS')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('SUCCESS')}:`} ${str}`), success: (type, str) => console.log(createLogMessage('SUCCESS', type, str)),
info: (type, str) => console.info(`${timestamp()} ${type ? `${getTag('INFO')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('INFO')}:`} ${str}`), info: (type, str) => console.info(createLogMessage('INFO', type, str)),
debug: (type, str) => console.log(`${timestamp()} ${type ? `${getTag('DEBUG')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('DEBUG')}:`} ${str}`), debug: (type, str) => console.log(createLogMessage('DEBUG', type, str)),
data: (type, str) => console.log(`${timestamp()} ${type ? `${getTag('DATA')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('DATA')}:`} ${str}`), data: (type, str) => console.log(createLogMessage('DATA', type, str)),
command: (type, str) => console.log(`${timestamp()} ${type ? `${getTag('COMMAND')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('COMMAND')}:`} ${str}`), command: (type, str) => console.log(createLogMessage('COMMAND', type, str)),
event: (type, str) => console.log(`${timestamp()} ${type ? `${getTag('EVENT')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('EVENT')}:`} ${str}`), event: (type, str) => console.log(createLogMessage('EVENT', type, str)),
error: (type, str) => console.log(`${timestamp()} ${type ? `${getTag('ERROR')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('ERROR')}:`} ${str}`), error: (type, str) => console.log(createLogMessage('ERROR', type, str)),
warn: (type, str) => console.log(`${timestamp()} ${type ? `${getTag('WARN')} ${chalk.whiteBright.bgBlue.bold(`[${type}]`)}:` : `${getTag('WARN')}:`} ${str}`), warn: (type, str) => console.log(createLogMessage('WARN', type, str)),
startLog: (identifier) => console.log(`${timestamp()} ${getTag('DEBUG')} ${chalk.greenBright('[START]')} ${identifier}`), startLog: (identifier) => console.log(`${timestamp()} ${getTag('DEBUG')} ${chalk.greenBright('[START]')} ${identifier}`),
endLog: (identifier) => console.log(`${timestamp()} ${getTag('DEBUG')} ${chalk.redBright('[ END ]')} ${identifier}`), endLog: (identifier) => console.log(`${timestamp()} ${getTag('DEBUG')} ${chalk.redBright('[ END ]')} ${identifier}`),
@ -40,46 +107,17 @@ module.exports = {
timestamp, timestamp,
getExecutionTime: (hrtime) => { getExecutionTime: (hrtime) => {
const timeSinceHrMs = ( const timeSinceHrMs = (
process.hrtime(hrtime)[0] * 1000 process.hrtime(hrtime)[0] * 1000 +
+ hrtime[1] / 1000000 hrtime[1] / 1000000
).toFixed(2); ).toFixed(2);
return `${chalk.yellowBright( return `${chalk.yellowBright((timeSinceHrMs / 1000).toFixed(2))} seconds (${chalk.yellowBright(timeSinceHrMs)} ms)`;
(timeSinceHrMs / 1000).toFixed(2))
} seconds (${chalk.yellowBright(timeSinceHrMs)} ms)`;
}, },
printErr: (err) => { printErr: (err) => {
if (!(err instanceof Error)) { if (!(err instanceof Error)) {
console.error(err) console.error(err);
return; return;
} }
console.error(formatErrorStack(err));
console.error(
!err.stack
? chalk.red(err)
: err.stack
.split('\n')
.map((msg, index) => {
if (index === 0) {
return chalk.red(msg);
}
const isFailedFunctionCall = index === 1;
const traceStartIndex = msg.indexOf('(');
const traceEndIndex = msg.lastIndexOf(')');
const hasTrace = traceStartIndex !== -1;
const functionCall = msg.slice(
msg.indexOf('at') + 3,
hasTrace ? traceStartIndex - 1 : msg.length
);
const trace = msg.slice(traceStartIndex, traceEndIndex + 1);
return ` ${chalk.grey('at')} ${isFailedFunctionCall
? `${chalk.redBright(functionCall)} ${chalk.red.underline(trace)}`
: `${chalk.greenBright(functionCall)} ${chalk.grey(trace)}`
}`;
})
.join('\n')
)
} }
}; };

View File

@ -1,72 +1,129 @@
const Embed = require("../functions/embed"); const path = require("path");
const Embed = require(path.join(__dirname, "../functions/embed"));
/**
* Validates and processes role assignments from a message
* @param {Object} client - The client instance
* @param {Object} message - The message object
* @param {Object} db - Database instance
* @returns {Promise<void>}
*/
async function Collector(client, message, db) { async function Collector(client, message, db) {
const regex = /{role:(.*?)}/; const ROLE_REGEX = /{role:(.*?)}/g;
const regexAll = /{role:(.*?)}/g; const MAX_ROLES = 20;
// Get collector for the message author
const collector = client.messageCollector.get(message.authorId); const collector = client.messageCollector.get(message.authorId);
if (!message.content.match(regexAll) || message.content.match(regexAll)?.length === 0) {
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(`${client.translate.get(db.language, "Events.messageCreate.noRoles")}: \`{role:Red}\``)] }, false).catch(() => { return }); // Validate message contains role tags
return message.react(client.config.emojis.cross).catch(() => { return }); const roleMatches = message.content.match(ROLE_REGEX);
if (!roleMatches?.length) {
await sendErrorResponse(message, client, db, "Events.messageCreate.noRoles", `\`{role:Red}\``);
return;
} }
const roles = message.content.match(regexAll).map((r) => r?.match(regex)[1]); // Extract role names from message
if (roles.length > 20) { const roleNames = roleMatches.map(match => match.match(/{role:(.*?)}/)[1]);
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(client.translate.get(db.language, "Events.messageCreate.maxRoles"))] }, false).catch(() => { return });
return message.react(client.config.emojis.cross).catch(() => { return }); // Check role count limit
} if (roleNames.length > MAX_ROLES) {
collector.regex = roles await sendErrorResponse(message, client, db, "Events.messageCreate.maxRoles");
const roleIds = [] return;
let newRoles = roles.map((r) => {
return [...message.server.roles].map((r) => r).find((role) => r.toLowerCase() === role[1]?.name?.toLowerCase());
})
newRoles.map((r) => roleIds.push(r));
if (roleIds.map((r) => !r).includes(true)) {
let unknown = [];
roleIds.map((r, i) => {
i++
if (!r) {
unknown.push(roles[i - 1]);
}
});
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(`${client.translate.get(db.language, "Events.messageCreate.unknown")}\n${unknown.map(e => `\`{role:${e}}\``).join(", ")}`)] }, false).catch(() => { return });
return message.react(client.config.emojis.cross).catch(() => { return });
} }
let duplicate = []; collector.regex = roleNames;
roleIds.map((r, i) => {
i++
if (roleIds.filter(e => e[0] === r[0]).length > 1) duplicate.push(roleIds[i - 1]);
});
if (duplicate.length > 0) { // Find matching server roles
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(`${client.translate.get(db.language, "Events.messageCreate.duplicate")}\n${duplicate.map(e => `\`{role:${e[1].name}}\``)}`)] }, false).catch(() => { return }); const serverRoles = [...message.server.roles].map(([id, role]) => ({ id, role }));
return message.react(client.config.emojis.cross).catch(() => { return }); const matchedRoles = roleNames.map(name =>
serverRoles.find(({ role }) => role.name.toLowerCase() === name.toLowerCase())
);
// Check for unknown roles
const unknownRoles = roleNames.filter((name, index) => !matchedRoles[index]);
if (unknownRoles.length > 0) {
await sendErrorResponse(
message,
client,
db,
"Events.messageCreate.unknown",
unknownRoles.map(role => `\`{role:${role}}\``).join(", ")
);
return;
} }
let positions = []; // Check for duplicate roles
const botRole = message.channel.server.member.orderedRoles.reverse()[0] const roleIds = matchedRoles.map(({ id }) => id);
const duplicates = roleIds.filter((id, index) => roleIds.indexOf(id) !== index);
if (duplicates.length > 0) {
const duplicateRoles = duplicates.map(id =>
matchedRoles.find(({ id: roleId }) => roleId === id).role.name
);
await sendErrorResponse(
message,
client,
db,
"Events.messageCreate.duplicate",
duplicateRoles.map(name => `\`{role:${name}}\``).join(", ")
);
return;
}
// Check bot role permissions
const botRole = message.channel.server.member.orderedRoles.reverse()[0];
if (!botRole) { if (!botRole) {
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(client.translate.get(db.language, "Events.messageCreate.noBotRole"))] }, false).catch(() => { return }); await sendErrorResponse(message, client, db, "Events.messageCreate.noBotRole");
return message.react(client.config.emojis.cross).catch(() => { return }); return;
} }
roleIds.map((r, i) => { // Check role positions
i++ const invalidPositions = matchedRoles.filter(({ role }) => role.rank <= botRole.rank);
if (r[1].rank <= botRole.rank) positions.push(roleIds[i - 1]) if (invalidPositions.length > 0) {
}); await sendErrorResponse(
message,
if (positions.length > 0) { client,
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(`${client.translate.get(db.language, "Events.messageCreate.positions")}\n${positions.map(e => `\`{role:${e[1].name}}\``)}`)] }, false).catch(() => { return }); db,
return message.react(client.config.emojis.cross).catch(() => { return }); "Events.messageCreate.positions",
invalidPositions.map(({ role }) => `\`{role:${role.name}}\``).join(", ")
);
return;
} }
message.delete().catch(() => { }); // Process valid role assignment
collector.roles = roleIds; await message.delete().catch(() => {});
collector.roles = matchedRoles;
const react = [client.config.emojis.check]; const react = [client.config.emojis.check];
return message.channel.sendMessage(collector.type === "content" ? { content: message.content, interactions: [react] } : { embeds: [new Embed().setDescription(message.content).setColor("#A52F05")], interactions: [react] }).then((msg) => { const messageContent = collector.type === "content"
collector.messageId = msg.id; ? { content: message.content, interactions: [react] }
}); : {
embeds: [new Embed().setDescription(message.content).setColor("#A52F05")],
interactions: [react]
};
const sentMessage = await message.channel.sendMessage(messageContent);
collector.messageId = sentMessage.id;
}
/**
* Helper function to send error responses
* @param {Object} message - The message object
* @param {Object} client - The client instance
* @param {Object} db - Database instance
* @param {string} translationKey - Translation key for the error message
* @param {string} [additionalInfo] - Additional information to append to the error message
*/
async function sendErrorResponse(message, client, db, translationKey, additionalInfo = "") {
const errorMessage = additionalInfo
? `${client.translate.get(db.language, translationKey)}\n${additionalInfo}`
: client.translate.get(db.language, translationKey);
await message.reply(
{ embeds: [new Embed().setColor("#FF0000").setDescription(errorMessage)] },
false
).catch(() => {});
await message.react(client.config.emojis.cross).catch(() => {});
} }
module.exports = Collector; module.exports = Collector;

View File

@ -1,72 +1,157 @@
const Embed = require("../functions/embed"); const path = require("path");
async function Collector(client, message, db) { const Embed = require(path.join(__dirname, "../functions/embed"));
const regex = /{role:(.*?)}/;
const regexAll = /{role:(.*?)}/g; /**
const collector = client.messageEdit.get(message.authorId); * Validates role mentions in a message
if (!message.content.match(regexAll) || message.content.match(regexAll)?.length === 0) { * @param {string} content - Message content to validate
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(`${client.translate.get(db.language, "Events.messageCreate.noRoles")}: \`{role:Red}\``)] }, false).catch(() => { return }); * @param {string} language - User's language setting
return message.react(client.config.emojis.cross).catch(() => { return }); * @param {Object} client - Discord client instance
* @returns {Object} Validation result with roles and error message if any
*/
function validateRoleMentions(content, language, client) {
const regex = /{role:(.*?)}/g;
const matches = content.match(regex);
if (!matches || matches.length === 0) {
return {
isValid: false,
error: client.translate.get(language, "Events.messageCreate.noRoles")
};
} }
const roles = message.content.match(regexAll).map((r) => r?.match(regex)[1]); const roles = matches.map(match => match.match(/{role:(.*?)}/)[1]);
if (roles.length > 20) { if (roles.length > 20) {
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(client.translate.get(db.language, "Events.messageCreate.maxRoles"))] }, false).catch(() => { return }); return {
return message.react(client.config.emojis.cross).catch(() => { return }); isValid: false,
} error: client.translate.get(language, "Events.messageCreate.maxRoles")
collector.regex = roles };
const roleIds = []
let newRoles = roles.map((r) => {
return [...message.server.roles].map((r) => r).find((role) => r.toLowerCase() === role[1]?.name?.toLowerCase());
})
newRoles.map((r) => roleIds.push(r));
if (roleIds.map((r) => !r).includes(true)) {
let unknown = [];
roleIds.map((r, i) => {
i++
if (!r) {
unknown.push(roles[i - 1]);
}
});
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(`${client.translate.get(db.language, "Events.messageCreate.unknown")}\n${unknown.map(e => `\`{role:${e}}\``).join(", ")}`)] }, false).catch(() => { return });
return message.react(client.config.emojis.cross).catch(() => { return });
} }
let duplicate = []; return { isValid: true, roles };
roleIds.map((r, i) => { }
i++
if (roleIds.filter(e => e[0] === r[0]).length > 1) duplicate.push(roleIds[i - 1]);
});
if (duplicate.length > 0) { /**
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(`${client.translate.get(db.language, "Events.messageCreate.duplicate")}\n${duplicate.map(e => `\`{role:${e[1].name}}\``)}`)] }, false).catch(() => { return }); * Finds server roles by name
return message.react(client.config.emojis.cross).catch(() => { return }); * @param {Array} roles - Array of role names
* @param {Object} server - Server object
* @returns {Object} Result containing found roles and unknown roles
*/
function findServerRoles(roles, server) {
const roleIds = roles.map(roleName =>
[...server.roles].find(([_, role]) =>
roleName.toLowerCase() === role.name.toLowerCase()
)
);
const unknown = roles.filter((_, index) => !roleIds[index]);
return { roleIds, unknown };
}
/**
* Checks for duplicate roles
* @param {Array} roleIds - Array of role IDs
* @returns {Array} Array of duplicate roles
*/
function findDuplicateRoles(roleIds) {
return roleIds.filter((role, index) =>
roleIds.findIndex(r => r[0] === role[0]) !== index
);
}
/**
* Checks role positions against bot's role
* @param {Array} roleIds - Array of role IDs
* @param {Object} botRole - Bot's highest role
* @returns {Array} Array of roles with invalid positions
*/
function checkRolePositions(roleIds, botRole) {
return roleIds.filter(role => role[1].rank <= botRole.rank);
}
/**
* Main collector function for message editing
* @param {Object} client - Discord client instance
* @param {Object} message - Message object
* @param {Object} db - Database object
*/
async function Collector(client, message, db) {
const collector = client.messageEdit.get(message.authorId);
// Validate role mentions
const validation = validateRoleMentions(message.content, db.language, client);
if (!validation.isValid) {
await message.reply({
embeds: [new Embed().setColor("#FF0000").setDescription(validation.error)]
}, false).catch(() => {});
return message.react(client.config.emojis.cross).catch(() => {});
} }
let positions = []; // Find server roles
const botRole = message.channel.server.member.orderedRoles.reverse()[0] const { roleIds, unknown } = findServerRoles(validation.roles, message.server);
if (unknown.length > 0) {
await message.reply({
embeds: [new Embed().setColor("#FF0000").setDescription(
`${client.translate.get(db.language, "Events.messageCreate.unknown")}\n${
unknown.map(e => `\`{role:${e}}\``).join(", ")
}`
)]
}, false).catch(() => {});
return message.react(client.config.emojis.cross).catch(() => {});
}
// Check for duplicates
const duplicates = findDuplicateRoles(roleIds);
if (duplicates.length > 0) {
await message.reply({
embeds: [new Embed().setColor("#FF0000").setDescription(
`${client.translate.get(db.language, "Events.messageCreate.duplicate")}\n${
duplicates.map(e => `\`{role:${e[1].name}}\``)
}`
)]
}, false).catch(() => {});
return message.react(client.config.emojis.cross).catch(() => {});
}
// Check role positions
const botRole = message.channel.server.member.orderedRoles.reverse()[0];
if (!botRole) { if (!botRole) {
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(client.translate.get(db.language, "Events.messageCreate.noBotRole"))] }, false).catch(() => { return }); await message.reply({
return message.react(client.config.emojis.cross).catch(() => { return }); embeds: [new Embed().setColor("#FF0000").setDescription(
client.translate.get(db.language, "Events.messageCreate.noBotRole")
)]
}, false).catch(() => {});
return message.react(client.config.emojis.cross).catch(() => {});
} }
roleIds.map((r, i) => { const invalidPositions = checkRolePositions(roleIds, botRole);
i++ if (invalidPositions.length > 0) {
if (r[1].rank <= botRole.rank) positions.push(roleIds[i - 1]) await message.reply({
}); embeds: [new Embed().setColor("#FF0000").setDescription(
`${client.translate.get(db.language, "Events.messageCreate.positions")}\n${
if (positions.length > 0) { invalidPositions.map(e => `\`{role:${e[1].name}}\``)
message.reply({ embeds: [new Embed().setColor("#FF0000").setDescription(`${client.translate.get(db.language, "Events.messageCreate.positions")}\n${positions.map(e => `\`{role:${e[1].name}}\``)}`)] }, false).catch(() => { return }); }`
return message.react(client.config.emojis.cross).catch(() => { return }); )]
}, false).catch(() => {});
return message.react(client.config.emojis.cross).catch(() => {});
} }
message.delete().catch(() => { }); // Process valid message
await message.delete().catch(() => {});
collector.roles = roleIds; collector.roles = roleIds;
collector.regex = validation.roles;
const react = [client.config.emojis.check]; const react = [client.config.emojis.check];
return message.channel.sendMessage(collector.type === "content" ? { content: message.content, interactions: [react] } : { embeds: [new Embed().setDescription(message.content).setColor("#A52F05")], interactions: [react] }).then((msg) => { const messageContent = collector.type === "content"
collector.messageId = msg.id; ? { content: message.content, interactions: [react] }
}); : {
embeds: [new Embed().setDescription(message.content).setColor("#A52F05")],
interactions: [react]
};
const msg = await message.channel.sendMessage(messageContent);
collector.messageId = msg.id;
} }
module.exports = Collector; module.exports = Collector;

View File

@ -1,34 +1,104 @@
const PollDB = require("../models/polls"); const path = require("path");
const Embed = require("./embed"); const PollDB = require(path.join(__dirname, "../models/polls"));
const Embed = require(path.join(__dirname, "./embed"));
const Canvas = require('canvas'); const Canvas = require('canvas');
const format = `${(new Date().getMonth() + 1) < 10 ? `0${new Date().getMonth() + 1}` : new Date().getMonth() + 1}/${new Date().getDate()}/${new Date().getFullYear()} ${new Date().getHours()}:${(new Date().getMinutes() < 10 ? '0' : '') + new Date().getMinutes()}`;
// Constants
const CANVAS_CONFIG = {
WIDTH: 600,
PADDING: 10,
BAR_HEIGHT: 40,
FONT_SIZES: {
SMALL: '12px',
MEDIUM: '17px'
},
COLORS: {
BACKGROUND: "#23272A",
TEXT_PRIMARY: "#FFFFFF",
TEXT_SECONDARY: "#4E535A",
BAR_BACKGROUND: "#2C2F33",
BAR_FILL: "#A52F05",
BAR_INACTIVE: "#24282B",
SELECTION: "#717cf4"
}
};
// Helper function to format date
const getFormattedDate = () => {
const now = new Date();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
return `${month}/${day}/${now.getFullYear()} ${hours}:${minutes}`;
};
/**
* Polls class for managing and rendering polls
*/
class Polls { class Polls {
constructor({ time, client, name, options, users, avatars, votes, owner, lang }) { /**
* @param {Object} options - Poll configuration options
* @param {number} options.time - Duration of the poll in milliseconds
* @param {Object} options.client - Discord client instance
* @param {Object} options.name - Poll name and description
* @param {Object} options.options - Poll options configuration
* @param {string[]} [options.users] - Array of user IDs who voted
* @param {string[]} [options.avatars] - Array of user avatar URLs
* @param {number[]} [options.votes] - Array of vote counts
* @param {string} options.owner - Poll owner ID
* @param {string} options.lang - Language code
*/
constructor({ time, client, name, options, users = [], avatars = [], votes, owner, lang }) {
this.client = client; this.client = client;
this.time = time; this.time = time;
if (votes) this.votes = votes;
else this.votes = options.name.length === 2 ? [0, 0] : options.name.length === 3 ? [0, 0, 0] : options.name.length === 4 ? [0, 0, 0, 0] : options.name.length === 5 ? [0, 0, 0, 0, 0] : options.name.length === 6 ? [0, 0, 0, 0, 0, 0] : options.name.length === 7 ? [0, 0, 0, 0, 0, 0, 0] : options.name.length === 8 ? [0, 0, 0, 0, 0, 0, 0, 0] : options.name.length === 9 ? [0, 0, 0, 0, 0, 0, 0, 0, 0] : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
this.users = users || [];
this.avatars = avatars || [];
this.options = { name: name.name, description: name.description };
this.voteOptions = options;
this.owner = owner; this.owner = owner;
this.lang = lang; this.lang = lang;
this.size = { canvas: options.name.length === 2 ? 200 : options.name.length === 3 ? 250 : options.name.length === 4 ? 300 : options.name.length === 5 ? 350 : options.name.length === 6 ? 400 : options.name.length === 7 ? 450 : options.name.length === 8 ? 500 : options.name.length === 9 ? 550 : 600, bar: options.name.length === 2 ? 150 : options.name.length === 3 ? 200 : options.name.length === 4 ? 250 : options.name.length === 5 ? 300 : options.name.length === 6 ? 350 : options.name.length === 7 ? 400 : options.name.length === 8 ? 450 : options.name.length === 9 ? 500 : 550 }; this.users = users;
this.avatars = avatars;
this.options = { name: name.name, description: name.description };
this.voteOptions = options;
// Initialize votes array based on number of options
this.votes = votes || new Array(options.name.length).fill(0);
// Calculate canvas dimensions based on number of options
this.size = this.calculateCanvasSize(options.name.length);
} }
/**
* Calculate canvas dimensions based on number of options
* @param {number} optionCount - Number of poll options
* @returns {Object} Canvas and bar dimensions
*/
calculateCanvasSize(optionCount) {
const baseSize = 200;
const increment = 50;
const size = baseSize + (optionCount - 2) * increment;
return {
canvas: size,
bar: size - 50
};
}
/**
* Start the poll
* @param {Object} message - Discord message object
* @param {Object} poll - Poll instance
*/
async start(message, poll) { async start(message, poll) {
this.client.polls.set(message.id, { poll, messageId: message.id, users: this.users, owner: this.owner, lang: this.lang }) this.client.polls.set(message.id, {
setTimeout(async () => { poll,
if (!this.client.polls.get(message.id)) return; messageId: message.id,
await this.update(); users: this.users,
message.edit({ embeds: [new Embed().setMedia(await this.client.Uploader.upload(poll.canvas.toBuffer(), `Poll.png`)).setColor(`#F24646`)], content: this.client.translate.get(this.lang, "Functions.poll.end") }).catch(() => { }) owner: this.owner,
this.client.polls.delete(message.id); lang: this.lang
await PollDB.findOneAndDelete({ messageId: message.id }); });
}, this.time);
if (this.time < 0) return; if (this.time < 0) return;
await (new PollDB({
// Save poll to database
await new PollDB({
owner: this.owner, owner: this.owner,
channelId: message.channelId, channelId: message.channelId,
messageId: message.id, messageId: message.id,
@ -41,208 +111,325 @@ class Polls {
time: this.time, time: this.time,
lang: this.lang, lang: this.lang,
now: Date.now(), now: Date.now(),
}).save()); }).save();
}
textHeight(text, ctx, m) { // Set poll end timer
let metrics = m || ctx.measureText(text); setTimeout(async () => {
return metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; if (!this.client.polls.get(message.id)) return;
}
await this.update();
roundRect(ctx, x, y, width, height, radius, fill, stroke) { // Credit to https://stackoverflow.com/users/227299/juan-mendes const endMessage = this.client.translate.get(this.lang, "Functions.poll.end");
if (typeof stroke === 'undefined') {
stroke = true; try {
} await message.edit({
if (typeof radius === 'undefined') { embeds: [new Embed()
radius = 5; .setMedia(await this.client.Uploader.upload(poll.canvas.toBuffer(), 'Poll.png'))
} .setColor('#F24646')],
if (typeof radius === 'number') { content: endMessage
radius = { tl: radius, tr: radius, br: radius, bl: radius }; });
} else { } catch (error) {
var defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 }; console.error('Failed to edit poll message:', error);
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
} }
}
ctx.beginPath(); this.client.polls.delete(message.id);
ctx.moveTo(x + radius.tl, y); await PollDB.findOneAndDelete({ messageId: message.id });
ctx.lineTo(x + width - radius.tr, y); }, this.time);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
} }
async update() { /**
let roundRect = this.roundRect; * Calculate text height for canvas
let textHeight = this.textHeight; * @param {string} text - Text to measure
* @param {CanvasRenderingContext2D} ctx - Canvas context
* @param {TextMetrics} [metrics] - Optional text metrics
* @returns {number} Text height
*/
textHeight(text, ctx, metrics) {
const m = metrics || ctx.measureText(text);
return m.actualBoundingBoxAscent + m.actualBoundingBoxDescent;
}
var width = 600, height = this.size.canvas, padding = 10; /**
const canvas = Canvas.createCanvas(width, height); * Draw rounded rectangle on canvas
* @param {CanvasRenderingContext2D} ctx - Canvas context
* @param {number} x - X coordinate
* @param {number} y - Y coordinate
* @param {number} width - Rectangle width
* @param {number} height - Rectangle height
* @param {number|Object} radius - Corner radius
* @param {boolean} fill - Whether to fill the rectangle
* @param {boolean} stroke - Whether to stroke the rectangle
*/
roundRect(ctx, x, y, width, height, radius = 5, fill = true, stroke = true) {
const r = typeof radius === 'number'
? { tl: radius, tr: radius, br: radius, bl: radius }
: { tl: 0, tr: 0, br: 0, bl: 0, ...radius };
ctx.beginPath();
ctx.moveTo(x + r.tl, y);
ctx.lineTo(x + width - r.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + r.tr);
ctx.lineTo(x + width, y + height - r.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - r.br, y + height);
ctx.lineTo(x + r.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - r.bl);
ctx.lineTo(x, y + r.tl);
ctx.quadraticCurveTo(x, y, x + r.tl, y);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
/**
* Update the poll canvas
*/
async update() {
const { WIDTH, PADDING, BAR_HEIGHT, COLORS, FONT_SIZES } = CANVAS_CONFIG;
const height = this.size.canvas;
// Create canvas
const canvas = Canvas.createCanvas(WIDTH, height);
this.canvas = canvas; this.canvas = canvas;
const ctx = this.canvas.getContext('2d'); const ctx = this.canvas.getContext('2d');
this.ctx = ctx; this.ctx = ctx;
let name = this.options.name?.length > 70 ? this.options?.name.slice(0, 67) + "..." : this.options.name; // Draw background
var nameHeight = textHeight(name, ctx); ctx.fillStyle = COLORS.BACKGROUND;
this.roundRect(ctx, 0, 0, WIDTH, height, 5, true, false);
let description = this.options.description.length > 80 ? this.options.description.slice(0, 77) + "..." : this.options.description; // Draw title and description
var descHeight = textHeight(description, ctx); const name = this.truncateText(this.options.name, 70);
const description = this.truncateText(this.options.description, 80);
const nameHeight = this.textHeight(name, ctx);
const descHeight = this.textHeight(description, ctx);
ctx.fillStyle = "#23272A"; // Draw title
roundRect(ctx, 0, 0, width, height, 5, true, false); // background ctx.fillStyle = COLORS.TEXT_SECONDARY;
ctx.font = `normal ${FONT_SIZES.SMALL} Sans-Serif`;
ctx.fillText(name, PADDING, PADDING + 2 + nameHeight / 2);
ctx.fillStyle = "#4E535A"; // Draw description
ctx.font = `normal 12px Sans-Serif`; ctx.fillStyle = COLORS.TEXT_PRIMARY;
ctx.fillText(name, padding, padding + 2 + nameHeight / 2); // name ctx.font = `normal ${FONT_SIZES.MEDIUM} Sans-Serif`;
ctx.fillText(description, PADDING, PADDING + 15 + nameHeight + descHeight / 2);
ctx.fillStyle = "#FFFFFF"; const headerHeight = PADDING + descHeight + nameHeight + 15;
ctx.font = `normal 17px Sans-Serif`; const dataWidth = WIDTH - PADDING * 2;
ctx.fillText(description, padding, padding + 15 + nameHeight + descHeight / 2); // description
var headerHeight = padding + descHeight + nameHeight + 15; // Draw vote bars
var dataWidth = width - padding * 2; this.drawVoteBars(ctx, dataWidth - 20, BAR_HEIGHT, this.votes,
var barHeight = 40; { pad: PADDING, hHeight: headerHeight },
var votes = this.votes; this.voteOptions.name);
var names = this.voteOptions.name;
this.drawVoteBars(ctx, dataWidth - 20, barHeight, votes, { pad: padding, hHeight: headerHeight }, names); // Draw footer
await this.drawFooter(ctx, padding, padding + headerHeight + barHeight * 2 + 20, width, height, padding, this.avatars); await this.drawFooter(ctx, PADDING, PADDING + headerHeight + BAR_HEIGHT * 2 + 20,
WIDTH, height, PADDING, this.avatars);
} }
/**
* Truncate text with ellipsis
* @param {string} text - Text to truncate
* @param {number} maxLength - Maximum length
* @returns {string} Truncated text
*/
truncateText(text, maxLength) {
return text?.length > maxLength ? text.slice(0, maxLength - 3) + "..." : text;
}
/**
* Add a vote to the poll
* @param {number} option - Option index
* @param {string} user - User ID
* @param {string} avatar - User avatar URL
* @param {string} id - Message ID
* @returns {Canvas} Updated canvas
*/
async addVote(option, user, avatar, id) { async addVote(option, user, avatar, id) {
if (this.avatars.length === 6) this.avatars.shift(); if (this.avatars.length === 6) this.avatars.shift();
this.avatars.push(avatar); this.avatars.push(avatar);
this.votes[option]++; this.votes[option]++;
await PollDB.findOneAndUpdate({ messageId: id }, { $push: { users: user, avatars: avatar }, $inc: { [`votes.${option}`]: 1 } });
await PollDB.findOneAndUpdate(
{ messageId: id },
{
$push: { users: user, avatars: avatar },
$inc: { [`votes.${option}`]: 1 }
}
);
await this.update(); await this.update();
return this.canvas; return this.canvas;
} }
/**
* Remove a vote from the poll
* @param {number} option - Option index
* @param {string} user - User ID
* @param {string} avatar - User avatar URL
* @param {string} id - Message ID
* @returns {Canvas} Updated canvas
*/
async removeVote(option, user, avatar, id) { async removeVote(option, user, avatar, id) {
this.avatars.splice(this.avatars.indexOf(avatar), 1); this.avatars.splice(this.avatars.indexOf(avatar), 1);
this.votes[option]--; this.votes[option]--;
await PollDB.findOneAndUpdate({ messageId: id }, { $pull: { users: user, avatars: avatar }, $inc: { [`votes.${option}`]: -1 } });
await PollDB.findOneAndUpdate(
{ messageId: id },
{
$pull: { users: user, avatars: avatar },
$inc: { [`votes.${option}`]: -1 }
}
);
await this.update(); await this.update();
return this.canvas; return this.canvas;
} }
/**
* Draw vote bars on canvas
* @param {CanvasRenderingContext2D} ctx - Canvas context
* @param {number} width - Bar width
* @param {number} height - Bar height
* @param {number[]} votes - Vote counts
* @param {Object} vars - Position variables
* @param {string[]} names - Option names
* @param {number} [vote] - Selected vote index
*/
drawVoteBars(ctx, width, height, votes, vars, names, vote) { drawVoteBars(ctx, width, height, votes, vars, names, vote) {
let roundRect = this.roundRect; const { PADDING, COLORS } = CANVAS_CONFIG;
let textHeight = this.textHeight; const { pad: padding, hHeight: headerHeight } = vars;
let padding = vars.pad;
let headerHeight = vars.hHeight; const sum = votes.reduce((prev, curr) => prev + curr, 0);
let sum = votes.reduce((prev, curr) => prev + curr); const percentages = votes.map(v => Math.floor(v / (sum / 100) * 10) / 10);
let percentages = votes.map((v) => Math.floor(v / (sum / 100) * 10) / 10);
ctx.save(); ctx.save();
ctx.translate(padding, padding + headerHeight); ctx.translate(padding, padding + headerHeight);
var barPadding = 5; const barPadding = 5;
percentages.forEach((percentage, i) => { percentages.forEach((percentage, i) => {
if (!percentage) percentage = 0; const y = (height + 10) * i;
let paddingLeft = (vote != undefined) ? 30 : 0; const paddingLeft = vote !== undefined ? 30 : 0;
ctx.fillStyle = "#2C2F33"; // Draw bar background
let y = (height + 10) * i; ctx.fillStyle = COLORS.BAR_BACKGROUND;
roundRect(ctx, 20, y, width, height, 5, true, false); // full bar this.roundRect(ctx, 20, y, width, height, 5, true, false);
if (vote == i || percentage) { ctx.fillStyle = "#A52F05"; } // Draw bar fill
else { ctx.fillStyle = "#24282B"; } // percentage display ctx.fillStyle = (vote === i || percentage) ? COLORS.BAR_FILL : COLORS.BAR_INACTIVE;
roundRect(ctx, 20, y, width * (votes[i] / (sum / 100) / 100), height, 5, true, false); this.roundRect(ctx, 20, y, width * (votes[i] / (sum / 100) / 100), height, 5, true, false);
ctx.fillStyle = "#4E535A"; // Draw option number
let h = textHeight(i + 1, ctx); ctx.fillStyle = COLORS.TEXT_SECONDARY;
ctx.fillText(i + 1, 0, y + height / 2 + h / 2); const numHeight = this.textHeight(i + 1, ctx);
ctx.fillText(i + 1, 0, y + height / 2 + numHeight / 2);
ctx.fillStyle = "#FFFFFF"; // Option names // Draw option name
h = textHeight(names[i], ctx); ctx.fillStyle = COLORS.TEXT_PRIMARY;
ctx.fillText(names[i].length > 65 ? names[i].slice(0, 62) + "..." : names[i], 30 + paddingLeft, y + 13 + h); const name = this.truncateText(names[i], 65);
const nameHeight = this.textHeight(name, ctx);
ctx.fillText(name, 30 + paddingLeft, y + 13 + nameHeight);
if (vote != undefined) { // Draw selection circle if voting
ctx.strokeStyle = "#FFFFFF"; // selection circle if (vote !== undefined) {
ctx.fillStyle = "#717cf4"; ctx.strokeStyle = COLORS.TEXT_PRIMARY;
ctx.fillStyle = COLORS.SELECTION;
ctx.beginPath(); ctx.beginPath();
ctx.arc(35, y + 10 + h * 0.75, 6, 0, 2 * Math.PI); ctx.arc(35, y + 10 + nameHeight * 0.75, 6, 0, 2 * Math.PI);
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();
if (vote == i) {
if (vote === i) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(35, y + 10 + h * 0.75, 3, 0, 2 * Math.PI); ctx.arc(35, y + 10 + nameHeight * 0.75, 3, 0, 2 * Math.PI);
ctx.closePath(); ctx.closePath();
ctx.fill(); ctx.fill();
} }
} }
ctx.fillStyle = "#2C2F33"; // percentage and vote count background // Draw percentage and vote count
let metrics = ctx.measureText(percentage + "% (" + votes[i] + ")"); const voteText = `${percentage}% (${votes[i]})`;
let w = metrics.width; const metrics = ctx.measureText(voteText);
h = textHeight(percentage + "% (" + votes[i] + ")", ctx, metrics); const textHeight = this.textHeight(voteText, ctx, metrics);
y = y + (height - h - barPadding * 2) + barPadding * 2.6; const textY = y + (height - textHeight - barPadding * 2) + barPadding * 2.6;
if (vote == i || vote == undefined) roundRect(ctx, width - barPadding - w - 3, y - h - 4, w + 5, h + 12, 5, true, false);
ctx.fillStyle = "#A52F05"; // percentage and vote count if (vote === i || vote === undefined) {
ctx.fillText(percentage + "% (" + votes[i] + ")", width - barPadding - w, y); ctx.fillStyle = COLORS.BAR_BACKGROUND;
this.roundRect(ctx, width - barPadding - metrics.width - 3,
textY - textHeight - 4, metrics.width + 5, textHeight + 12, 5, true, false);
}
ctx.fillStyle = COLORS.BAR_FILL;
ctx.fillText(voteText, width - barPadding - metrics.width, textY);
}); });
ctx.restore(); ctx.restore();
} }
/**
* Draw footer on canvas
* @param {CanvasRenderingContext2D} ctx - Canvas context
* @param {number} x - X coordinate
* @param {number} y - Y coordinate
* @param {number} width - Canvas width
* @param {number} height - Canvas height
* @param {number} padding - Padding value
* @param {string[]} users - Array of user avatar URLs
*/
async drawFooter(ctx, x, y, width, height, padding, users) { async drawFooter(ctx, x, y, width, height, padding, users) {
const { COLORS } = CANVAS_CONFIG;
ctx.save(); ctx.save();
ctx.translate(10, this.size.bar); ctx.translate(10, this.size.bar);
var rad = 18; const rad = 18;
ctx.fillStyle = "#4E535A"; ctx.fillStyle = COLORS.TEXT_SECONDARY;
ctx.lineWidth = 2; ctx.lineWidth = 2;
ctx.strokeStyle = "#4E535A"; ctx.strokeStyle = COLORS.TEXT_SECONDARY;
// Draw separator line
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(0 - padding, 0); ctx.moveTo(0 - padding, 0);
ctx.lineTo(width, 0); ctx.lineTo(width, 0);
ctx.stroke(); ctx.stroke();
let votes = (this.votes.reduce((p, c) => p + c) == 1) ? `${this.votes.reduce((p, c) => p + c)} vote` : `${this.votes.reduce((p, c) => p + c)} votes`; // Draw vote count
let metrics = ctx.measureText(votes); const totalVotes = this.votes.reduce((p, c) => p + c, 0);
let h = this.textHeight(votes, ctx, metrics); const voteText = totalVotes === 1 ? '1 vote' : `${totalVotes} votes`;
ctx.fillText(votes, 5, rad + h); const metrics = ctx.measureText(voteText);
const textHeight = this.textHeight(voteText, ctx, metrics);
ctx.fillText(voteText, 5, rad + textHeight);
// Avatars // Draw avatars
var pos = rad * users.length + 10 + metrics.width; let pos = rad * users.length + 10 + metrics.width;
var yPos = 6; const yPos = 6;
users.reverse(); users.reverse();
for (let i = 0; i < users.length; i++) {
ctx.beginPath();
let user = users[i];
const a = Canvas.createCanvas(rad * 2, rad * 2); for (const avatar of users) {
const context = a.getContext("2d"); const avatarCanvas = Canvas.createCanvas(rad * 2, rad * 2);
const avatarCtx = avatarCanvas.getContext('2d');
context.beginPath(); avatarCtx.beginPath();
context.arc(rad, rad, rad, 0, Math.PI * 2, true); avatarCtx.arc(rad, rad, rad, 0, Math.PI * 2, true);
context.closePath(); avatarCtx.closePath();
context.clip(); avatarCtx.clip();
const avatar = await Canvas.loadImage(user); try {
context.drawImage(avatar, 0, 0, rad * 2, rad * 2); const avatarImage = await Canvas.loadImage(avatar);
ctx.drawImage(a, pos, yPos); avatarCtx.drawImage(avatarImage, 0, 0, rad * 2, rad * 2);
ctx.drawImage(avatarCanvas, pos, yPos);
} catch (error) {
console.error('Failed to load avatar:', error);
}
ctx.closePath();
pos -= rad; pos -= rad;
} }
// Date // Draw date
let date = format; const date = getFormattedDate();
metrics = ctx.measureText(date); const dateMetrics = ctx.measureText(date);
h = this.textHeight(date, ctx, metrics); const dateHeight = this.textHeight(date, ctx, dateMetrics);
ctx.fillText(date, width - 15 - metrics.width, rad + h); ctx.fillText(date, width - 15 - dateMetrics.width, rad + dateHeight);
ctx.restore(); ctx.restore();
} }
} }

View File

@ -1,5 +1,4 @@
const crypt = require("crypto"); const crypt = require("crypto");
// const FlakeId = require('flakeid');
module.exports = { module.exports = {
/** /**

View File

@ -1,48 +1,109 @@
/**
* Reloads a module (event, function, or command) in the client
* @param {Object} client - The Discord client instance
* @param {string} category - The category of the module ('events', 'functions', or command name)
* @param {string} [name] - The name of the module (required for events and functions)
* @returns {string} Status message indicating success or failure
*/
function Reload(client, category, name) { function Reload(client, category, name) {
if (category === "events") { // Input validation
if (!name) return 'Provide an event name to reload!' if (!client) throw new Error('Client instance is required');
try { if (!category) return 'Provide a category/command name to reload!';
const evtName = name;
delete require.cache[require.resolve(`../events/${name}.js`)];
const pull = require(`../events/${name}`);
client.off(evtName, typeof client._events[evtName] == 'function' ? client._events[evtName] : client._events[evtName][0]) const reloaders = {
client.event.delete(evtName) events: () => reloadEvent(client, name),
functions: () => reloadFunction(client, name),
default: () => reloadCommand(client, category)
};
client.on(evtName, pull.bind(null, client)) return (reloaders[category] || reloaders.default)();
client.event.set(evtName, pull.bind(null, client)) }
} catch (e) {
return `Couldn't reload: **${category}/${name}**\n**Error**: ${e.message}`
}
return `Reloaded event: **${name}**.js`
}
if (category === "functions") {
if (!name) return 'Provide a function name to reload!'
try {
const evtName = name;
delete require.cache[require.resolve(`../functions/${name}.js`)];
const pull = require(`../functions/${name}`);
client.functions.delete(evtName)
client.functions.set(evtName, pull)
} catch (e) {
return `Couldn't reload: **functions/${name}**\n**Error**: ${e.message}`
}
return `Reloaded function: **${name}**.js`
}
/**
* Reloads an event module
* @param {Object} client - The Discord client instance
* @param {string} name - The name of the event
* @returns {string} Status message
*/
function reloadEvent(client, name) {
if (!name) return 'Provide an event name to reload!';
try { try {
if (!category) return 'Provide a command name to reload!' const eventPath = `../events/${name}.js`;
delete require.cache[require.resolve(`../commands/${category}.js`)]; delete require.cache[require.resolve(eventPath)];
const pull = require(`../commands/${category}.js`); const eventModule = require(eventPath);
if (client.commands.get(category).config.aliases) client.commands.get(category).config.aliases.forEach(a => client.aliases.delete(a));
client.commands.delete(category); // Remove existing event listener
client.commands.set(category, pull); const existingHandler = client._events[name];
if (client.commands.get(category).config.aliases) client.commands.get(category).config.aliases.forEach(a => client.aliases.set(a, category)); if (existingHandler) {
return `Reloaded command: **commands/${category}**.js` client.off(name, typeof existingHandler === 'function' ? existingHandler : existingHandler[0]);
} catch (e) { }
return `Couldn't reload: **commands/${category}**\n**Error**: ${e.message}` client.event.delete(name);
// Add new event listener
const boundHandler = eventModule.bind(null, client);
client.on(name, boundHandler);
client.event.set(name, boundHandler);
return `Reloaded event: **${name}**.js`;
} catch (error) {
return `Couldn't reload: **events/${name}**\n**Error**: ${error.message}`;
}
}
/**
* Reloads a function module
* @param {Object} client - The Discord client instance
* @param {string} name - The name of the function
* @returns {string} Status message
*/
function reloadFunction(client, name) {
if (!name) return 'Provide a function name to reload!';
try {
const functionPath = `../functions/${name}.js`;
delete require.cache[require.resolve(functionPath)];
const functionModule = require(functionPath);
client.functions.delete(name);
client.functions.set(name, functionModule);
return `Reloaded function: **${name}**.js`;
} catch (error) {
return `Couldn't reload: **functions/${name}**\n**Error**: ${error.message}`;
}
}
/**
* Reloads a command module
* @param {Object} client - The Discord client instance
* @param {string} commandName - The name of the command
* @returns {string} Status message
*/
function reloadCommand(client, commandName) {
try {
const commandPath = `../commands/${commandName}.js`;
delete require.cache[require.resolve(commandPath)];
const commandModule = require(commandPath);
// Handle aliases
const existingCommand = client.commands.get(commandName);
if (existingCommand?.config?.aliases) {
existingCommand.config.aliases.forEach(alias => client.aliases.delete(alias));
}
// Update command
client.commands.delete(commandName);
client.commands.set(commandName, commandModule);
// Update aliases if they exist
if (commandModule.config?.aliases) {
commandModule.config.aliases.forEach(alias => client.aliases.set(alias, commandName));
}
return `Reloaded command: **commands/${commandName}**.js`;
} catch (error) {
return `Couldn't reload: **commands/${commandName}**\n**Error**: ${error.message}`;
} }
} }

View File

@ -1,12 +1,50 @@
const { readdirSync } = require("fs") const { readdirSync } = require("fs")
const { join } = require("path")
const color = require("../functions/colorCodes") const color = require("../functions/colorCodes")
module.exports = (client) => {
const commands = readdirSync(`./commands/`).filter(d => d.endsWith('.js'));
for (let file of commands) {
let pull = require(`../commands/${file}`);
client.commands.set(pull.config.name, pull);
if (pull.config.aliases) pull.config.aliases.forEach(a => client.aliases.set(a, pull.config.name));
};
console.log(color("%", `%b[Command_Handler]%7 :: Loaded %e${client.commands.size} %7commands`)); /**
* Loads and registers all command files from the commands directory
* @param {Object} client - The Discord client instance
* @returns {Promise<void>}
*/
module.exports = async (client) => {
try {
const commandsPath = join(__dirname, "..", "commands")
const commandFiles = readdirSync(commandsPath).filter(file => file.endsWith(".js"))
let loadedCommands = 0
let failedCommands = 0
for (const file of commandFiles) {
try {
const command = require(join(commandsPath, file))
// Validate command structure
if (!command.config?.name) {
console.error(color("%", `%r[Command_Handler]%7 :: Command in ${file} is missing required config.name property`))
failedCommands++
continue
}
// Register command and aliases
client.commands.set(command.config.name, command)
if (command.config.aliases?.length) {
command.config.aliases.forEach(alias => client.aliases.set(alias, command.config.name))
}
loadedCommands++
} catch (error) {
console.error(color("%", `%r[Command_Handler]%7 :: Failed to load command ${file}: ${error.message}`))
failedCommands++
}
}
console.log(color("%", `%b[Command_Handler]%7 :: Successfully loaded %e${loadedCommands}%7 commands`))
if (failedCommands > 0) {
console.warn(color("%", `%y[Command_Handler]%7 :: Failed to load %r${failedCommands}%7 commands`))
}
} catch (error) {
console.error(color("%", `%r[Command_Handler]%7 :: Critical error: ${error.message}`))
throw error // Re-throw to handle at higher level
}
} }

View File

@ -1,11 +1,24 @@
const { connect } = require("mongoose").set('strictQuery', true); const { connect } = require("mongoose").set('strictQuery', true);
const color = require("../functions/colorCodes") const color = require("../functions/colorCodes")
const fs = require('fs');
const path = require('path');
module.exports = class DatabaseHandler { module.exports = class DatabaseHandler {
constructor(connectionString) { constructor(connectionString) {
this.cache = new Map(); this.cache = new Map();
this.guildModel = require('../models/guilds');
this.connectionString = connectionString; this.connectionString = connectionString;
this.models = {};
this.initializeModels();
}
initializeModels() {
const modelsPath = path.join(__dirname, '../models');
const modelFiles = fs.readdirSync(modelsPath).filter(file => file.endsWith('.js'));
for (const file of modelFiles) {
const modelName = path.parse(file).name;
this.models[modelName] = require(path.join(modelsPath, file));
}
} }
cacheSweeper(client) { cacheSweeper(client) {
@ -46,17 +59,17 @@ module.exports = class DatabaseHandler {
} }
async fetchGuild(guildId, createIfNotFound = false) { async fetchGuild(guildId, createIfNotFound = false) {
const fetched = await this.guildModel.findOne({ id: guildId }); const fetched = await this.models.guilds.findOne({ id: guildId });
if (fetched) return fetched; if (fetched) return fetched;
if (!fetched && createIfNotFound) { if (!fetched && createIfNotFound) {
await this.guildModel.create({ await this.models.guilds.create({
id: guildId, id: guildId,
language: 'en_EN', language: 'en_EN',
botJoined: Date.now() / 1000 | 0, botJoined: Date.now() / 1000 | 0,
}); });
return this.guildModel.findOne({ id: guildId }); return this.models.guilds.findOne({ id: guildId });
} return null; } return null;
} }
@ -78,7 +91,7 @@ module.exports = class DatabaseHandler {
async deleteGuild(guildId, onlyCache = false) { async deleteGuild(guildId, onlyCache = false) {
if (this.cache.has(guildId)) this.cache.delete(guildId); if (this.cache.has(guildId)) this.cache.delete(guildId);
return !onlyCache ? this.guildModel.deleteMany({ id: guildId }) : true; return !onlyCache ? this.models.guilds.deleteMany({ id: guildId }) : true;
} }
async updateGuild(guildId, data = {}, createIfNotFound = false) { async updateGuild(guildId, data = {}, createIfNotFound = false) {
@ -91,13 +104,13 @@ module.exports = class DatabaseHandler {
this.cache.set(guildId, data); this.cache.set(guildId, data);
return this.guildModel.updateOne({ return this.models.guilds.updateOne({
id: guildId, id: guildId,
}, data); }, data);
} return null; } return null;
} }
async getAll() { async getAll() {
return this.guildModel.find(); return this.models.guilds.find();
} }
}; };

View File

@ -1,12 +1,44 @@
const { readdirSync } = require("fs") const { readdirSync } = require("fs")
const path = require("path")
const color = require("../functions/colorCodes") const color = require("../functions/colorCodes")
module.exports = (client) => {
const events = readdirSync(`./events/`).filter(d => d.endsWith('.js'));
for (let file of events) {
let evt = require(`../events/${file}`);
client.event.set(file.split(".")[0], evt.bind(null, client));
client.on(file.split('.')[0], evt.bind(null, client));
};
console.log(color("%", `%b[Event_Handler]%7 :: Loaded %e${client.event.size} %7events`)); /**
}; * Loads and registers all event handlers for the client
* @param {Object} client - The Discord client instance
* @returns {Promise<void>}
*/
module.exports = async (client) => {
try {
const eventsPath = path.join(__dirname, "..", "events")
const eventFiles = readdirSync(eventsPath)
.filter(file => file.endsWith(".js"))
const loadedEvents = await Promise.all(
eventFiles.map(async (file) => {
try {
const eventName = path.parse(file).name
const event = require(path.join(eventsPath, file))
// Register the event
client.event.set(eventName, event.bind(null, client))
client.on(eventName, event.bind(null, client))
return eventName
} catch (error) {
console.error(color("%", `%r[Event_Handler]%7 :: Failed to load event %e${file}%7: ${error.message}`))
return null
}
})
)
const successfulEvents = loadedEvents.filter(Boolean)
console.log(color("%", `%b[Event_Handler]%7 :: Successfully loaded %e${successfulEvents.length}%7 events`))
if (successfulEvents.length < eventFiles.length) {
console.warn(color("%", `%y[Event_Handler]%7 :: %r${eventFiles.length - successfulEvents.length}%7 events failed to load`))
}
} catch (error) {
console.error(color("%", `%r[Event_Handler]%7 :: Critical error: ${error.message}`))
throw error // Re-throw to handle it in the main application
}
}

View File

@ -1,11 +1,40 @@
const { readdirSync } = require("fs") const { readdirSync } = require("fs")
const { join } = require("path")
const logger = require("../functions/logger") const logger = require("../functions/logger")
module.exports = (client) => {
const functions = readdirSync(`./functions`).filter(d => d.endsWith('.js'));
for (let file of functions) {
let evt = require(`../functions/${file}`);
client.functions.set(file.split(".")[0], evt);
};
logger.event('Function_Handler', `Loaded ${client.functions.size} functions`); /**
* Loads and registers all function modules from the functions directory
* @param {Object} client - The client instance
* @returns {Promise<void>}
*/
module.exports = async (client) => {
try {
if (!client || !client.functions) {
throw new Error('Invalid client object or missing functions collection');
}
const functionsDir = join(__dirname, '..', 'functions');
const functions = readdirSync(functionsDir)
.filter(file => file.endsWith('.js') && file !== 'logger.js');
const loadedFunctions = await Promise.all(
functions.map(async (file) => {
try {
const functionName = file.split('.')[0];
const functionModule = require(join(functionsDir, file));
client.functions.set(functionName, functionModule);
return functionName;
} catch (error) {
logger.error('Function_Handler', `Failed to load function ${file}: ${error.message}`);
return null;
}
})
);
const successfulLoads = loadedFunctions.filter(Boolean).length;
logger.event('Function_Handler', `Successfully loaded ${successfulLoads}/${functions.length} functions`);
} catch (error) {
logger.error('Function_Handler', `Failed to initialize function handler: ${error.message}`);
throw error;
}
}; };

125
index.js
View File

@ -2,57 +2,102 @@ const { Client } = require("revolt.js");
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const { token, mongoDB, api } = require("./botconfig.json"); const { token, mongoDB, api } = require("./botconfig.json");
const logger = require('./functions/logger'); const logger = require('./functions/logger');
const checkPolls = require('./functions/checkPolls') const checkPolls = require('./functions/checkPolls');
const color = require("./functions/colorCodes"); const color = require("./functions/colorCodes");
const client = new Client({ baseURL: api });
const Uploader = require("revolt-uploader"); const Uploader = require("revolt-uploader");
const fetch = require("wumpfetch");
const TranslationHandler = require('./handlers/translation'); const TranslationHandler = require('./handlers/translation');
const DatabaseHandler = require('./handlers/database'); const DatabaseHandler = require('./handlers/database');
client.Uploader = new Uploader(client); class Bot {
client.config = require("./config"); constructor(config) {
client.translate = new TranslationHandler(); this.config = config;
client.logger = require('./functions/logger'); this.client = new Client({ baseURL: config.api });
client.botConfig = require("./botconfig.json"); this.initializeCore();
this.initializeCollections();
this.setupErrorHandling();
}
client.database = new DatabaseHandler(mongoDB); initializeCore() {
client.database.connectToDatabase(); this.client.Uploader = new Uploader(this.client);
client.database.cacheSweeper(client); this.client.config = require("./config");
client.database.guildSweeper(client); this.client.translate = new TranslationHandler();
this.client.logger = logger;
this.client.botConfig = this.config;
}
["reactions", "paginate", "timeout", "polls", "used", "messageCollector", "messageEdit"].forEach(x => client[x] = new Map()); initializeCollections() {
["aliases", "commands", "event", "functions"].forEach(x => client[x] = new Collection()); const collections = ["aliases", "commands", "event", "functions"];
["command", "event", "function"].forEach(x => require(`./handlers/${x}`)(client)); const maps = ["reactions", "paginate", "timeout", "polls", "used", "messageCollector", "messageEdit"];
client.once("ready", async () => { collections.forEach(x => this.client[x] = new Collection());
logger.success('Bot Ready', `${client.user.username} is ready`); maps.forEach(x => this.client[x] = new Map());
}
//client.database.connectToDatabase(); async initializeDatabase() {
//client.database.cacheSweeper(client); try {
//client.database.guildSweeper(client); const db = new DatabaseHandler(this.config.mongoDB);
await db.connectToDatabase();
this.client.database = db;
this.client.models = db.models;
db.cacheSweeper(this.client);
db.guildSweeper(this.client);
logger.success('Database', 'Successfully connected to database');
} catch (error) {
logger.error('Database Error', error);
throw error;
}
}
await checkPolls(client); setupErrorHandling() {
const errorTypes = {
"unhandledRejection": "Unhandled Rejection/Catch",
"uncaughtException": "Uncaught Exception/Catch",
"uncaughtExceptionMonitor": "Uncaught Exception/Catch (MONITOR)"
};
}); Object.entries(errorTypes).forEach(([event, message]) => {
process.on(event, (error, origin) => {
logger.error('Error Handling', `${message}: ${error.message}`);
console.log(color("%", `%4[Error_Handling] :: ${message}%c`));
console.log(error);
if (origin) console.log(origin);
});
});
}
async initializeHandlers() {
try {
["command", "event", "function"].forEach(x => require(`./handlers/${x}`)(this.client));
logger.success('Handlers', 'Successfully initialized all handlers');
} catch (error) {
logger.error('Handler Error', error);
throw error;
}
}
process.on("unhandledRejection", (reason, p) => { setupEventListeners() {
console.log(color("%", "%4[Error_Handling] :: Unhandled Rejection/Catch%c")); this.client.once("ready", async () => {
console.log(reason); logger.success('Bot Ready', `${this.client.user.username} is ready`);
console.log(p) await checkPolls(this.client);
}); });
process.on("uncaughtException", (err, origin) => { }
console.log(color("%", "%4[Error_Handling] :: Uncaught Exception/Catch%c"));
console.log(err);
console.log(origin)
});
process.on("uncaughtExceptionMonitor", (err, origin) => {
console.log(color("%", "%4[Error_Handling] :: Uncaught Exception/Catch (MONITOR)%c"));
console.log(err);
console.log(origin)
});
client.loginBot(token); async start() {
try {
await this.initializeDatabase();
await this.initializeHandlers();
this.setupEventListeners();
await this.client.loginBot(this.config.token);
} catch (error) {
logger.error('Startup Error', error);
process.exit(1);
}
}
}
// Start the bot
const bot = new Bot({ token, mongoDB, api });
bot.start();

10
package-lock.json generated
View File

@ -16,7 +16,7 @@
"mongoose": "^7.2.0", "mongoose": "^7.2.0",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"node-fetch-commonjs": "^3.3.2", "node-fetch-commonjs": "^3.3.2",
"revolt-uploader": "^1.1.1", "revolt-uploader": "^1.1.5",
"revolt.js": "npm:revolt.js-update@^7.0.0-beta.9", "revolt.js": "npm:revolt.js-update@^7.0.0-beta.9",
"screen": "^0.2.10", "screen": "^0.2.10",
"wumpfetch": "^0.3.1" "wumpfetch": "^0.3.1"
@ -1227,10 +1227,10 @@
} }
}, },
"node_modules/revolt-uploader": { "node_modules/revolt-uploader": {
"version": "1.1.4", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/revolt-uploader/-/revolt-uploader-1.1.4.tgz", "resolved": "https://registry.npmjs.org/revolt-uploader/-/revolt-uploader-1.1.6.tgz",
"integrity": "sha512-hnUCc1grg6Yq1J2q0HcsbfbWCvHIR6hPHzk3/75EjWEjP7bPYwAAVsHOwwtfdMtwMUPN9EAmBj3NvU4t+o1JJw==", "integrity": "sha512-teMUhz/QJDqx/sXJFFReur8kR/KUSl2A5dYy9hLY/KLLgpaZw+el4bV+TgFb0vPtpM4hg7mLn44gLiuBR7kD7g==",
"license": "ISC", "license": "MIT",
"dependencies": { "dependencies": {
"form-data": "^4.0.0", "form-data": "^4.0.0",
"node-fetch": "^2.6.7" "node-fetch": "^2.6.7"

View File

@ -15,7 +15,7 @@
"mongoose": "^7.2.0", "mongoose": "^7.2.0",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"node-fetch-commonjs": "^3.3.2", "node-fetch-commonjs": "^3.3.2",
"revolt-uploader": "^1.1.1", "revolt-uploader": "^1.1.5",
"revolt.js": "npm:revolt.js-update@^7.0.0-beta.9", "revolt.js": "npm:revolt.js-update@^7.0.0-beta.9",
"screen": "^0.2.10", "screen": "^0.2.10",
"wumpfetch": "^0.3.1" "wumpfetch": "^0.3.1"