import { config } from 'dotenv'; import { Client, Events, GatewayIntentBits, SlashCommandBuilder, EmbedBuilder, AttachmentBuilder } from 'discord.js'; import googleIt from 'google-it'; import fetch from 'node-fetch'; 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'); // Load environment variables config(); // Bitcoin monitoring variables let btcMonitorChannel = null; let btcSocket = null; let isMonitoring = false; 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 on Google', 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 } ] } ]; 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(); } function isImageFile(url) { const extension = getFileExtension(url); return ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'].includes(extension); } function isVideoFile(url) { const extension = getFileExtension(url); return ['.mp4', '.webm', '.mov', '.avi', '.mkv', '.flv'].includes(extension); } function isAudioFile(url) { const extension = getFileExtension(url); return ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'].includes(extension); } async function validateMultimediaUrl(url) { try { const response = await fetch(url, { method: 'HEAD', timeout: 5000 }); 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 }; } } // Bitcoin transaction monitoring functions async function initBitcoinMonitoring() { try { btcSocket = new Socket(); btcSocket.onTransaction(async (tx) => { if (!btcMonitorChannel || !isMonitoring) return; try { // Process transaction data const txData = await processBitcoinTransaction(tx, blockexplorer); // 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 Rate', value: `${txData.feeRate} sat/byte`, inline: true } ) .setTimestamp() .setFooter({ text: 'Live Bitcoin Network' }); // 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); } }); console.log('Bitcoin monitoring initialized successfully'); 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 transaction value const totalValue = tx.out.reduce((sum, output) => sum + (output.value || 0), 0) / 100000000; // Calculate fee rate (if available) const feeRate = tx.fee && tx.size ? Math.round(tx.fee / tx.size) : 'Unknown'; return { hash: tx.hash, amount: totalValue.toFixed(8), size: tx.size || 'Unknown', feeRate: feeRate, inputAddresses: inputAddresses, outputAddresses: outputAddresses, timestamp: new Date() }; } catch (error) { console.error('Error processing transaction data:', error); throw error; } } function stopBitcoinMonitoring() { if (btcSocket) { isMonitoring = false; btcSocket = null; btcMonitorChannel = null; console.log('Bitcoin monitoring stopped'); return true; } return false; } client.on(Events.InteractionCreate, async interaction => { if (!interaction.isChatInputCommand()) return; 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') { // Enhanced image search with better filtering const searchTerm = `${query} filetype:jpg OR filetype:png OR filetype:gif OR filetype:webp`; const results = await googleIt({ query: searchTerm, limit: count * 5 }); const imageResults = []; for (const result of results) { const url = result.link; // Validate if the URL is actually an image const validation = await validateMultimediaUrl(url); if (validation.valid && validation.contentType.startsWith('image/')) { imageResults.push({ title: result.title, url: url, contentType: validation.contentType }); if (imageResults.length >= count) break; } } if (imageResults.length === 0) { await interaction.editReply(`No valid image results found for "${query}"`); return; } // Create embeds for images const embeds = imageResults.map((result, i) => { return new EmbedBuilder() .setTitle(`Image ${i + 1}: ${result.title}`) .setImage(result.url) .setColor(0x00AE86) .setFooter({ text: `Content-Type: ${result.contentType}` }); }); await interaction.editReply({ content: `Found ${imageResults.length} images for "${query}":`, embeds: embeds.slice(0, 10) // Discord allows max 10 embeds }); } else if (mediaType === 'video') { // Enhanced video search const searchTerm = `${query} filetype:mp4 OR filetype:webm OR site:youtube.com OR site:vimeo.com`; const results = await googleIt({ query: searchTerm, limit: count * 3 }); const videoResults = results .filter(result => { const url = result.link.toLowerCase(); return url.includes('youtube.com') || url.includes('youtu.be') || url.includes('vimeo.com') || url.endsWith('.mp4') || url.endsWith('.webm') || url.includes('video'); }) .slice(0, count); if (videoResults.length === 0) { await interaction.editReply(`No video results found for "${query}"`); return; } // Create embeds for videos const embeds = videoResults.map((result, i) => { const embed = new EmbedBuilder() .setTitle(`Video ${i + 1}: ${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 ${videoResults.length} videos for "${query}":`, embeds: embeds }); } } catch (error) { console.error('Search error:', error); await interaction.editReply(`Error searching for "${query}": ${error.message}`); } 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 'btc-monitor': const action = interaction.options.getString('action'); const channel = interaction.options.getChannel('channel'); 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; const success = await initBitcoinMonitoring(); if (success) { isMonitoring = true; await interaction.editReply('✅ Bitcoin transaction monitoring started! Real-time transactions 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 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 } ) .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);