diff --git a/assets/gaydar.jpg b/assets/gaydar.jpg new file mode 100644 index 0000000..d7a0000 Binary files /dev/null and b/assets/gaydar.jpg differ diff --git a/commands/gaydar.js b/commands/gaydar.js index 6334ee1..ce7ab1a 100644 --- a/commands/gaydar.js +++ b/commands/gaydar.js @@ -1,5 +1,7 @@ +const fs = require("fs"); +const path = require("path"); const Embed = require("../functions/embed") -// const { } = require('revolt.js') +const Uploader = require("revolt-uploader"); module.exports = { config: { @@ -8,33 +10,90 @@ module.exports = { cooldown: 5000, available: true, permissions: [], + roles: [], + dm: false, aliases: ['gd'] }, run: async (client, message, args, db) => { - function randomInteger(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; + try { + + function randomInteger(min, max) { + 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; - // }); }, }; \ No newline at end of file diff --git a/events/messageCreate.js b/events/messageCreate.js index 49a1fc0..ded25b9 100644 --- a/events/messageCreate.js +++ b/events/messageCreate.js @@ -3,8 +3,6 @@ const Collector = require("../functions/messageCollector"); const EditCollector = require("../functions/messageEdit"); const CommandDB = require('../models/commands'); const { isJson } = require('../functions/randomStr'); -const logger = require('../functions/logger'); -const audit = require('../functions/audit'); module.exports = async (client, message) => { // Early return checks diff --git a/handlers/command.js b/handlers/command.js index ce381b4..9e42424 100644 --- a/handlers/command.js +++ b/handlers/command.js @@ -1,12 +1,50 @@ const { readdirSync } = require("fs") +const { join } = require("path") 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} + */ +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 + } } \ No newline at end of file diff --git a/handlers/database.js b/handlers/database.js index 338615d..226b439 100644 --- a/handlers/database.js +++ b/handlers/database.js @@ -1,11 +1,24 @@ const { connect } = require("mongoose").set('strictQuery', true); const color = require("../functions/colorCodes") +const fs = require('fs'); +const path = require('path'); module.exports = class DatabaseHandler { constructor(connectionString) { this.cache = new Map(); - this.guildModel = require('../models/guilds'); 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) { @@ -46,17 +59,17 @@ module.exports = class DatabaseHandler { } 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 && createIfNotFound) { - await this.guildModel.create({ + await this.models.guilds.create({ id: guildId, language: 'en_EN', botJoined: Date.now() / 1000 | 0, }); - return this.guildModel.findOne({ id: guildId }); + return this.models.guilds.findOne({ id: guildId }); } return null; } @@ -78,7 +91,7 @@ module.exports = class DatabaseHandler { async deleteGuild(guildId, onlyCache = false) { 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) { @@ -91,13 +104,13 @@ module.exports = class DatabaseHandler { this.cache.set(guildId, data); - return this.guildModel.updateOne({ + return this.models.guilds.updateOne({ id: guildId, }, data); } return null; } async getAll() { - return this.guildModel.find(); + return this.models.guilds.find(); } }; \ No newline at end of file diff --git a/handlers/event.js b/handlers/event.js index 55c74dd..6a967f8 100644 --- a/handlers/event.js +++ b/handlers/event.js @@ -1,12 +1,44 @@ const { readdirSync } = require("fs") +const path = require("path") 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`)); -}; \ No newline at end of file +/** + * Loads and registers all event handlers for the client + * @param {Object} client - The Discord client instance + * @returns {Promise} + */ +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 + } +} \ No newline at end of file diff --git a/handlers/function.js b/handlers/function.js index be5637c..c77ebdd 100644 --- a/handlers/function.js +++ b/handlers/function.js @@ -1,11 +1,40 @@ const { readdirSync } = require("fs") +const { join } = require("path") 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} + */ +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; + } }; \ No newline at end of file diff --git a/index.js b/index.js index bb29e1e..6930b7c 100644 --- a/index.js +++ b/index.js @@ -2,57 +2,102 @@ const { Client } = require("revolt.js"); const { Collection } = require('@discordjs/collection'); const { token, mongoDB, api } = require("./botconfig.json"); const logger = require('./functions/logger'); -const checkPolls = require('./functions/checkPolls') - +const checkPolls = require('./functions/checkPolls'); const color = require("./functions/colorCodes"); - -const client = new Client({ baseURL: api }); const Uploader = require("revolt-uploader"); -const fetch = require("wumpfetch"); const TranslationHandler = require('./handlers/translation'); const DatabaseHandler = require('./handlers/database'); -client.Uploader = new Uploader(client); -client.config = require("./config"); -client.translate = new TranslationHandler(); -client.logger = require('./functions/logger'); -client.botConfig = require("./botconfig.json"); +class Bot { + constructor(config) { + this.config = config; + this.client = new Client({ baseURL: config.api }); + this.initializeCore(); + this.initializeCollections(); + this.setupErrorHandling(); + } -client.database = new DatabaseHandler(mongoDB); -client.database.connectToDatabase(); -client.database.cacheSweeper(client); -client.database.guildSweeper(client); + initializeCore() { + this.client.Uploader = new Uploader(this.client); + this.client.config = require("./config"); + 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()); -["aliases", "commands", "event", "functions"].forEach(x => client[x] = new Collection()); -["command", "event", "function"].forEach(x => require(`./handlers/${x}`)(client)); + initializeCollections() { + const collections = ["aliases", "commands", "event", "functions"]; + const maps = ["reactions", "paginate", "timeout", "polls", "used", "messageCollector", "messageEdit"]; -client.once("ready", async () => { - logger.success('Bot Ready', `${client.user.username} is ready`); + collections.forEach(x => this.client[x] = new Collection()); + maps.forEach(x => this.client[x] = new Map()); + } -//client.database.connectToDatabase(); -//client.database.cacheSweeper(client); -//client.database.guildSweeper(client); + async initializeDatabase() { + try { + 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) => { - console.log(color("%", "%4[Error_Handling] :: Unhandled Rejection/Catch%c")); - console.log(reason); - console.log(p) -}); -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) -}); + setupEventListeners() { + this.client.once("ready", async () => { + logger.success('Bot Ready', `${this.client.user.username} is ready`); + await checkPolls(this.client); + }); + } -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(); diff --git a/package-lock.json b/package-lock.json index 37a1cc1..3a079c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "mongoose": "^7.2.0", "nanoid": "3.3.4", "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", "screen": "^0.2.10", "wumpfetch": "^0.3.1" @@ -1227,10 +1227,10 @@ } }, "node_modules/revolt-uploader": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/revolt-uploader/-/revolt-uploader-1.1.4.tgz", - "integrity": "sha512-hnUCc1grg6Yq1J2q0HcsbfbWCvHIR6hPHzk3/75EjWEjP7bPYwAAVsHOwwtfdMtwMUPN9EAmBj3NvU4t+o1JJw==", - "license": "ISC", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/revolt-uploader/-/revolt-uploader-1.1.6.tgz", + "integrity": "sha512-teMUhz/QJDqx/sXJFFReur8kR/KUSl2A5dYy9hLY/KLLgpaZw+el4bV+TgFb0vPtpM4hg7mLn44gLiuBR7kD7g==", + "license": "MIT", "dependencies": { "form-data": "^4.0.0", "node-fetch": "^2.6.7" diff --git a/package.json b/package.json index a1e4348..bf2fa4b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "mongoose": "^7.2.0", "nanoid": "3.3.4", "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", "screen": "^0.2.10", "wumpfetch": "^0.3.1"