tes-wc-bot
Version:
🌐 Advanced Telegram Bot for Cloudflare Wildcard Domain Management with Telegram Notifications
294 lines (256 loc) • 11.1 kB
JavaScript
const { Telegraf } = require('telegraf');
const path = require('path');
const fs = require('fs');
// Import all handlers
const configHandlers = require('../handlers/configHandlers');
const domainHandlers = require('../handlers/domainHandlers');
const cloudflareHandlers = require('../handlers/cloudflareHandlers');
const generalHandlers = require('../handlers/generalHandlers');
const adminHandlers = require('../handlers/adminHandlers');
// Import services
const NotificationService = require('../services/NotificationService');
class WildcardBot {
constructor(options = {}) {
this.options = {
configPath: options.configPath || './config/default.js',
dataPath: options.dataPath || './data',
...options,
};
// Load configuration
this.loadConfig();
// Initialize bot with timeout configuration
this.bot = new Telegraf(process.env.BOT_TOKEN, {
telegram: {
agent: null,
webhookReply: false,
// Increase timeout to prevent ETIMEDOUT errors
timeout: 30000, // 30 seconds timeout
retryAfter: 1,
// Add polling configuration
polling: {
timeout: 30,
limit: 100,
allowed_updates: ['message', 'callback_query']
}
}
});
// Setup data directory
this.setupDataDirectory();
// Initialize handlers
this.initializeHandlers();
console.log('🤖 Wildcard Bot initialized successfully!');
}
loadConfig() {
try {
// Load user config
const configPath = path.resolve(this.options.configPath);
if (fs.existsSync(configPath)) {
this.config = require(configPath);
console.log(`✅ Configuration loaded from: ${configPath}`);
} else {
// Fallback to default config
this.config = require('../config/default');
console.log('⚠️ Using default configuration');
}
} catch (error) {
console.error('❌ Failed to load configuration:', error.message);
// Use minimal default config
this.config = {
ADMIN_IDS: [],
MAX_CUSTOM_DOMAINS: 5,
MAX_DOMAINS_PER_USER: 10,
NOTIFICATIONS: {
TELEGRAM: { enabled: true },
WHATSAPP: { enabled: false },
},
DEFAULT_DOMAINS: ['example.com'],
};
}
// Make config globally available
global.BOT_CONFIG = this.config;
}
setupDataDirectory() {
const dataDir = path.resolve(this.options.dataPath);
// Create data directory if it doesn't exist
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
console.log(`📁 Created data directory: ${dataDir}`);
}
// Initialize data files if they don't exist
const dataFiles = [
{ file: 'config.json', content: {} },
{ file: 'domains.json', content: this.config.DEFAULT_DOMAINS || [] },
{ file: 'customDomains.json', content: {} },
{ file: 'adminUsage.json', content: {} },
];
dataFiles.forEach(({ file, content }) => {
const filePath = path.join(dataDir, file);
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, JSON.stringify(content, null, 2));
console.log(`📄 Created data file: ${file}`);
}
});
}
initializeHandlers() {
// Register all command handlers
this.registerGeneralCommands();
this.registerConfigCommands();
this.registerDomainCommands();
this.registerCloudflareCommands();
this.registerAdminCommands();
// Error handling with better timeout management
this.bot.catch(async (err, ctx) => {
console.error('❌ Bot error:', err);
// Handle different types of errors
if (err.code === 'ETIMEDOUT') {
console.error('⏰ Request timeout - retrying operation');
try {
await ctx.reply('⏰ Koneksi timeout. Bot sedang mencoba ulang...');
} catch (retryError) {
console.error('❌ Failed to send timeout message:', retryError.message);
}
} else if (err.code === 'ENOTFOUND' || err.code === 'ECONNRESET') {
console.error('🌐 Network connection error');
try {
await ctx.reply('🌐 Masalah koneksi jaringan. Silakan coba lagi dalam beberapa saat.');
} catch (retryError) {
console.error('❌ Failed to send network error message:', retryError.message);
}
} else if (err.response && err.response.error_code === 429) {
console.error('📊 Rate limit exceeded');
try {
await ctx.reply('📊 Terlalu banyak permintaan. Silakan tunggu sebentar dan coba lagi.');
} catch (retryError) {
console.error('❌ Failed to send rate limit message:', retryError.message);
}
} else {
// Generic error
try {
await ctx.reply('❌ Terjadi kesalahan. Silakan coba lagi atau hubungi admin.');
} catch (retryError) {
console.error('❌ Failed to send error message:', retryError.message);
}
}
});
console.log('🔧 All command handlers registered');
}
registerGeneralCommands() {
this.bot.start(generalHandlers.startCommand);
this.bot.help(generalHandlers.helpCommand);
this.bot.command('ping', (ctx) => ctx.reply('🏓 Pong!'));
}
registerConfigCommands() {
this.bot.command('addcf', configHandlers.addCloudflareConfig);
this.bot.command('cfconfig', configHandlers.viewCloudflareConfig);
this.bot.command('updatecf', configHandlers.updateCloudflareConfig);
this.bot.command('deletecf', configHandlers.deleteCloudflareConfig);
}
registerDomainCommands() {
this.bot.command('listdomain', domainHandlers.listDomains);
this.bot.command('new', domainHandlers.addCustomDomain);
this.bot.command('mysub', domainHandlers.mySubdomains);
this.bot.command('searchdomain', domainHandlers.handleSearchDomain);
this.bot.command('delsub', domainHandlers.handleDelSub);
this.bot.command('analytics', domainHandlers.domainAnalytics);
this.bot.command('clearcache', domainHandlers.clearCache);
// Handle CONFIRM DELETE text
this.bot.hears(/^CONFIRM DELETE$/i, async (ctx) => {
const { deleteCloudflareConfig } = require('../handlers/configHandlers');
try {
// Process the deletion
ctx.message.text = '/deletecf CONFIRM DELETE';
await deleteCloudflareConfig(ctx);
} catch (error) {
console.error('❌ Error processing CONFIRM DELETE:', error);
await ctx.reply(
'❌ Terjadi kesalahan saat menghapus konfigurasi. Silakan coba lagi.'
);
}
});
}
registerCloudflareCommands() {
this.bot.command('setupwildcard', cloudflareHandlers.setupWildcard);
}
registerAdminCommands() {
// Only register admin commands if admin IDs are configured
if (this.config.ADMIN_IDS && this.config.ADMIN_IDS.length > 0) {
this.bot.command('stats', adminHandlers.botStats);
this.bot.command('broadcast', adminHandlers.broadcastMessage);
this.bot.command('userinfo', adminHandlers.userInfo);
this.bot.command('testnotif', adminHandlers.testNotification);
console.log(
`👑 Admin commands registered for ${this.config.ADMIN_IDS.length} admin(s)`
);
} else {
console.log('⚠️ No admin IDs configured - admin commands disabled');
}
}
async start() {
try {
// Validate bot token
if (!process.env.BOT_TOKEN) {
throw new Error('BOT_TOKEN environment variable is required');
}
// Start bot
await this.bot.launch();
console.log('🚀 Wildcard Bot started successfully!');
console.log(`📱 Bot username: @${this.bot.botInfo.username}`);
console.log(`👑 Admin IDs: ${this.config.ADMIN_IDS.join(', ') || 'None configured'}`);
console.log('✨ Bot is ready to receive commands!');
// Enable graceful stop
process.once('SIGINT', () => this.stop());
process.once('SIGTERM', () => this.stop());
return true;
} catch (error) {
console.error('❌ Failed to start bot:', error.message);
throw error;
}
}
stop() {
console.log('👋 Stopping Wildcard Bot...');
this.bot.stop('SIGINT');
console.log('✅ Bot stopped successfully');
}
// Utility methods for users
async sendNotification(message, type = 'info') {
try {
// Only send Telegram notifications
if (this.config.NOTIFICATIONS?.TELEGRAM?.enabled) {
await NotificationService.sendTelegramNotification(this.bot.telegram, message);
return true;
} else {
console.log('⚠️ Telegram notifications not enabled');
return false;
}
} catch (error) {
console.error('❌ Failed to send notification:', error.message);
return false;
}
}
// Get bot statistics
getBotStats() {
const configPath = path.join(this.options.dataPath, 'config.json');
const customDomainsPath = path.join(this.options.dataPath, 'customDomains.json');
let userCount = 0;
let domainCount = 0;
try {
if (fs.existsSync(configPath)) {
const configs = JSON.parse(fs.readFileSync(configPath, 'utf8'));
userCount = Object.keys(configs).length;
}
if (fs.existsSync(customDomainsPath)) {
const domains = JSON.parse(fs.readFileSync(customDomainsPath, 'utf8'));
domainCount = Object.values(domains).flat().length;
}
} catch (error) {
console.error('Error reading stats:', error.message);
}
return {
users: userCount,
domains: domainCount,
uptime: process.uptime(),
version: require('../package.json').version,
};
}
}
module.exports = WildcardBot;