require("dotenv").config(); const { Client, GatewayIntentBits, REST, Routes, ApplicationCommandOptionType, ApplicationCommandType, ChannelType } = require("discord.js"); const axios = require("axios"); const ping = require("ping"); const whois = require('whois-json'); const fs = require('fs'); const path = require('path'); const tempDir = path.join(__dirname, '../temp'); if (fs.existsSync(tempDir)) { console.log("Cleaning up temp directory..."); const files = fs.readdirSync(tempDir); for (const file of files) { fs.unlinkSync(path.join(tempDir, file)); } } else { fs.mkdirSync(tempDir, { recursive: true }); } const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessages, GatewayIntentBits.DirectMessageReactions, GatewayIntentBits.DirectMessageTyping, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildMessageTyping ], partials: ['CHANNEL', 'MESSAGE'] // This is important for DM functionality }); // Commands that should work in DMs (registered globally) const globalCommands = [ { name: "fetch_data", description: "Fetches data from an API", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "url", description: "The URL to fetch data from", type: ApplicationCommandOptionType.String, required: true, }, ], }, { name: "ping", description: "Pings a remote server.", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "ip", description: "The IP Adress to ping.", type: ApplicationCommandOptionType.String, required: true, }, ], }, { name: "server_status", description: "Fetches data from an API", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "raw", description: "Display raw JSON data", type: ApplicationCommandOptionType.Boolean, required: false, }, ], }, { name: "cody", description: "Ask Cody (Sourcegraph AI) a coding question", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "question", description: "Your coding question", type: ApplicationCommandOptionType.String, required: true, }, ], }, { name: "weather", description: "Get current weather for a location", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "location", description: "City name or postal code", type: ApplicationCommandOptionType.String, required: true, }, ], }, { name: "mcstatus", description: "Check the status of a Minecraft server", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "server", description: "Server address (e.g., mc.hypixel.net)", type: ApplicationCommandOptionType.String, required: true, }, { name: "bedrock", description: "Is this a Bedrock server? (Default: false)", type: ApplicationCommandOptionType.Boolean, required: false, }, ], }, { name: "animal", description: "Get a random animal image", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "type", description: "Type of animal", type: ApplicationCommandOptionType.String, required: true, choices: [ { name: "Dog", value: "dog" }, { name: "Cat", value: "cat" }, { name: "Panda", value: "panda" }, { name: "Fox", value: "fox" }, { name: "Bird", value: "bird" }, { name: "Koala", value: "koala" }, { name: "Red Panda", value: "red_panda" }, { name: "Raccoon", value: "raccoon" }, { name: "Kangaroo", value: "kangaroo" } ] }, ], }, { name: "anime", description: "Get anime-related content", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "type", description: "Type of anime content", type: ApplicationCommandOptionType.String, required: true, choices: [ { name: "Wink", value: "wink" }, { name: "Pat", value: "pat" }, { name: "Hug", value: "hug" }, { name: "Face Palm", value: "face-palm" }, { name: "Quote", value: "quote" } ] }, ], }, { name: "checkdns", description: "Check if a domain is blocked by running it through a DNS server", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "domain", description: "Domain name to check (e.g. example.com)", type: ApplicationCommandOptionType.String, required: true, }, { name: "provider", description: "DNS provider to use", type: ApplicationCommandOptionType.String, required: false, choices: [ { name: "Cloudflare (1.1.1.1)", value: "1.1.1.1" }, { name: "Google (8.8.8.8)", value: "8.8.8.8" }, { name: "OpenDNS", value: "208.67.222.222" }, { name: "Quad9", value: "9.9.9.9" }, { name: "AdGuard", value: "94.140.14.14" }, { name: "Turknet", value: "193.192.98.8" }, { name: "TTnet", value: "195.175.39.49" }, { name: "Turkcell", value: "195.175.39.49" }, { name: "Superonline", value: "195.114.66.100" } ] } ] }, { name: "traceroute", description: "Show network path to a destination", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "target", description: "IP address or domain to trace (might take a long time to complete)", type: ApplicationCommandOptionType.String, required: true, }, { name: "hops", description: "Maximum number of hops (default: 30)", type: ApplicationCommandOptionType.Integer, required: false, min_value: 1, max_value: 32 } ] }, { name: "whois", description: "Get domain registration information", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "domain", description: "Domain to lookup (e.g. example.com)", type: ApplicationCommandOptionType.String, required: true, } ] }, { name: "stats", description: "Show bot and server statistics", type: ApplicationCommandType.ChatInput, dm_permission: true }, { name: "checkport", description: "Check if specific ports are open on a domain", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "target", description: "Domain or IP to scan", type: ApplicationCommandOptionType.String, required: true, }, { name: "ports", description: "Ports to scan (comma separated, e.g. 80,443,3306)", type: ApplicationCommandOptionType.String, required: true, } ] }, { name: "wikipedia", description: "Get a summary of a Wikipedia article", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "query", description: "The topic to search for on Wikipedia", type: ApplicationCommandOptionType.String, required: true, }, { name: "language", description: "Wikipedia language (default: en)", type: ApplicationCommandOptionType.String, required: false, choices: [ { name: "English", value: "en" }, { name: "Spanish", value: "es" }, { name: "French", value: "fr" }, { name: "German", value: "de" }, { name: "Russian", value: "ru" }, { name: "Japanese", value: "ja" }, { name: "Chinese", value: "zh" }, { name: "Turkish", value: "tr" } ] } ], }, { name: "urban", description: "Look up a term on Urban Dictionary", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "term", description: "The slang term to look up", type: ApplicationCommandOptionType.String, required: true, }, { name: "random", description: "Get a random definition instead", type: ApplicationCommandOptionType.Boolean, required: false } ], }, { name: "currency", description: "Convert between currencies using real-time exchange rates", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "amount", description: "Amount to convert", type: ApplicationCommandOptionType.Number, required: true, }, { name: "from", description: "Source currency code (e.g., USD)", type: ApplicationCommandOptionType.String, required: true, }, { name: "to", description: "Target currency code (e.g., EUR)", type: ApplicationCommandOptionType.String, required: true, } ], }, { name: "hash", description: "Generate hash of text or file (up to 500MB)", type: ApplicationCommandType.ChatInput, dm_permission: true, options: [ { name: "algorithm", description: "Hash algorithm to use", type: ApplicationCommandOptionType.String, required: true, choices: [ { name: "MD5", value: "md5" }, { name: "SHA-1", value: "sha1" }, { name: "SHA-256", value: "sha256" }, { name: "SHA-512", value: "sha512" }, { name: "SHA3-256", value: "sha3-256" }, { name: "SHA3-512", value: "sha3-512" } ] }, { name: "text", description: "Text to hash (if not uploading a file)", type: ApplicationCommandOptionType.String, required: false, }, { name: "file", description: "File to hash (up to 500MB)", type: ApplicationCommandOptionType.Attachment, required: false, } ], } ]; // Commands that only make sense in a guild context const guildCommands = [ // Add guild-specific commands here if needed ]; // User context menu commands (should also be registered globally) const userCommands = [ { name: "User Info", type: ApplicationCommandType.User, }, ]; // Function to get existing commands without deleting them async function getExistingCommands(rest, route) { try { return await rest.get(route); } catch (error) { console.error(`Error fetching commands from ${route}:`, error); return []; } } // New function to safely update commands async function updateCommandsSafely() { if (!process.env.DISCORD_TOKEN || !process.env.CLIENT_ID) { console.error("Missing required environment variables: DISCORD_TOKEN or CLIENT_ID"); return; } const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN); try { console.log("Starting command registration..."); // First, get all existing global commands to check for entry point commands const existingGlobalCommands = await getExistingCommands( rest, Routes.applicationCommands(process.env.CLIENT_ID) ); // Find any entry point or special commands we need to preserve const entryPointCommands = existingGlobalCommands.filter( cmd => cmd.integration_types && cmd.integration_types.includes(1) ); // Create a map to track command names we've already added const commandNameMap = new Map(); // Create a filtered array of commands without duplicates const allGlobalCommands = []; // First add global commands for (const cmd of globalCommands) { if (!commandNameMap.has(cmd.name)) { commandNameMap.set(cmd.name, true); allGlobalCommands.push(cmd); } else { console.warn(`Skipping duplicate global command: ${cmd.name}`); } } // Then add user commands for (const cmd of userCommands) { if (!commandNameMap.has(cmd.name)) { commandNameMap.set(cmd.name, true); allGlobalCommands.push(cmd); } else { console.warn(`Skipping duplicate user command: ${cmd.name}`); } } // Finally, add entry point commands that don't duplicate existing names for (const cmd of entryPointCommands) { if (!commandNameMap.has(cmd.name)) { commandNameMap.set(cmd.name, true); allGlobalCommands.push(cmd); } else { console.log(`Entry point command "${cmd.name}" already exists, keeping existing definition`); } } console.log(`Registering ${allGlobalCommands.length} unique global commands...`); // Update global commands (including DM-compatible commands) await rest.put( Routes.applicationCommands(process.env.CLIENT_ID), { body: allGlobalCommands } ); console.log(`Successfully registered ${allGlobalCommands.length} global commands`); // If we have guild-specific commands, register them for each guild if (guildCommands.length > 0) { // Wait for client to be ready to access guilds if (!client.isReady()) { await new Promise(resolve => { client.once('ready', resolve); }); } // Register guild commands for each guild the bot is in for (const guild of client.guilds.cache.values()) { console.log(`Registering guild commands for ${guild.name} (${guild.id})...`); await rest.put( Routes.applicationGuildCommands(process.env.CLIENT_ID, guild.id), { body: guildCommands } ); } console.log(`Successfully registered ${guildCommands.length} guild commands per server`); } console.log("All commands registered successfully!"); } catch (error) { console.error("Error updating commands:", error); if (error.code === 50240) { console.error("This error suggests you need to include all Entry Point commands in your update."); console.error("Try using the updateCommandsSafely function which preserves Entry Point commands."); } } } // Function to ask Cody a question and parse the streaming response async function askCody(question) { // [Function implementation remains the same] if (!process.env.SOURCEGRAPH_API_KEY) { throw new Error("SOURCEGRAPH_API_KEY is not set in environment variables"); } if (question.length > 1999) { throw new Error("Input question is too long - must be 1999 characters or less"); } try { const response = await axios({ method: 'post', url: 'https://sourcegraph.com/.api/completions/stream', data: { messages: [ { speaker: "human", text: question } ], temperature: 0.3, maxTokensToSample: 2000, topK: 50, topP: 0.95 }, headers: { 'Content-Type': 'application/json', 'Authorization': `token ${process.env.SOURCEGRAPH_API_KEY}` }, responseType: 'text' }); const events = response.data.split('\n\n').filter(line => line.trim() !== ''); let fullCompletion = ''; for (const event of events) { const lines = event.split('\n'); const eventType = lines[0].replace('event: ', ''); if (eventType === 'completion') { const dataLine = lines[1]; if (dataLine && dataLine.startsWith('data: ')) { try { const jsonData = JSON.parse(dataLine.substring(6)); if (jsonData.completion) { fullCompletion = jsonData.completion; } } catch (e) { console.error('Error parsing JSON from Cody response:', e); } } } } return fullCompletion; } catch (error) { console.error('Error calling Cody API:', error.message); if (error.response) { console.error('Response data:', error.response.data); console.error('Response status:', error.response.status); } throw error; } } client.once("ready", async () => { console.log(`Logged in as ${client.user.tag}`); // Register commands after the bot is ready await updateCommandsSafely(); // Send startup notification if (process.env.OWNER_ID) { try { const ownerId = process.env.OWNER_ID; const owner = await client.users.fetch(ownerId); const startupEmbed = { title: "Bot Status Update", description: `Bot started successfully at `, color: 0x00ff00, fields: [ { name: "Bot Name", value: client.user.tag, inline: true }, { name: "Relative Time", value: ``, inline: true } ], footer: { text: "blahaj.tr" } }; await owner.send({ embeds: [startupEmbed] }); } catch (error) { console.error("Failed to send startup notification:", error); } } }); client.on("interactionCreate", async (interaction) => { // Add debug logging to help diagnose DM issues console.log(`Received interaction: ${interaction.commandName} | Channel type: ${interaction.channel.type} | DM: ${interaction.channel.type === ChannelType.DM}`); // Handle slash commands if (interaction.isChatInputCommand()) { try { switch (interaction.commandName) { case "fetch_data": try { const url = interaction.options.getString("url"); if (!url.startsWith("http://") && !url.startsWith("https://")) { await interaction.reply({ content: "Please provide a valid URL starting with http:// or https://", ephemeral: true, }); return; } const response = await axios.get(url); await interaction.reply({ content: `\`\`\`json\n${JSON.stringify(response.data, null, 2)}\n\`\`\``, }); } catch (error) { console.error(error); await interaction.reply({ content: "Failed to fetch data.", ephemeral: true }); } break; case "ping": try { await interaction.deferReply(); const ip = interaction.options.getString("ip"); const pingResult = await ping.promise.probe(ip); if (pingResult.time == "unknown") { await interaction.editReply({ content: "Unable to ping the IP address.", ephemeral: true, }); return; } const pingEmbed = { title: "Ping Results", description: `Results for IP: ${ip}`, color: 0x00ff00, fields: [ { name: "Response Time", value: `${pingResult.time}ms`, inline: true } ], timestamp: new Date(), footer: { text: "Ping Command" } }; await interaction.editReply({ embeds: [pingEmbed] }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to ping.", ephemeral: true }); } break; case "server_status": try { const response = await axios.get("https://blahaj.tr:2589/status"); const isRaw = interaction.options.getBoolean("raw") ?? false; if (isRaw) { await interaction.reply({ content: `\`\`\`json\n${JSON.stringify(response.data, null, 2)}\n\`\`\``, }); } else { let formattedResponse = ""; const fields = []; for (const [server, data] of Object.entries(response.data)) { const status = data.online ? "🟢 Online" : "🔴 Offline"; const responseTime = data.responseTime.toFixed(2); fields.push({ name: server, value: `Status: ${status}\nResponse Time: ${responseTime}ms`, inline: true }); } const statusEmbed = { title: "Server Status", color: 0x00ff00, fields: fields, timestamp: new Date(), footer: { text: "Server Status Command" } }; await interaction.reply({ embeds: [statusEmbed] }); } } catch (error) { console.error(error); await interaction.reply({ content: "Failed to get status.", ephemeral: true }); } break; case "cody": try { await interaction.deferReply(); const question = interaction.options.getString("question"); if (!process.env.SOURCEGRAPH_API_KEY) { await interaction.editReply({ content: "Sourcegraph API key not configured. Please add SOURCEGRAPH_API_KEY to your environment variables.", ephemeral: true }); return; } console.log(`Asking Cody: ${question}`); // Call Cody API const codyResponse = await askCody(question); console.log(`Cody response received: ${codyResponse ? 'yes' : 'no'}`); // Format the response let formattedResponse = codyResponse || "No response received from Cody."; // Calculate total length including the question const fullResponse = `**Question:** ${question}\n\n**Cody's Answer:**\n${formattedResponse}`; // If the response is too long for Discord (which has a 2000 character limit) if (fullResponse.length > 1900) { formattedResponse = formattedResponse.substring(0, 1900 - question.length - 50) + "...\n(Response truncated due to Discord's character limit)"; } await interaction.editReply({ content: `**Question:** ${question}\n\n**Cody's Answer:**\n${formattedResponse}` }); } catch (error) { console.error("Cody API error:", error); await interaction.editReply({ content: "Sorry, I couldn't get an answer from Cody. Please try again later.", ephemeral: true }); } break; case "weather": try { await interaction.deferReply(); const location = interaction.options.getString("location"); // Make sure you have WEATHER_API_KEY in your .env file if (!process.env.WEATHER_API_KEY) { await interaction.editReply({ content: "Weather API key not configured. Please add WEATHER_API_KEY to your environment variables.", ephemeral: true }); return; } const weatherUrl = `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${encodeURIComponent(location)}&aqi=no`; const response = await axios.get(weatherUrl); const data = response.data; const location_name = data.location.name; const region = data.location.region; const country = data.location.country; const temp_c = data.current.temp_c; const temp_f = data.current.temp_f; const condition = data.current.condition.text; const humidity = data.current.humidity; const wind_kph = data.current.wind_kph; const wind_mph = data.current.wind_mph; const feelslike_c = data.current.feelslike_c; const feelslike_f = data.current.feelslike_f; const weatherEmbed = { title: `Weather for ${location_name}, ${region}, ${country}`, description: `Current condition: ${condition}`, fields: [ { name: 'Temperature', value: `${temp_c}°C / ${temp_f}°F`, inline: true }, { name: 'Feels Like', value: `${feelslike_c}°C / ${feelslike_f}°F`, inline: true }, { name: 'Humidity', value: `${humidity}%`, inline: true }, { name: 'Wind Speed', value: `${wind_kph} km/h / ${wind_mph} mph`, inline: true } ], thumbnail: { url: `https:${data.current.condition.icon}` }, timestamp: new Date(), footer: { text: 'Powered by WeatherAPI.com' } }; await interaction.editReply({ embeds: [weatherEmbed] }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to fetch weather data. Please check the location name and try again.", ephemeral: true }); } break; // Handle all the other commands the same way as in the original code case "mcstatus": try { await interaction.deferReply(); const serverAddress = interaction.options.getString("server"); const isBedrock = interaction.options.getBoolean("bedrock") ?? false; const apiUrl = isBedrock ? `https://api.mcsrvstat.us/bedrock/2/${encodeURIComponent(serverAddress)}` : `https://api.mcsrvstat.us/2/${encodeURIComponent(serverAddress)}`; const response = await axios.get(apiUrl); const data = response.data; if (!data.online) { await interaction.editReply({ content: `📡 **${serverAddress}** is currently offline or could not be reached.` }); return; } const serverEmbed = { title: `Minecraft Server Status: ${serverAddress}`, color: 0x44FF44, thumbnail: { url: 'https://www.minecraft.net/content/dam/games/minecraft/key-art/MC_The-Wild-Update_540x300.jpg' }, fields: [ { name: 'Status', value: data.online ? '✅ Online' : '❌ Offline', inline: true }, { name: 'Players', value: data.players ? `${data.players.online}/${data.players.max}` : 'Unknown', inline: true }, { name: 'Version', value: data.version || 'Unknown', inline: true } ], footer: { text: 'Powered by mcsrvstat.us' }, timestamp: new Date() }; if (data.motd && data.motd.clean && data.motd.clean.length > 0) { serverEmbed.description = `**MOTD:**\n${data.motd.clean.join('\n')}`; } if (data.players && data.players.list && data.players.list.length > 0) { const playerList = data.players.list.slice(0, 20).join(', '); const hasMore = data.players.list.length > 20; serverEmbed.fields.push({ name: 'Online Players', value: playerList + (hasMore ? '...' : '') }); } await interaction.editReply({ embeds: [serverEmbed] }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to fetch Minecraft server status. Please check the server address and try again.", ephemeral: true }); } break; case "animal": try { await interaction.deferReply(); const animalType = interaction.options.getString("type"); const imageResponse = await axios.get(`https://some-random-api.com/animal/${animalType}`); const imageUrl = imageResponse.data.image; const animalEmbed = { color: 0x3498db, image: { url: imageUrl }, footer: { text: 'Powered by some-random-api.com' }, timestamp: new Date() }; await interaction.editReply({ embeds: [animalEmbed] }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to fetch animal image. The API might be down or the animal type is not available.", ephemeral: true }); } break; case "anime": try { await interaction.deferReply(); const type = interaction.options.getString("type"); let apiUrl; let isQuote = false; if (type === "quote") { apiUrl = "https://some-random-api.ml/animu/quote"; isQuote = true; } else { apiUrl = `https://some-random-api.ml/animu/${type}`; } const response = await axios.get(apiUrl); if (isQuote) { const quote = response.data.sentence; const character = response.data.character; const anime = response.data.anime; const quoteEmbed = { title: "Anime Quote", description: `"${quote}"`, fields: [ { name: "Character", value: character, inline: true }, { name: "Anime", value: anime, inline: true } ], color: 0xe74c3c, footer: { text: 'Powered by some-random-api.ml' }, timestamp: new Date() }; await interaction.editReply({ embeds: [quoteEmbed] }); } else { const gifUrl = response.data.link; const actionTitle = type.charAt(0).toUpperCase() + type.slice(1).replace('-', ' '); const gifEmbed = { title: `Anime ${actionTitle}`, color: 0xe74c3c, image: { url: gifUrl }, footer: { text: 'Powered by some-random-api.ml' }, timestamp: new Date() }; await interaction.editReply({ embeds: [gifEmbed] }); } } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to fetch anime content. The API might be down or the requested action is not available.", ephemeral: true }); } break; case "checkdns": try { await interaction.deferReply(); const domain = interaction.options.getString("domain"); const provider = interaction.options.getString("provider") || "1.1.1.1"; const dns = require('dns'); const resolver = new dns.Resolver(); resolver.setServers([provider]); resolver.resolve4(domain, async (err, addresses) => { if (err) { const dnsEmbed = { title: "DNS Lookup Result", description: `Domain: ${domain}\nProvider: ${provider}`, color: 0xFF0000, fields: [ { name: "Status", value: "❌ Domain is blocked or unreachable", inline: true }, { name: "Error", value: err.code, inline: true } ], timestamp: new Date(), footer: { text: "DNS Check Service" } }; await interaction.editReply({ embeds: [dnsEmbed] }); } else { const dnsEmbed = { title: "DNS Lookup Result", description: `Domain: ${domain}\nProvider: ${provider}`, color: 0x00FF00, fields: [ { name: "Status", value: "✅ Domain is accessible", inline: true }, { name: "IP Addresses", value: addresses.join('\n'), inline: true } ], timestamp: new Date(), footer: { text: "DNS Check Service" } }; await interaction.editReply({ embeds: [dnsEmbed] }); } }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to perform DNS lookup. Please check the domain name and try again.", ephemeral: true }); } break; case "traceroute": try { await interaction.deferReply(); const target = interaction.options.getString("target"); const maxHops = interaction.options.getInteger("hops") || 16; const { spawn } = require('child_process'); // Use shell option to properly handle the pipe const tracepath = spawn('traceroute -q 1 -d -m ' + maxHops + ' ' + target + ' | awk \'{print $1, $2, $3}\'', { shell: true }); let output = ''; tracepath.stdout.on('data', async (data) => { const newData = data.toString() .split('\n') .map(line => line.trim()) .join('\n'); output += newData; const traceEmbed = { title: `Path to ${target}`, description: `\`\`\`\n${output}\`\`\``, color: 0x3498db, timestamp: new Date(), footer: { text: "Tracing..." } }; await interaction.editReply({ embeds: [traceEmbed] }); }); tracepath.stderr.on('data', (data) => { console.log(`stderr: ${data}`); }); tracepath.on('close', async () => { const finalEmbed = { title: `Path to ${target} - Complete`, description: `\`\`\`\n${output}\`\`\``, color: 0x00ff00, timestamp: new Date(), footer: { text: "✅ Trace complete" } }; await interaction.editReply({ embeds: [finalEmbed] }); }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to trace path. Please check the target and try again.", ephemeral: true }); } break; case "wikipedia": try { await interaction.deferReply(); const query = interaction.options.getString("query"); const language = interaction.options.getString("language") || "en"; // Import the wikipedia package const wikipedia = require('wikipedia'); // Set the language wikipedia.setLang(language); // Search for the query const searchResults = await wikipedia.search(query); if (!searchResults.results || searchResults.results.length === 0) { await interaction.editReply({ content: `No results found for "${query}" on Wikipedia.`, ephemeral: true }); return; } // Get the first result const page = await wikipedia.page(searchResults.results[0].title); // Get summary and basic info const summary = await page.summary(); // Create a rich embed const wikiEmbed = { title: summary.title, url: summary.content_urls.desktop.page, description: summary.extract.length > 1000 ? summary.extract.substring(0, 1000) + "..." : summary.extract, color: 0x0099ff, thumbnail: summary.thumbnail ? { url: summary.thumbnail.source } : null, fields: [ { name: "Page ID", value: summary.pageid.toString(), inline: true }, { name: "Language", value: language.toUpperCase(), inline: true } ], footer: { text: "Powered by Wikipedia", icon_url: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png" }, timestamp: new Date() }; // Add a related articles field if we have other search results if (searchResults.results.length > 1) { const relatedArticles = searchResults.results .slice(1, 4) // Get 3 related articles .map(result => `[${result.title}](https://${language}.wikipedia.org/wiki/${encodeURIComponent(result.title.replace(/ /g, '_'))})`) .join('\n'); wikiEmbed.fields.push({ name: "Related Articles", value: relatedArticles }); } await interaction.editReply({ embeds: [wikiEmbed] }); } catch (error) { console.error(error); // Handle specific Wikipedia errors if (error.message.includes("No article found")) { await interaction.editReply({ content: "Couldn't find a specific Wikipedia article with that title. Try a different search term.", ephemeral: true }); } else { await interaction.editReply({ content: "Error fetching Wikipedia data. Please try again later.", ephemeral: true }); } } break; case "whois": try { await interaction.deferReply(); const domain = interaction.options.getString("domain"); const result = await whois(domain); const whoisEmbed = { title: `WHOIS Lookup: ${domain}`, color: 0x2ecc71, fields: [ { name: "Registrar", value: result.registrar || "Not available", inline: true }, { name: "Creation Date", value: result.creationDate ? new Date(result.creationDate).toLocaleDateString() : "Not available", inline: true }, { name: "Expiration Date", value: result.expirationDate ? new Date(result.expirationDate).toLocaleDateString() : "Not available", inline: true }, { name: "Name Servers", value: Array.isArray(result.nameServers) ? result.nameServers.join('\n') : "Not available" }, { name: "Status", value: Array.isArray(result.status) ? result.status.join('\n') : (result.status || "Not available") } ], timestamp: new Date(), footer: { text: "Domain Information Service" } }; await interaction.editReply({ embeds: [whoisEmbed] }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to fetch WHOIS information. Please check the domain name and try again.", ephemeral: true }); } break; case "stats": try { await interaction.deferReply(); const os = require('os'); // Calculate uptime const uptime = process.uptime(); const days = Math.floor(uptime / 86400); const hours = Math.floor((uptime % 86400) / 3600); const minutes = Math.floor((uptime % 3600) / 60); // Get system info const memUsage = process.memoryUsage(); const cpuLoad = os.loadavg(); const statsEmbed = { title: "Bot Statistics", color: 0x7289DA, fields: [ { name: "Bot Info", value: [ `**Servers:** ${client.guilds.cache.size}`, `**Users:** ${client.users.cache.size}`, `**Channels:** ${client.channels.cache.size}`, `**Commands:** ${globalCommands.length}` ].join('\n'), inline: true }, { name: "System Info", value: [ `**Platform:** ${os.platform()}`, `**Memory Usage:** ${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`, `**CPU Load:** ${cpuLoad[0].toFixed(2)}%`, `**Node.js:** ${process.version}` ].join('\n'), inline: true }, { name: "Uptime", value: `${days}d ${hours}h ${minutes}m`, inline: true } ], timestamp: new Date(), footer: { text: "blahaj-srv" } }; await interaction.editReply({ embeds: [statsEmbed] }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to fetch statistics.", ephemeral: true }); } break; case "checkport": try { await interaction.deferReply(); const target = interaction.options.getString("target"); const ports = interaction.options.getString("ports").split(",").map(p => parseInt(p.trim())); const net = require('net'); const checkPort = (port) => { return new Promise((resolve) => { const socket = new net.Socket(); socket.setTimeout(2000); // 2 second timeout socket.on('connect', () => { socket.destroy(); resolve({ port, status: 'open' }); }); socket.on('timeout', () => { socket.destroy(); resolve({ port, status: 'closed' }); }); socket.on('error', () => { socket.destroy(); resolve({ port, status: 'closed' }); }); socket.connect(port, target); }); }; const results = await Promise.all(ports.map(port => checkPort(port))); const scanEmbed = { title: `Port Scan Results for ${target}`, color: 0x00ff00, fields: results.map(result => ({ name: `Port ${result.port}`, value: result.status === 'open' ? '🟢 Open' : '🔴 Closed', inline: true })), timestamp: new Date(), footer: { text: "Port Check" } }; await interaction.editReply({ embeds: [scanEmbed] }); } catch (error) { console.error(error); await interaction.editReply({ content: "Failed to perform port scan. Please check the target and port numbers.", ephemeral: true }); } break; case "urban": try { await interaction.deferReply(); const term = interaction.options.getString("term"); const isRandom = interaction.options.getBoolean("random") || false; // API endpoint const endpoint = isRandom ? "https://api.urbandictionary.com/v0/random" : `https://api.urbandictionary.com/v0/define?term=${encodeURIComponent(term)}`; const response = await axios.get(endpoint); if (!response.data.list || response.data.list.length === 0) { await interaction.editReply({ content: `No definitions found for "${term}" on Urban Dictionary.`, ephemeral: true }); return; } // Sort by thumbs up count if there are multiple definitions const definitions = response.data.list.sort((a, b) => b.thumbs_up - a.thumbs_up); const definition = definitions[0]; // Clean up the text by replacing square brackets with formatted links let cleanDefinition = definition.definition.replace(/\[([^\]]+)\]/g, '**$1**'); let cleanExample = definition.example.replace(/\[([^\]]+)\]/g, '**$1**'); // Truncate if too long if (cleanDefinition.length > 1024) { cleanDefinition = cleanDefinition.substring(0, 1021) + '...'; } if (cleanExample.length > 1024) { cleanExample = cleanExample.substring(0, 1021) + '...'; } // Create a rich embed const urbanEmbed = { title: isRandom ? definition.word : term, url: definition.permalink, color: 0xEFFF00, // Urban Dictionary yellow fields: [ { name: "Definition", value: cleanDefinition || "No definition provided" } ], footer: { text: `👍 ${definition.thumbs_up} | 👎 ${definition.thumbs_down} | Written by ${definition.author}`, icon_url: "https://i.imgur.com/VFXr0ID.jpg" }, timestamp: new Date(definition.written_on) }; // Add example if it exists if (cleanExample && cleanExample.trim().length > 0) { urbanEmbed.fields.push({ name: "Example", value: cleanExample }); } // Add related definitions if there are more if (definitions.length > 1) { const relatedCount = Math.min(definitions.length - 1, 3); urbanEmbed.fields.push({ name: `${relatedCount} More Definition${relatedCount > 1 ? 's' : ''}`, value: `This term has ${definitions.length} definitions. Use the link above to see them all.` }); } // Add a warning that content might be offensive const warningMessage = "⚠️ **Note:** Urban Dictionary contains user-submitted content that may be offensive or inappropriate."; await interaction.editReply({ content: warningMessage, embeds: [urbanEmbed] }); } catch (error) { console.error(error); await interaction.editReply({ content: "Error fetching Urban Dictionary definition. Please try again later.", ephemeral: true }); } break; case "currency": try { await interaction.deferReply(); const amount = interaction.options.getNumber("amount"); const fromCurrency = interaction.options.getString("from").toUpperCase(); const toCurrency = interaction.options.getString("to").toUpperCase(); // Check if API key is configured if (!process.env.EXCHANGE_RATE_API_KEY) { await interaction.editReply({ content: "Exchange Rate API key not configured. Please add EXCHANGE_RATE_API_KEY to your environment variables.", ephemeral: true }); return; } // Validate amount if (amount <= 0) { await interaction.editReply({ content: "Please provide a positive amount to convert.", ephemeral: true }); return; } // Fetch exchange rates const apiUrl = `https://v6.exchangerate-api.com/v6/${process.env.EXCHANGE_RATE_API_KEY}/latest/${fromCurrency}`; const response = await axios.get(apiUrl); // Check if the source currency is valid if (response.data.result === "error") { await interaction.editReply({ content: `Error: ${response.data.error-type || "Invalid request"}. Please check your currency codes.`, ephemeral: true }); return; } // Check if target currency exists in the response if (!response.data.conversion_rates[toCurrency]) { await interaction.editReply({ content: `Could not find exchange rate for ${toCurrency}. Please check your currency code.`, ephemeral: true }); return; } // Calculate the converted amount const rate = response.data.conversion_rates[toCurrency]; const convertedAmount = amount * rate; // Format numbers with proper separators and decimals const formatNumber = (num) => { return new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 4 }).format(num); }; // Get currency information to display symbols const currencyInfo = { USD: { symbol: '$', name: 'US Dollar' }, EUR: { symbol: '€', name: 'Euro' }, GBP: { symbol: '£', name: 'British Pound' }, JPY: { symbol: '¥', name: 'Japanese Yen' }, TRY: { symbol: '₺', name: 'Turkish Lira' }, // Add more currencies as needed }; const fromCurrencyInfo = currencyInfo[fromCurrency] || { symbol: '', name: fromCurrency }; const toCurrencyInfo = currencyInfo[toCurrency] || { symbol: '', name: toCurrency }; // Create a rich embed const conversionEmbed = { title: "Currency Conversion", color: 0x4CAF50, // Green fields: [ { name: "From", value: `${fromCurrencyInfo.symbol} ${formatNumber(amount)} ${fromCurrency} (${fromCurrencyInfo.name})`, inline: false }, { name: "To", value: `${toCurrencyInfo.symbol} ${formatNumber(convertedAmount)} ${toCurrency} (${toCurrencyInfo.name})`, inline: false }, { name: "Exchange Rate", value: `1 ${fromCurrency} = ${formatNumber(rate)} ${toCurrency}`, inline: true }, { name: "Last Updated", value: new Date(response.data.time_last_update_unix * 1000).toLocaleString(), inline: true } ], footer: { text: "Powered by ExchangeRate-API" }, timestamp: new Date() }; await interaction.editReply({ embeds: [conversionEmbed] }); } catch (error) { console.error(error); const errorMessage = error.response?.data?.error || "Error fetching exchange rates. Please try again later."; await interaction.editReply({ content: errorMessage, ephemeral: true }); } break; case "hash": try { await interaction.deferReply(); const algorithm = interaction.options.getString("algorithm"); const text = interaction.options.getString("text"); const file = interaction.options.getAttachment("file"); // Validate that either text or file is provided if (!text && !file) { await interaction.editReply({ content: "Please provide either text or a file to hash.", ephemeral: true }); return; } // If both are provided, prioritize the file if (text && file) { await interaction.followUp({ content: "Both text and file were provided. Processing the file and ignoring the text.", ephemeral: true }); } // For text input, generate hash directly if (text && !file) { const crypto = require('crypto'); const hash = crypto.createHash(algorithm).update(text).digest('hex'); const hashEmbed = { title: `${algorithm.toUpperCase()} Hash`, description: "Text hash generated successfully", color: 0x3498db, fields: [ { name: "Input Text", value: text.length > 1024 ? text.substring(0, 1021) + "..." : text }, { name: "Hash", value: "```\n" + hash + "\n```" } ], timestamp: new Date(), footer: { text: `Algorithm: ${algorithm.toUpperCase()}` } }; await interaction.editReply({ embeds: [hashEmbed] }); return; } // For file input, download and hash the file if (file) { // Check file size (500MB limit) const maxSize = 500 * 1024 * 1024; // 500MB in bytes if (file.size > maxSize) { await interaction.editReply({ content: `File is too large. Maximum size is 500MB. Your file is ${(file.size / (1024 * 1024)).toFixed(2)}MB.`, ephemeral: true }); return; } // If file is larger than 25MB, warn the user it might take a while if (file.size > 25 * 1024 * 1024) { await interaction.editReply({ content: `Processing a ${(file.size / (1024 * 1024)).toFixed(2)}MB file. This might take a while...` }); } // Process the file using streams for efficiency const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const stream = require('stream'); const { promisify } = require('util'); const pipeline = promisify(stream.pipeline); const axios = require('axios'); // Create a temporary file path const tempDir = path.join(__dirname, '../temp'); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } const tempFile = path.join(tempDir, `${Date.now()}_${file.name.replace(/[^a-zA-Z0-9.]/g, '_')}`); try { // Download the file const writer = fs.createWriteStream(tempFile); const response = await axios({ method: 'GET', url: file.url, responseType: 'stream' }); await pipeline(response.data, writer); // After download completes, hash the file with progress updates const fileSize = fs.statSync(tempFile).size; const hash = crypto.createHash(algorithm); const input = fs.createReadStream(tempFile); let processedBytes = 0; let lastProgressUpdate = Date.now(); input.on('data', (chunk) => { hash.update(chunk); processedBytes += chunk.length; // Update progress every 3 seconds for files larger than 50MB const now = Date.now(); if (fileSize > 50 * 1024 * 1024 && now - lastProgressUpdate > 3000) { const progress = (processedBytes / fileSize * 100).toFixed(2); interaction.editReply({ content: `Processing file: ${progress}% complete...` }).catch(console.error); lastProgressUpdate = now; } }); // Wait for the hash to complete const hashHex = await new Promise((resolve, reject) => { input.on('end', () => resolve(hash.digest('hex'))); input.on('error', reject); }); // Clean up the temp file fs.unlinkSync(tempFile); // Create the response embed const fileExtension = path.extname(file.name).toLowerCase(); const hashEmbed = { title: `${algorithm.toUpperCase()} Hash Generated`, description: "File hash calculated successfully", color: 0x00ff00, fields: [ { name: "File", value: `${file.name} (${(file.size / (1024 * 1024)).toFixed(2)} MB)` }, { name: "Hash", value: "```\n" + hashHex + "\n```" } ], timestamp: new Date(), footer: { text: `Algorithm: ${algorithm.toUpperCase()}` } }; await interaction.editReply({ embeds: [hashEmbed] }); } catch (fileError) { console.error("File processing error:", fileError); // Clean up temp file if it exists if (fs.existsSync(tempFile)) { fs.unlinkSync(tempFile); } await interaction.editReply({ content: "Error processing file. The file might be inaccessible or corrupted.", ephemeral: true }); } } } catch (error) { console.error("Hash command error:", error); await interaction.editReply({ content: "Error generating hash. Please try again with a smaller file or different input.", ephemeral: true }); } break; default: await interaction.reply({ content: `Command '${interaction.commandName}' not implemented yet.`, ephemeral: true }); } } catch (error) { console.error(`Error executing command ${interaction.commandName}:`, error); // Try to respond if we haven't already try { const replyMethod = interaction.deferred ? interaction.editReply : interaction.reply; await replyMethod.call(interaction, { content: "An error occurred while executing this command.", ephemeral: true }); } catch (e) { console.error("Could not send error response:", e); } } } else if (interaction.isUserContextMenuCommand()) { // Handle user context menu commands if (interaction.commandName === "User Info") { const user = interaction.targetUser; const userInfoEmbed = { title: "User Information", color: 0x9B59B6, thumbnail: { url: user.displayAvatarURL({ dynamic: true }) }, fields: [ { name: "Username", value: user.username, inline: true }, { name: "User ID", value: user.id, inline: true }, { name: "Account Created", value: ``, inline: true } ], footer: { text: "User Information" }, timestamp: new Date() }; await interaction.reply({ embeds: [userInfoEmbed], ephemeral: true }); } } }); // Handle guild join events to register guild-specific commands if needed client.on("guildCreate", async (guild) => { console.log(`Joined new guild: ${guild.name} (${guild.id})`); if (guildCommands.length > 0) { try { console.log(`Registering guild commands for ${guild.name}...`); const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN); await rest.put( Routes.applicationGuildCommands(process.env.CLIENT_ID, guild.id), { body: guildCommands } ); console.log(`Successfully registered guild commands for ${guild.name}`); } catch (error) { console.error(`Error registering guild commands for ${guild.name}:`, error); } } // Notify owner if configured if (process.env.OWNER_ID) { try { const owner = await client.users.fetch(process.env.OWNER_ID); const guildJoinEmbed = { title: "New Guild Joined", color: 0x00ff00, fields: [ { name: "Guild Name", value: guild.name, inline: true }, { name: "Guild ID", value: guild.id, inline: true }, { name: "Member Count", value: guild.memberCount.toString(), inline: true } ], timestamp: new Date(), footer: { text: "Guild Join Event" } }; await owner.send({ embeds: [guildJoinEmbed] }); } catch (error) { console.error("Failed to notify owner of guild join:", error); } } }); // Error handling process.on('unhandledRejection', error => { console.error('Unhandled promise rejection:', error); }); // Start the bot client.login(process.env.DISCORD_TOKEN).catch(error => { console.error("Failed to login:", error); process.exit(1); });