initial commit
Signed-off-by: ale <ale@manalejandro.com>
This commit is contained in:
commit
63a562eba7
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
.env
|
||||
package-lock.json
|
||||
*.lock
|
122
README.md
Normal file
122
README.md
Normal file
@ -0,0 +1,122 @@
|
||||
# Discord Multimedia Bot
|
||||
|
||||
A powerful Discord bot that can send messages and search for multimedia files on Google with enhanced display capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- 🏓 **Ping Command** - Simple ping command to check if the bot is working
|
||||
- 📨 **Message Sending** - Send messages to any channel in the server
|
||||
- 🔍 **Multimedia Search** - Search for images and videos with embedded preview
|
||||
- ⬇️ **File Download** - Download and share multimedia files directly in Discord
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone this repository
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. Create a `.env` file with your bot token:
|
||||
```env
|
||||
DISCORD_TOKEN=your_token_here
|
||||
```
|
||||
4. Run the bot:
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### 🏓 Ping
|
||||
```
|
||||
/ping
|
||||
```
|
||||
Simple response to check if the bot is online.
|
||||
|
||||
### 📨 Send Message
|
||||
```
|
||||
/send channel:#channel-name message:Your message here
|
||||
```
|
||||
Sends a message to the specified channel.
|
||||
- `channel`: The target channel (required)
|
||||
- `message`: The message content (required)
|
||||
|
||||
### 🔍 Search Media
|
||||
```
|
||||
/search query:cute puppies type:image count:3
|
||||
```
|
||||
Searches for multimedia content on Google with enhanced display.
|
||||
- `query`: What to search for (required)
|
||||
- `type`: Either "image" or "video" (required)
|
||||
- `count`: Number of results to show (1-5, default: 3)
|
||||
|
||||
**Image Search Features:**
|
||||
- Displays images directly in Discord with embeds
|
||||
- Shows content type information
|
||||
- Validates image URLs before displaying
|
||||
- Supports JPG, PNG, GIF, and WebP formats
|
||||
|
||||
**Video Search Features:**
|
||||
- Creates rich embeds with video information
|
||||
- Shows YouTube video thumbnails automatically
|
||||
- Includes video descriptions when available
|
||||
- Supports YouTube, Vimeo, and direct video files
|
||||
|
||||
### ⬇️ Download Files
|
||||
```
|
||||
/download url:https://example.com/image.jpg title:My Image
|
||||
```
|
||||
Downloads and shares multimedia files directly in Discord.
|
||||
- `url`: Direct URL to the multimedia file (required)
|
||||
- `title`: Optional title for the file (optional)
|
||||
|
||||
**Download Features:**
|
||||
- Validates multimedia URLs before downloading
|
||||
- Checks file size (8MB Discord limit)
|
||||
- Supports images, videos, and audio files
|
||||
- Automatically cleans up temporary files
|
||||
- Shows file information in rich embeds
|
||||
|
||||
## Security Features
|
||||
|
||||
- ✅ Environment variables for token security
|
||||
- ✅ URL validation for multimedia content
|
||||
- ✅ File size checking
|
||||
- ✅ Temporary file cleanup
|
||||
- ✅ Error handling and user feedback
|
||||
|
||||
## Supported File Types
|
||||
|
||||
**Images:** JPG, JPEG, PNG, GIF, WebP, BMP, SVG
|
||||
**Videos:** MP4, WebM, MOV, AVI, MKV, FLV
|
||||
**Audio:** MP3, WAV, OGG, M4A, AAC, FLAC
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
- **dotenv** for secure environment variable management
|
||||
- **Enhanced fetch** with proper headers and error handling
|
||||
- **Discord.js v14** with modern slash commands
|
||||
- **Google-it** for search functionality
|
||||
- **Stream pipeline** for efficient file downloads
|
||||
- **Rich embeds** for multimedia display
|
||||
|
||||
## Security Note
|
||||
|
||||
⚠️ **Important:** Keep your bot token private and never commit it to a public repository. Always use environment variables for sensitive data.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The bot includes comprehensive error handling for:
|
||||
- Invalid URLs
|
||||
- Network timeouts
|
||||
- File size limitations
|
||||
- Permission errors
|
||||
- API rate limits
|
||||
|
||||
## Performance Features
|
||||
|
||||
- Efficient file streaming for downloads
|
||||
- Automatic temporary file cleanup
|
||||
- URL validation before processing
|
||||
- Content type verification
|
||||
- Background processing for long operations
|
612
index.js
Normal file
612
index.js
Normal file
@ -0,0 +1,612 @@
|
||||
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);
|
16
package.json
Normal file
16
package.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "discordbot",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"author": "ale",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"blockchain.info": "^2.12.1",
|
||||
"discord.js": "^14.18.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"google-it": "^1.6.4",
|
||||
"node-fetch": "^2.7.0"
|
||||
}
|
||||
}
|
66
test-bot.sh
Executable file
66
test-bot.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Discord Bot Test Script
|
||||
# This script demonstrates how to test the bot functionality
|
||||
|
||||
echo "🤖 Discord Multimedia Bot - Test Guide"
|
||||
echo "======================================="
|
||||
echo ""
|
||||
|
||||
echo "📋 Available Commands:"
|
||||
echo ""
|
||||
echo "1. /ping"
|
||||
echo " - Tests if the bot is responsive"
|
||||
echo " - Expected response: 'Pong!'"
|
||||
echo ""
|
||||
|
||||
echo "2. /send channel:#general message:Hello World!"
|
||||
echo " - Sends a message to the specified channel"
|
||||
echo " - Make sure the bot has permission to send messages"
|
||||
echo ""
|
||||
|
||||
echo "3. /search query:cats type:image count:2"
|
||||
echo " - Searches for cat images"
|
||||
echo " - Will display embedded images directly in Discord"
|
||||
echo ""
|
||||
|
||||
echo "4. /search query:funny videos type:video count:3"
|
||||
echo " - Searches for funny videos"
|
||||
echo " - Will show video links with thumbnails"
|
||||
echo ""
|
||||
|
||||
echo "5. /download url:https://example.com/image.jpg title:Test Image"
|
||||
echo " - Downloads and shares a multimedia file"
|
||||
echo " - File must be under 8MB for Discord"
|
||||
echo ""
|
||||
|
||||
echo "🔧 Bot Status:"
|
||||
if pgrep -f "node index.js" > /dev/null; then
|
||||
echo "✅ Bot is currently running"
|
||||
echo "📊 Process ID: $(pgrep -f 'node index.js')"
|
||||
else
|
||||
echo "❌ Bot is not running"
|
||||
echo "💡 Start with: node index.js"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📁 Project Structure:"
|
||||
ls -la /home/ale/projects/discord/discordbot/ | grep -E '\.(js|json|md|env)$'
|
||||
|
||||
echo ""
|
||||
echo "🌐 Environment:"
|
||||
if [ -f "/home/ale/projects/discord/discordbot/.env" ]; then
|
||||
echo "✅ .env file exists"
|
||||
else
|
||||
echo "❌ .env file missing - create it with DISCORD_TOKEN=your_token"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📦 Dependencies:"
|
||||
if [ -f "/home/ale/projects/discord/discordbot/package.json" ]; then
|
||||
echo "✅ package.json exists"
|
||||
echo "📋 Installed packages:"
|
||||
cd /home/ale/projects/discord/discordbot && npm list --depth=0 2>/dev/null | grep -E '(discord.js|dotenv|google-it|node-fetch)'
|
||||
else
|
||||
echo "❌ package.json missing"
|
||||
fi
|
Loading…
x
Reference in New Issue
Block a user