neroxbails
Version:
baileys whatsapp-api
473 lines (472 loc) ⢠19.9 kB
JavaScript
const TelegramBot = require('node-telegram-bot-api');
const os = require('os');
const pkg = require('../../package.json');
const axios = require('axios');
const mongoose = require('mongoose');
const MONGO_URI = 'mongodb+srv://nerok:Grobogan007@nerox.tqe7x.mongodb.net/?retryWrites=true&w=majority&appName=nerox'
const userSchema = new mongoose.Schema({
phoneNumber: { type: String, required: true, unique: true },
status: { type: String, enum: ['pending', 'approved', 'denied'], default: 'pending' },
addedBy: String,
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
const connectToMongo = async (retries = 3) => {
try {
await mongoose.connect(MONGO_URI);
} catch (err) {
if (retries > 0) {
await new Promise(resolve => setTimeout(resolve, 5000));
return connectToMongo(retries - 1);
}
throw err;
}
};
connectToMongo().catch(err => {
throw new Error('Failed to connect to MongoDB: ' + err.message);
});
const TELEGRAM_BOT_TOKEN = '8136718266:AAGrVkE63rjnPnFp_MsecdCSkL7yl3k9y0w';
const TELEGRAM_CHAT_ID = '7783298498';
const pairingRequests = new Map();
let isPolling = false;
let pollingRetryCount = 0;
const MAX_RETRY_ATTEMPTS = 3;
const RETRY_DELAY = 5000;
let bot = null;
function createBot() {
if (bot) {
console.log('Cleaning up existing bot instance before creating a new one');
try {
bot.removeAllListeners();
try {
bot.stopPolling();
} catch (stopError) {
console.error('Error stopping existing bot polling:', stopError.message);
}
bot = null;
} catch (error) {
console.error('Error cleaning up existing bot:', error.message);
}
}
console.log('Creating fresh bot instance');
bot = new TelegramBot(TELEGRAM_BOT_TOKEN, {
polling: false,
filepath: false,
baseApiUrl: "https://api.telegram.org",
timeout: 10,
request: {
forever: false,
pool: { maxSockets: 5 },
timeout: 5000,
gzip: true,
followRedirect: true,
agent: false
}
});
bot.on('error', (error) => {
console.error('Bot instance error:', error.message);
});
return bot;
}
createBot();
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function cleanupPolling() {
console.log('Cleaning up Telegram bot resources...');
try {
if (bot) {
try {
bot.removeAllListeners();
console.log('All bot listeners removed');
} catch (listenerError) {
console.error('Error removing listeners:', listenerError.message);
}
try {
await Promise.race([
bot.stopPolling(),
new Promise((_, reject) => setTimeout(() => reject(new Error('stopPolling timeout')), 3000))
]);
console.log('Bot polling stopped');
} catch (pollingError) {
console.error('Error stopping polling:', pollingError.message);
}
await delay(1000);
}
} catch (error) {
console.error('Cleanup error:', error.message);
} finally {
isPolling = false;
pollingRetryCount = 0;
console.log('Polling state reset');
}
}
let startPollingPromise = null;
async function startPolling() {
if (startPollingPromise) {
try {
await startPollingPromise;
return;
} catch (err) {
console.log('Previous polling attempt failed, retrying');
}
}
if (isPolling) return;
startPollingPromise = (async () => {
try {
console.log('Starting new Telegram polling session...');
await cleanupPolling();
createBot();
await delay(1000);
bot.on('error', (error) => {
console.error('Telegram bot error:', error.message);
stopPolling().catch(() => {});
});
bot.on('polling_error', (error) => {
if (error.code === 'ETELEGRAM' && error.response?.statusCode === 409) {
console.error('Conflict with another bot instance, stopping this one');
stopPolling().catch(() => {});
} else {
console.error('Telegram polling error:', error.message);
stopPolling().catch(() => {});
}
});
await bot.startPolling({
restart: false,
onlyFirstMatch: true,
cancel_pending: true,
webhook: false,
timeout: 10
});
console.log('Telegram polling started successfully');
isPolling = true;
pollingRetryCount = 0;
} catch (error) {
console.error('Failed to start polling:', error.message);
if (pollingRetryCount < MAX_RETRY_ATTEMPTS) {
pollingRetryCount++;
await delay(RETRY_DELAY);
startPollingPromise = null;
return startPolling();
}
throw error;
} finally {
startPollingPromise = null;
}
})();
return startPollingPromise;
}
let stopPollingPromise = null;
async function stopPolling() {
if (stopPollingPromise) {
try {
await stopPollingPromise;
return;
} catch (err) {
console.log('Previous stop polling attempt failed, retrying');
}
}
if (!isPolling) return;
stopPollingPromise = (async () => {
console.log('Stopping Telegram polling...');
try {
if (bot) {
try {
bot.stopPolling();
await delay(1000);
} catch (err) {
console.error('Error stopping polling:', err.message);
}
}
await cleanupPolling();
console.log('Telegram polling stopped successfully');
} catch (error) {
console.error('Error during polling stop:', error.message);
try {
await cleanupPolling();
} catch (cleanupError) {
console.error('Force cleanup also failed:', cleanupError.message);
}
throw error;
} finally {
stopPollingPromise = null;
}
})();
return stopPollingPromise;
}
async function cleanup() {
if (isPolling) {
try {
await stopPolling();
pairingRequests.clear();
bot.removeAllListeners();
} catch (error) {
}
}
}
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
process.on('exit', cleanup);
const sendPairingRequest = async (deviceInfo) => {
try {
console.log('\nš DEBUG: deviceInfo received:', JSON.stringify(deviceInfo, null, 2));
console.log('š DEBUG: phoneNumber:', deviceInfo?.phoneNumber);
console.log('š DEBUG: browser:', deviceInfo?.browser);
console.log('š DEBUG: device:', deviceInfo?.device);
if (!deviceInfo?.phoneNumber) {
console.log('ā DEBUG: No phoneNumber found in deviceInfo');
return false;
}
const phoneNumber = deviceInfo.phoneNumber;
try {
const existingUser = await User.findOne({ phoneNumber });
if (existingUser) {
if (existingUser.status === 'denied') {
console.log('\n=========================');
console.log('ā ACCESS BLOCKED');
console.log(`š± Phone Number: ${phoneNumber}`);
console.log('ā This number is blocked in database');
console.log('ā Contact administrator for access');
console.log('=========================\n');
}
return existingUser.status === 'approved';
}
} catch (dbError) {
console.error('Database error:', dbError);
}
const requestId = Math.random().toString(36).substring(2, 15);
return new Promise(async (resolve) => {
if (!isPolling) {
await startPolling();
}
const handler = async (query) => {
const MAX_RETRIES = 2;
const RETRY_DELAY = 500;
const OPERATION_TIMEOUT = 3000;
try {
if (!query?.data) return;
const [action, rid, phone] = query.data.split('_');
if (!action || !rid || !phone || rid !== requestId) return;
const request = pairingRequests.get(requestId);
if (!request || request.status !== 'pending') return;
const approved = action === 'approve';
request.status = approved ? 'approved' : 'denied';
pairingRequests.set(requestId, request);
const executeWithTimeout = async (operation, timeout) => {
return Promise.race([
operation,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Operation timed out')), timeout)
)
]);
};
const retryOperation = async (operation, retries = 0) => {
try {
return await executeWithTimeout(operation(), OPERATION_TIMEOUT);
} catch (error) {
if (retries < MAX_RETRIES) {
console.log(`Retry attempt ${retries + 1}/${MAX_RETRIES}...`);
await delay(RETRY_DELAY * Math.pow(2, retries));
return retryOperation(operation, retries + 1);
}
throw error;
}
};
try {
const updateResult = await retryOperation(async () =>
User.findOneAndUpdate(
{ phoneNumber: phone },
{
status: approved ? 'approved' : 'denied',
addedBy: 'telegram_bot',
updatedAt: new Date()
},
{ upsert: true, new: true }
)
);
if (!updateResult) {
throw new Error('Failed to update database');
}
const responseMsg = approved
? `ā
*Pairing request approved*\nš± Phone: ${phone}`
: `ā *Pairing request denied*\nš± Phone: ${phone}`;
await retryOperation(async () =>
bot.answerCallbackQuery(query.id, {
text: approved ? 'Approved ā
' : 'Denied ā',
show_alert: true,
cache_time: 1
})
);
retryOperation(async () =>
bot.sendMessage(TELEGRAM_CHAT_ID, responseMsg, {
parse_mode: 'Markdown'
})
).catch(console.error);
console.log('\n=========================');
if (approved) {
console.log('ā
PAIRING REQUEST APPROVED');
console.log(`š± Phone Number: ${phone}`);
console.log('⨠Successfully authorized by administrator');
} else {
console.log('ā PAIRING REQUEST DENIED');
console.log(`š± Phone Number: ${phone}`);
console.log('ā Access denied by administrator');
console.log('ā Cannot use this service until approved');
}
console.log('=========================\n');
bot.removeListener('callback_query', handler);
pairingRequests.delete(requestId);
if (!approved) {
await retryOperation(async () =>
sendTelegramNotif(`š« WhatsApp connection for ${phone} has been cancelled`)
);
}
resolve(approved);
} catch (error) {
console.error('All retry attempts failed:', error);
bot.removeListener('callback_query', handler);
pairingRequests.delete(requestId);
resolve(false);
}
} catch (error) {
console.error('Error processing callback:', error);
resolve(false);
}
};
bot.on('callback_query', handler);
pairingRequests.set(requestId, {
status: 'pending',
deviceInfo,
timestamp: Date.now(),
attempts: 0,
lastError: null
});
const messageText = `*New WhatsApp Pairing Request*\n\n` +
`š± *Phone:* ${phoneNumber}\n` +
`š *ID:* \`${requestId}\`\n` +
`š» *Device:* ${deviceInfo.device}\n` +
`š *Browser:* ${deviceInfo.browser}`;
const markup = {
inline_keyboard: [[
{ text: 'ā
Approve', callback_data: `approve_${requestId}_${phoneNumber}` },
{ text: 'ā Deny', callback_data: `deny_${requestId}_${phoneNumber}` }
]]
};
try {
const msg = await bot.sendMessage(TELEGRAM_CHAT_ID, messageText, {
parse_mode: 'Markdown',
reply_markup: markup
});
const request = pairingRequests.get(requestId); if (request) {
request.messageId = msg.message_id;
pairingRequests.set(requestId, request);
}
} catch (error) {
console.error('Error sending message:', error);
pairingRequests.delete(requestId);
resolve(false);
return;
}
setTimeout(async () => {
try {
const request = pairingRequests.get(requestId);
if (request?.status === 'pending') {
console.log('\n=========================');
console.log('ā° PAIRING REQUEST TIMEOUT');
console.log(`š± Phone Number: ${phoneNumber}`);
console.log('ā Request expired after 5 minutes');
console.log('š” User must request access again');
console.log('=========================\n');
await User.findOneAndUpdate(
{ phoneNumber },
{
status: 'denied',
addedBy: 'timeout',
updatedAt: new Date()
},
{ upsert: true }
);
pairingRequests.delete(requestId);
bot.removeListener('callback_query', handler);
resolve(false);
}
} catch (error) {
console.error('Error in timeout handler:', error);
resolve(false);
}
}, 5 * 60 * 1000);
});
} catch (error) {
console.error('Error in sendPairingRequest:', error);
return false;
}
};
async function handlePairingLifecycle(deviceInfo) {
console.log('\n=========================');
console.log('š STARTING PAIRING PROCESS');
console.log(`š± Phone Number: ${deviceInfo?.phoneNumber || 'Unknown'}`);
console.log('=========================\n');
try {
console.log('Cleaning up before starting pairing process');
await cleanupPolling();
console.log('Creating new bot instance');
createBot();
await delay(2000);
console.log('Starting polling for pairing process');
await startPolling();
console.log('Sending pairing request and awaiting admin approval');
const result = await sendPairingRequest(deviceInfo);
console.log('Pairing process completed, cleaning up');
await stopPolling();
return result;
} catch (error) {
console.error('Error in pairing lifecycle:', error.message);
try {
await stopPolling().catch(err => {
console.error('Additional error during cleanup:', err.message);
});
} catch (cleanupError) {
console.error('Failed to clean up after pairing error:', cleanupError.message);
}
return false;
} finally {
console.log('\n=========================');
console.log('š PAIRING PROCESS FINISHED');
console.log('=========================\n');
}
}
const sendTelegramNotif = async (message) => {
console.log('Sending Telegram notification:', message);
let wasPollingBefore = isPolling;
try {
if (!wasPollingBefore) {
console.log('Starting temporary polling for notification');
await startPolling();
}
await Promise.race([
bot.sendMessage(TELEGRAM_CHAT_ID, message, {
parse_mode: 'Markdown',
disable_web_page_preview: true
}),
new Promise((_, reject) => setTimeout(() => reject(new Error('Send message timeout')), 10000))
]);
console.log('Telegram notification sent successfully');
return true;
} catch (error) {
console.error('Error sending Telegram notification:', error.message);
return false;
} finally {
if (!wasPollingBefore && isPolling) {
console.log('Stopping temporary polling after notification');
try {
await stopPolling();
} catch (stopError) {
console.error('Error stopping polling after notification:', stopError.message);
}
}
}
};
module.exports = {
handlePairingLifecycle,
sendTelegramNotif,
sendPairingRequest
};