discordbot/index.js
ale c21e4d891a
google trends
Signed-off-by: ale <ale@manalejandro.com>
2025-06-08 06:45:29 +02:00

1071 lines
43 KiB
JavaScript

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);