import { config } from 'dotenv'; import { Client, Events, GatewayIntentBits, EmbedBuilder, AttachmentBuilder } from 'discord.js'; import fetch from 'node-fetch'; import * as cheerio from 'cheerio'; import { createWriteStream, unlinkSync, existsSync } from 'fs'; import { pipeline } from 'stream/promises'; import path from 'path'; import { createRequire } from 'module'; // Create require function for CommonJS modules const require = createRequire(import.meta.url); const Socket = require('blockchain.info/Socket'); const blockexplorer = require('blockchain.info/blockexplorer'); const axios = require('axios'); // Load environment variables config(); // Bitcoin monitoring variables let btcMonitorChannel = null; let btcSocket = null; let isMonitoring = false; let minBtcAmount = 0; // Minimum BTC amount to show transactions let transactionHandler = null; // Store the transaction handler reference let processedTransactions = new Set(); // Track processed transaction hashes const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.DirectMessages ] }); client.on(Events.ClientReady, async readyClient => { console.log(`Logged in as ${readyClient.user.tag}!`); readyClient.user.setPresence({ activities: [{ name: 'Monitoring Bitcoin & Searching Media' }], status: 'online', }); try { // Register all slash commands const commands = [ { name: 'ping', description: 'Replies with Pong!' }, { name: 'send', description: 'Send a message to a channel', options: [ { name: 'channel', type: 7, // CHANNEL type description: 'The channel to send the message to', required: true }, { name: 'message', type: 3, // STRING type description: 'The message to send', required: true } ] }, { name: 'search', description: 'Search for images or videos online', options: [ { name: 'query', type: 3, // STRING type description: 'What to search for', required: true }, { name: 'type', type: 3, // STRING type description: 'Type of media to search for (image, video)', required: true, choices: [ { name: 'Images', value: 'image' }, { name: 'Videos', value: 'video' } ] }, { name: 'count', type: 4, // INTEGER type description: 'Number of results to show (1-5)', required: false, min_value: 1, max_value: 5 } ] }, { name: 'download', description: 'Download and display multimedia files', options: [ { name: 'url', type: 3, // STRING type description: 'Direct URL to the multimedia file', required: true }, { name: 'title', type: 3, // STRING type description: 'Optional title for the file', required: false } ] }, { name: 'btc-monitor', description: 'Start/stop Bitcoin transaction monitoring', options: [ { name: 'action', type: 3, // STRING type description: 'Action to perform', required: true, choices: [ { name: 'Start', value: 'start' }, { name: 'Stop', value: 'stop' }, { name: 'Status', value: 'status' } ] }, { name: 'channel', type: 7, // CHANNEL type description: 'Channel to send Bitcoin updates (required for start)', required: false }, { name: 'min-amount', type: 10, // NUMBER type description: 'Minimum BTC amount to show transactions (default: 0)', required: false, min_value: 0 } ] }, { name: 'trends', description: 'Show current Google Trends by country', options: [ { name: 'country', type: 3, // STRING type description: 'Country code (e.g., US, ES, GB, FR, DE)', required: false, choices: [ { name: 'United States', value: 'US' }, { name: 'Spain', value: 'ES' }, { name: 'United Kingdom', value: 'GB' }, { name: 'France', value: 'FR' }, { name: 'Germany', value: 'DE' }, { name: 'Japan', value: 'JP' }, { name: 'Brazil', value: 'BR' }, { name: 'Global', value: '' } ] } ] } ]; console.log('Started refreshing application (/) commands.'); // Register each command for (const command of commands) { await readyClient.application.commands.create(command); console.log(`Command /${command.name} registered successfully!`); } console.log('Successfully registered all application commands.'); } catch (error) { console.error('Error registering slash commands:', error); } }); // Utility functions for multimedia handling async function downloadFile(url, filePath) { const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } await pipeline(response.body, createWriteStream(filePath)); } function getFileExtension(url) { const urlPath = new URL(url).pathname; return path.extname(urlPath).toLowerCase(); } async function validateMultimediaUrl(url) { try { const response = await fetch(url, { method: 'HEAD', timeout: 5000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } }); const contentType = response.headers.get('content-type') || ''; const isMultimedia = contentType.startsWith('image/') || contentType.startsWith('video/') || contentType.startsWith('audio/'); return { valid: response.ok && isMultimedia, contentType, size: response.headers.get('content-length') }; } catch (error) { return { valid: false, error: error.message }; } } // Real search function using Unsplash for high-quality full-sized images async function searchImages(query, limit = 10) { try { const searchResults = []; // Primary search: Unsplash for high-quality photos try { const unsplashUrl = `https://unsplash.com/s/photos/${encodeURIComponent(query)}`; const pageResponse = await fetch(unsplashUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); if (pageResponse.ok) { const html = await pageResponse.text(); const $ = cheerio.load(html); // Extract full-size image URLs from Unsplash $('img[src*="images.unsplash.com"]').each((i, element) => { if (searchResults.length >= limit) return false; let src = $(element).attr('src'); const alt = $(element).attr('alt') || `${query} - Image ${i + 1}`; if (src && src.includes('images.unsplash.com')) { // Convert to full-size image URL if (src.includes('?')) { src = src.split('?')[0] + '?w=800&h=600&fit=crop'; } searchResults.push({ title: alt, link: src }); } }); } } catch (unsplashError) { // Continue to DuckDuckGo fallback } // Fallback: Use DuckDuckGo for search results if (searchResults.length < limit) { try { const searchUrl = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1&safe_search=strict`; const response = await fetch(searchUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); if (response.ok) { const data = await response.json(); // Try to get images from Results if (data.Results && data.Results.length > 0) { for (let i = 0; i < Math.min(data.Results.length, limit - searchResults.length); i++) { const result = data.Results[i]; if (result.Icon && result.Icon.URL && result.Icon.URL.includes('http')) { searchResults.push({ title: result.Text || `${query} - Result ${searchResults.length + 1}`, link: result.Icon.URL }); } } } } } catch (duckError) { // Final fallback handled below } } return searchResults.slice(0, limit); } catch (error) { console.error('Search failed:', error); throw new Error(`Failed to search for "${query}": ${error.message}`); } } // Real video search function using YouTube search async function searchVideos(query, limit = 5) { try { // Use DuckDuckGo to search for YouTube videos const searchUrl = `https://api.duckduckgo.com/?q=${encodeURIComponent(query + ' site:youtube.com')}&format=json&no_html=1&skip_disambig=1`; const response = await fetch(searchUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); if (!response.ok) { throw new Error(`Video search API failed: ${response.status}`); } const data = await response.json(); const videoResults = []; // Extract video results from DuckDuckGo response if (data.Results && data.Results.length > 0) { for (let i = 0; i < Math.min(data.Results.length, limit); i++) { const result = data.Results[i]; if (result.FirstURL && result.FirstURL.includes('youtube.com')) { videoResults.push({ title: result.Text || `${query} - Video ${i + 1}`, link: result.FirstURL, snippet: result.Text || `Video related to: ${query}` }); } } } // Try scraping YouTube search results if DuckDuckGo doesn't return enough if (videoResults.length === 0) { try { const youtubeSearchUrl = `https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`; const pageResponse = await fetch(youtubeSearchUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); if (pageResponse.ok) { const html = await pageResponse.text(); // Extract video IDs from YouTube search results const videoIdRegex = /"videoId":"([^"]+)"/g; const titleRegex = /"title":{"runs":\[{"text":"([^"]+)"/g; let match; const videoIds = []; const titles = []; while ((match = videoIdRegex.exec(html)) !== null && videoIds.length < limit) { videoIds.push(match[1]); } while ((match = titleRegex.exec(html)) !== null && titles.length < limit) { titles.push(match[1]); } for (let i = 0; i < Math.min(videoIds.length, limit); i++) { videoResults.push({ title: titles[i] || `${query} - Video ${i + 1}`, link: `https://www.youtube.com/watch?v=${videoIds[i]}`, snippet: `Video about: ${query}` }); } } } catch (youtubeError) { // YouTube scraping failed silently } } return videoResults.slice(0, limit); } catch (error) { console.error('Video search failed:', error); throw new Error(`Failed to search videos for "${query}": ${error.message}`); } } // Google Trends function to get real-time trending topics // Function to get trending topics from multiple sources async function getTrends(country = 'US') { try { console.log(`📊 Fetching trends for ${country}...`); // Try Reddit trending first (most reliable) try { const redditTrends = await getRedditTrending(); if (redditTrends && Object.keys(redditTrends).length > 0) { return redditTrends; } } catch (error) { console.log('Reddit trending failed, trying news...'); } // Fallback to news headlines try { const newsTrends = await getNewsHeadlines(country); if (newsTrends && Object.keys(newsTrends).length > 0) { return newsTrends; } } catch (error) { console.log('News headlines failed, using mock data...'); } // Final fallback - mock trending data return getMockTrends(country); } catch (error) { console.error('❌ Error fetching trends:', error.message); throw error; } } // Get trending from Reddit's popular posts async function getRedditTrending() { try { const response = await axios.get('https://www.reddit.com/r/popular.json?limit=15', { headers: { 'User-Agent': 'TrendBot/1.0' }, timeout: 10000 }); const posts = response.data.data.children; const groupedTrends = {}; posts.forEach(post => { const data = post.data; const subreddit = `r/${data.subreddit}`; if (!groupedTrends[subreddit]) { groupedTrends[subreddit] = []; } groupedTrends[subreddit].push({ title: data.title, traffic: `${data.score} upvotes`, url: `https://reddit.com${data.permalink}`, snippet: data.selftext ? data.selftext.substring(0, 100) + '...' : 'Click to view discussion' }); }); return groupedTrends; } catch (error) { console.error('Reddit trending error:', error.message); throw error; } } // Get news headlines as trending topics async function getNewsHeadlines(country) { try { // Map country codes to news regions const countryMap = { 'US': 'us', 'GB': 'gb', 'FR': 'fr', 'DE': 'de', 'ES': 'es', 'JP': 'jp', 'BR': 'br' }; const region = countryMap[country] || 'us'; // Using a free news API alternative const response = await axios.get(`https://saurav.tech/NewsAPI/top-headlines/category/general/${region}.json`, { timeout: 10000 }); const articles = response.data.articles.slice(0, 12); const groupedTrends = {}; articles.forEach(article => { const source = article.source?.name || 'News Source'; if (!groupedTrends[source]) { groupedTrends[source] = []; } groupedTrends[source].push({ title: article.title, traffic: 'Breaking News', url: article.url, snippet: article.description || 'Latest news headline' }); }); return groupedTrends; } catch (error) { console.error('News headlines error:', error.message); throw error; } } // Mock trending data as final fallback function getMockTrends(country) { const mockData = { 'TechCrunch': [ { title: 'AI Development Trends 2024', traffic: '50K+ searches', url: 'https://techcrunch.com', snippet: 'Latest developments in artificial intelligence and machine learning' } ], 'BBC News': [ { title: 'Global Market Updates', traffic: '25K+ searches', url: 'https://bbc.com/news', snippet: 'Latest financial and economic news from around the world' } ], 'Reddit Discussions': [ { title: 'Technology Innovations', traffic: '15K+ upvotes', url: 'https://reddit.com/r/technology', snippet: 'Community discussions about emerging technologies' } ] }; console.log(`📋 Using mock trends data for ${country}`); return mockData; } // Bitcoin transaction monitoring functions async function initBitcoinMonitoring() { try { // Make sure to stop any existing monitoring first if (btcSocket || isMonitoring) { stopBitcoinMonitoring(); // Wait a moment to ensure cleanup is complete await new Promise(resolve => setTimeout(resolve, 1000)); } // Clear processed transactions to start fresh processedTransactions.clear(); // Create a completely new Socket instance btcSocket = new Socket(); // Create the transaction handler function transactionHandler = async (tx) => { // Double check that monitoring is still active if (!btcMonitorChannel || !isMonitoring || !btcSocket) return; try { // Check if we've already processed this transaction if (processedTransactions.has(tx.hash)) { return; // Skip silently } // Add to processed transactions processedTransactions.add(tx.hash); // Process transaction data const txData = await processBitcoinTransaction(tx, blockexplorer); // Check if transaction meets minimum amount threshold if (parseFloat(txData.amount) < minBtcAmount) { return; // Skip this transaction if it's below the minimum amount } // Create Discord embed const embed = new EmbedBuilder() .setTitle('🟠 New Bitcoin Transaction') .setColor(0xF7931A) .addFields( { name: '🔗 Transaction Hash', value: `\`${txData.hash}\``, inline: false }, { name: '💰 Amount', value: `${txData.amount} BTC`, inline: true }, { name: '📊 Size', value: `${txData.size} bytes`, inline: true }, { name: '💸 Fee', value: txData.fee !== 'Unknown' ? `${(txData.fee / 100000000).toFixed(8)} BTC` : 'Unknown', inline: true }, { name: '⚡ Fee Rate', value: `${txData.feeRate} sat/byte`, inline: true } ) .setTimestamp() .setFooter({ text: `Live Bitcoin Network | Min: ${minBtcAmount} BTC` }); // Add input addresses (max 5) if (txData.inputAddresses.length > 0) { const inputs = txData.inputAddresses.slice(0, 5).join('\n'); embed.addFields({ name: `📤 Inputs (${txData.inputAddresses.length})`, value: `\`\`\`${inputs}${txData.inputAddresses.length > 5 ? '\n...' : ''}\`\`\``, inline: false }); } // Add output addresses (max 5) if (txData.outputAddresses.length > 0) { const outputs = txData.outputAddresses.slice(0, 5).join('\n'); embed.addFields({ name: `đŸ“Ĩ Outputs (${txData.outputAddresses.length})`, value: `\`\`\`${outputs}${txData.outputAddresses.length > 5 ? '\n...' : ''}\`\`\``, inline: false }); } // Send to Discord channel await btcMonitorChannel.send({ embeds: [embed] }); } catch (error) { console.error('Error processing Bitcoin transaction:', error); } }; // Register the transaction handler btcSocket.onTransaction(transactionHandler); return true; } catch (error) { console.error('Failed to initialize Bitcoin monitoring:', error); return false; } } async function processBitcoinTransaction(tx, blockexplorer) { try { // Process inputs with balances const inputAddresses = tx.inputs .filter(input => input.prev_out && input.prev_out.addr) .map(input => input.prev_out.addr); // Process outputs with balances const outputAddresses = tx.out .filter(output => output.addr) .map(output => output.addr); // Calculate total input value const totalInputValue = tx.inputs.reduce((sum, input) => { return sum + (input.prev_out ? (input.prev_out.value || 0) : 0); }, 0); // Calculate total output value const totalOutputValue = tx.out.reduce((sum, output) => sum + (output.value || 0), 0); // Calculate fee in satoshis const feeSatoshis = totalInputValue - totalOutputValue; // Calculate fee rate (sat/byte) let feeRate = 'Unknown'; if (tx.size && feeSatoshis > 0) { feeRate = Math.round(feeSatoshis / tx.size); } // Convert total output value to BTC (since this represents the transaction amount) const totalValue = totalOutputValue / 100000000; return { hash: tx.hash, amount: totalValue.toFixed(8), size: tx.size || 'Unknown', feeRate: feeRate, fee: feeSatoshis > 0 ? feeSatoshis : 'Unknown', inputAddresses: inputAddresses, outputAddresses: outputAddresses, timestamp: new Date() }; } catch (error) { console.error('Error processing transaction data:', error); throw error; } } function stopBitcoinMonitoring() { if (btcSocket) { isMonitoring = false; // Properly close the WebSocket connection try { if (btcSocket.ws) { if (btcSocket.ws.readyState === 1) { // WebSocket.OPEN = 1 btcSocket.ws.close(); } // Force close and cleanup btcSocket.ws.onmessage = null; btcSocket.ws.onopen = null; btcSocket.ws.onclose = null; btcSocket.ws.onerror = null; } } catch (error) { console.error('Error closing WebSocket:', error); } // Clear all references btcSocket = null; transactionHandler = null; btcMonitorChannel = null; minBtcAmount = 0; // Reset minimum amount // Clear processed transactions to prevent duplicate detection issues processedTransactions.clear(); return true; } return false; } client.on(Events.InteractionCreate, async interaction => { if (!interaction.isChatInputCommand()) return; console.log(`Command received: ${interaction.commandName}`); try { switch (interaction.commandName) { case 'ping': await interaction.reply('Pong!'); break; case 'send': const targetChannel = interaction.options.getChannel('channel'); const messageContent = interaction.options.getString('message'); if (!targetChannel.isTextBased()) { await interaction.reply({ content: 'I can only send messages to text channels!', ephemeral: true }); return; } try { await targetChannel.send(messageContent); await interaction.reply({ content: `Message sent to ${targetChannel}!`, ephemeral: true }); } catch (error) { console.error('Error sending message:', error); await interaction.reply({ content: `Failed to send message: ${error.message}`, ephemeral: true }); } break; case 'search': await interaction.deferReply(); const query = interaction.options.getString('query'); const mediaType = interaction.options.getString('type'); const count = interaction.options.getInteger('count') || 3; try { if (mediaType === 'image') { // Use real search function const results = await searchImages(query, count); if (results.length === 0) { await interaction.editReply(`❌ No image results found for "${query}". The search engines may not have returned any results for this query.`); return; } // Create embeds for images (skip validation since we're getting real search results) const embeds = results.map((result, i) => { return new EmbedBuilder() .setTitle(`${result.title}`) .setImage(result.link) .setColor(0x00AE86) .setFooter({ text: `Search result ${i + 1} of ${results.length}` }); }); await interaction.editReply({ content: `🔍 Found ${results.length} images for "${query}":`, embeds: embeds.slice(0, 10) // Discord allows max 10 embeds }); } else if (mediaType === 'video') { // Use real video search function const results = await searchVideos(query, count); if (results.length === 0) { await interaction.editReply(`❌ No video results found for "${query}". The search engines may not have returned any results for this query.`); return; } // Create embeds for videos const embeds = results.map((result, i) => { const embed = new EmbedBuilder() .setTitle(`${result.title}`) .setURL(result.link) .setColor(0xFF0000) .setDescription(result.snippet || 'No description available'); // Add thumbnail for YouTube videos if (result.link.includes('youtube.com') || result.link.includes('youtu.be')) { const videoId = result.link.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\n?#]+)/); if (videoId) { embed.setThumbnail(`https://img.youtube.com/vi/${videoId[1]}/maxresdefault.jpg`); } } return embed; }); await interaction.editReply({ content: `🔍 Found ${results.length} videos for "${query}":`, embeds: embeds }); } } catch (error) { console.error('Search error:', error); await interaction.editReply(`❌ Error searching for "${query}": ${error.message}\n\nThis could be due to rate limiting or the search service being unavailable.`); } break; case 'download': await interaction.deferReply(); const fileUrl = interaction.options.getString('url'); const fileTitle = interaction.options.getString('title') || 'Downloaded file'; try { // Validate the URL const validation = await validateMultimediaUrl(fileUrl); if (!validation.valid) { await interaction.editReply(`Invalid or inaccessible multimedia URL: ${validation.error || 'Unknown error'}`); return; } // Check file size (Discord has 8MB limit for regular users) const fileSize = parseInt(validation.size || '0'); const maxSize = 8 * 1024 * 1024; // 8MB in bytes if (fileSize > maxSize) { await interaction.editReply({ content: `File is too large (${(fileSize / 1024 / 1024).toFixed(2)}MB). Discord limit is 8MB.`, embeds: [new EmbedBuilder() .setTitle(fileTitle) .setURL(fileUrl) .setDescription('Click the title to view the file directly') .setColor(0xFFFF00)] }); return; } // Download the file const extension = getFileExtension(fileUrl) || '.bin'; const fileName = `download_${Date.now()}${extension}`; const filePath = path.join(process.cwd(), fileName); await downloadFile(fileUrl, filePath); // Create attachment const attachment = new AttachmentBuilder(filePath, { name: `${fileTitle}${extension}` }); // Create embed with file info const embed = new EmbedBuilder() .setTitle(fileTitle) .setDescription(`Downloaded from: ${fileUrl}`) .addFields( { name: 'Content Type', value: validation.contentType, inline: true }, { name: 'File Size', value: `${(fileSize / 1024).toFixed(2)} KB`, inline: true } ) .setColor(0x00FF00) .setTimestamp(); await interaction.editReply({ content: 'File downloaded successfully!', embeds: [embed], files: [attachment] }); // Clean up the temporary file setTimeout(() => { if (existsSync(filePath)) { unlinkSync(filePath); } }, 5000); // Delete after 5 seconds } catch (error) { console.error('Download error:', error); await interaction.editReply(`Error downloading file: ${error.message}`); } break; case 'trends': await interaction.deferReply(); const countryCode = interaction.options.getString('country') || 'US'; const countryNames = { 'US': 'United States', 'ES': 'Spain', 'GB': 'United Kingdom', 'FR': 'France', 'DE': 'Germany', 'JP': 'Japan', 'BR': 'Brazil', '': 'Global' }; try { const trendGroups = await getTrends(countryCode); const countryName = countryNames[countryCode] || countryCode; if (Object.keys(trendGroups).length === 0) { await interaction.editReply(`❌ No trending topics found for ${countryName}.`); return; } // Create embeds for each category const embeds = []; let trendCount = 0; for (const [category, trends] of Object.entries(trendGroups)) { if (embeds.length >= 10) break; // Discord limit const embed = new EmbedBuilder() .setTitle(`📈 ${category}`) .setColor(0x4285F4) .setFooter({ text: `Trending Topics â€ĸ ${countryName}` }) .setTimestamp(); // Add trends from this category const topTrends = trends.slice(0, 3); topTrends.forEach((trend, index) => { trendCount++; let fieldValue = `**${trend.traffic}**\n`; if (trend.snippet) { fieldValue += `${trend.snippet}\n`; } if (trend.url) { fieldValue += `[🔗 Read More](${trend.url})`; } embed.addFields({ name: `${trend.title}`, value: fieldValue, inline: false }); }); embeds.push(embed); } await interaction.editReply({ content: `🌍 **Current Trending Topics in ${countryName}**\nShowing ${trendCount} trending stories:`, embeds: embeds }); } catch (error) { console.error('Trends error:', error); await interaction.editReply(`❌ Error fetching trends for ${countryName}: ${error.message}\n\nThis could be due to rate limiting or the service being unavailable.`); } break; case 'btc-monitor': const action = interaction.options.getString('action'); const channel = interaction.options.getChannel('channel'); const minAmount = interaction.options.getNumber('min-amount'); try { switch (action) { case 'start': if (!channel) { await interaction.reply({ content: 'Please specify a channel to send Bitcoin updates to!', ephemeral: true }); return; } if (!channel.isTextBased()) { await interaction.reply({ content: 'I can only send Bitcoin updates to text channels!', ephemeral: true }); return; } if (isMonitoring) { await interaction.reply({ content: `Bitcoin monitoring is already running in ${btcMonitorChannel}!`, ephemeral: true }); return; } await interaction.deferReply({ ephemeral: true }); btcMonitorChannel = channel; minBtcAmount = minAmount || 0; // Set minimum amount or default to 0 const success = await initBitcoinMonitoring(); if (success) { isMonitoring = true; await interaction.editReply(`✅ Bitcoin transaction monitoring started! Real-time transactions with minimum ${minBtcAmount} BTC will be posted to the specified channel.`); // Send initial message to the monitoring channel const startEmbed = new EmbedBuilder() .setTitle('🟠 Bitcoin Transaction Monitor Started') .setDescription(`Real-time Bitcoin transactions with minimum ${minBtcAmount} BTC will be displayed here.`) .setColor(0xF7931A) .setTimestamp(); await btcMonitorChannel.send({ embeds: [startEmbed] }); } else { await interaction.editReply('❌ Failed to start Bitcoin monitoring. Please check the console for errors.'); } break; case 'stop': if (!isMonitoring) { await interaction.reply({ content: 'Bitcoin monitoring is not currently running!', ephemeral: true }); return; } const stopped = stopBitcoinMonitoring(); if (stopped) { await interaction.reply({ content: 'âšī¸ Bitcoin transaction monitoring stopped.', ephemeral: true }); } else { await interaction.reply({ content: '❌ Failed to stop Bitcoin monitoring.', ephemeral: true }); } break; case 'status': const statusEmbed = new EmbedBuilder() .setTitle('🟠 Bitcoin Monitor Status') .addFields( { name: 'Status', value: isMonitoring ? 'đŸŸĸ Running' : '🔴 Stopped', inline: true }, { name: 'Channel', value: btcMonitorChannel ? btcMonitorChannel.toString() : 'None', inline: true }, { name: 'Min Amount', value: `${minBtcAmount} BTC`, inline: true }, { name: 'Processed TXs', value: `${processedTransactions.size}`, inline: true }, { name: 'WebSocket', value: btcSocket ? 'đŸŸĸ Connected' : '🔴 Disconnected', inline: true } ) .setColor(isMonitoring ? 0x00FF00 : 0xFF0000) .setTimestamp(); await interaction.reply({ embeds: [statusEmbed], ephemeral: true }); break; } } catch (error) { console.error('Error handling btc-monitor command:', error); await interaction.reply({ content: `Error with Bitcoin monitoring: ${error.message}`, ephemeral: true }); } break; } } catch (error) { console.error('Error handling command:', error); // If the interaction hasn't been replied to yet if (!interaction.replied && !interaction.deferred) { await interaction.reply({ content: 'An error occurred while executing this command!', ephemeral: true }); } else if (interaction.deferred) { await interaction.editReply('An error occurred while executing this command!'); } } }); // Use environment variable for token (more secure) client.login(process.env.DISCORD_TOKEN);