Stryker-SC/Discord-Bot-VCMaster-JavaScript-Revamped
0
1const fs = require('fs');2const path = require('path');3const { Client, Collection, GatewayIntentBits, Partials } = require('discord.js');4const config = require('./config');5const db = require('./database');6const logger = require('./utils/logger');7 8const clientOptions = {9 intents: [10 GatewayIntentBits.Guilds,11 GatewayIntentBits.GuildVoiceStates,12 GatewayIntentBits.GuildMessages,13 GatewayIntentBits.MessageContent,14 GatewayIntentBits.GuildMembers, // Needed for moving members15 GatewayIntentBits.GuildPresences, // Needed for dynamic channel naming based on user activity16 ],17 partials: [Partials.Channel] // Sometimes needed for dm channels, but good practice18};19 20// If a REST proxy URL is provided via environment variables, configure the client to use it.21// This is essential for Hugging Face Spaces to bypass aggressive outbound TCP blocking.22if (process.env.REST_PROXY_URL) {23 const cleanProxyUrl = process.env.REST_PROXY_URL.replace(/\/+$/, '');24 clientOptions.rest = {25 api: cleanProxyUrl26 };27 logger.info(`[Proxy Setup] Using Custom REST API Proxy: ${cleanProxyUrl}`);28}29 30const client = new Client(clientOptions);31 32client.commands = new Collection();33client.db = db; // Attach db to client for easy access34client.roleConfigCache = new Map(); // Global cache for auto-role configuration35 36// Load Commands37const foldersPath = path.join(__dirname, 'commands');38const commandFolders = fs.readdirSync(foldersPath);39 40for (const folder of commandFolders) {41 const commandsPath = path.join(foldersPath, folder);42 const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));43 for (const file of commandFiles) {44 const filePath = path.join(commandsPath, file);45 const command = require(filePath);46 if ('data' in command && 'execute' in command) {47 client.commands.set(command.data.name, command);48 } else {49 logger.warn(`[Command Loader] The command at ${filePath} is missing a required "data" or "execute" property.`);50 }51 }52}53 54// Load Events55const eventsPath = path.join(__dirname, 'events');56const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));57 58for (const file of eventFiles) {59 const filePath = path.join(eventsPath, file);60 const event = require(filePath);61 if (event.once) {62 client.once(event.name, (...args) => event.execute(...args, client));63 } else {64 client.on(event.name, (...args) => event.execute(...args, client));65 }66}67 68const dns = require('dns');69 70// 1. Override DNS servers for Node's internal resolver (dns.resolve)71if (process.env.SPACE_ID) {72 dns.setServers(['8.8.8.8', '8.8.4.4']);73 dns.setDefaultResultOrder('ipv4first');74 75 // 2. MONKEY-PATCH dns.lookup76 // Hugging Face breaks OS-level resolution for discord.com but blocks root from fixing /etc/resolv.conf.77 // Furthermore, Node 20's undici/net algorithms use "Happy Eyeballs" to connect to ALL resolved IPs simultaneously.78 // Hugging Face's strict outgoing burst firewall drops simultaneous connections as abuse, causing the dreaded EPERM.79 // This patch intercepts all DNS lookups:80 // A) Forces discord domains through Google DNS to solve ENOTFOUND.81 // B) Forces ALL lookups to return ONLY ONE IP, preventing Happy Eyeballs burst EPERMs.82 const originalLookup = dns.lookup;83 dns.lookup = function (hostname, options, callback) {84 if (typeof options === 'function') {85 callback = options;86 options = {};87 }88 89 if (hostname && hostname.includes('discord')) {90 dns.resolve4(hostname, (err, addresses) => {91 if (err || !addresses || addresses.length === 0) {92 return originalLookup(hostname, options, callback);93 }94 // CRITICAL FIX: Return ONLY 1 IP. Prevents 5 concurrent TCP SYN packets.95 if (options.all) {96 callback(null, [{ address: addresses[0], family: 4 }]);97 } else {98 callback(null, addresses[0], 4);99 }100 });101 } else {102 // Let other domains pass through, but still strictly limit to 1 IP if options.all is requested103 originalLookup(hostname, options, (err, address, family) => {104 if (err) return callback(err);105 if (options.all && Array.isArray(address) && address.length > 0) {106 callback(null, [address[0]]);107 } else {108 callback(null, address, family);109 }110 });111 }112 };113}114 115// Connect to Database116db.connect(config.mongoURI);117 118// Diagnostics: Test Outbound HTTP Connections119const https = require('https');120 121function testConnection(url, name) {122 logger.info(`[Diagnostic] Testing connection to ${name} (${url})...`);123 https.get(url, (res) => {124 logger.info(`[Diagnostic] Success! ${name} responded with status: ${res.statusCode}`);125 }).on('error', (e) => {126 logger.error(`[Diagnostic] FAILED! Connection to ${name} rejected:`, e.message);127 });128}129 130// Run tests before discord login131// testConnection('https://www.google.com', 'Google');132// testConnection('https://discord.com/api/v10/gateway', 'Discord API');133 134const login = async (retries = 5) => {135 try {136 await client.login(config.token);137 } catch (error) {138 if (retries === 0) {139 logger.error('[Discord API] Failed to login after multiple attempts:', error);140 process.exit(1);141 }142 logger.warn(`[Discord API] Login failed, retrying in 5 seconds... (${retries} attempts left)`);143 logger.error('[Discord API]', error); // Log the specific error144 setTimeout(() => login(retries - 1), 5000);145 }146};147 148login();149 