diff --git a/bot.js b/bot.js index d8ccb29..f7bf78d 100644 --- a/bot.js +++ b/bot.js @@ -1130,16 +1130,16 @@ const formatConversionRate = (conversionRate, fiatCode) => { return `${rate} ${fiatCode} / USDC`; }; -const createDepositKeyboard = (depositId) => { - return { - inline_keyboard: [[ - { - text: `๐Ÿ”— View Deposit ${depositId}`, - url: depositLink(depositId) - } - ]] - }; -}; +// const createDepositKeyboard = (depositId) => { +// return { +// inline_keyboard: [[ +// { +// text: `๐Ÿ”— View Deposit ${depositId}`, +// url: depositLink(depositId) +// } +// ]] +// }; +// }; // Sniper logic async function checkSniperOpportunity(depositId, depositAmount, currencyHash, conversionRate, verifierAddress) { @@ -1461,7 +1461,7 @@ bot.onText(/\/sniper (?!threshold)(.+)/, async (msg, match) => { const platform = parts[1] ? parts[1].toLowerCase() : null; const supportedCurrencies = Object.values(currencyHashToCode); - const supportedPlatforms = ['revolut', 'wise', 'cashapp', 'venmo', 'zelle']; + const supportedPlatforms = ['revolut', 'wise', 'cashapp', 'venmo', 'zelle', 'mercado pago', 'monzo','paypal']; if (!supportedCurrencies.includes(currency)) { bot.sendMessage(chatId, `โŒ Currency '${currency}' not supported.\n\n*Supported currencies:*\n${supportedCurrencies.join(', ')}`, { parse_mode: 'Markdown' }); @@ -1494,41 +1494,1058 @@ bot.onText(/\/unsnipe (.+)/, async (msg, match) => { bot.sendMessage(chatId, `๐ŸŽฏ Stopped sniping ${currency}${platformText}.`, { parse_mode: 'Markdown' }); }); -// Handle /start command - show help -bot.onText(/\/start/, (msg) => { +// Menu creation functions +const createMainMenu = () => { + return { + inline_keyboard: [ + [ + { text: '๐Ÿ“Š Deposit Tracking', callback_data: 'menu_deposits' }, + { text: '๐ŸŽฏ Sniper Setup', callback_data: 'menu_snipers' } + ], + [ + { text: '๐Ÿ“‹ My Status', callback_data: 'action_list' }, + { text: '๐Ÿ”ง Settings', callback_data: 'menu_settings' } + ], + [ + { text: 'โ“ Help', callback_data: 'action_help' }, + { text: '๐Ÿ“ˆ System Status', callback_data: 'action_status' } + ] + ] + }; +}; + +const createDepositMenu = () => { + return { + inline_keyboard: [ + [ + { text: '๐ŸŒ Listen to ALL Deposits', callback_data: 'action_deposit_all' } + ], + [ + { text: '๐Ÿ›‘ Stop Listening to All', callback_data: 'action_deposit_stop' } + ], + [ + { text: 'โž• Track Specific Deposit', callback_data: 'prompt_deposit_add' } + ], + [ + { text: 'โž– Remove Specific Deposit', callback_data: 'prompt_deposit_remove' } + ], + [ + { text: '๐Ÿ  Back to Main Menu', callback_data: 'menu_main' } + ] + ] + }; +}; + +const createSniperMenu = () => { + return { + inline_keyboard: [ + [ + { text: '๐ŸŽฏ Add Currency Sniper', callback_data: 'prompt_sniper_add' } + ], + [ + { text: '๐Ÿ“Š Set Alert Threshold', callback_data: 'prompt_threshold' } + ], + [ + { text: '๐Ÿ“‹ View My Snipers', callback_data: 'action_sniper_list' } + ], + [ + { text: '๐Ÿ—‘๏ธ Remove Sniper', callback_data: 'prompt_sniper_remove' } + ], + [ + { text: '๐Ÿงน Clear All Snipers', callback_data: 'action_sniper_clear' } + ], + [ + { text: '๐Ÿ  Back to Main Menu', callback_data: 'menu_main' } + ] + ] + }; +}; + +const createSettingsMenu = () => { + return { + inline_keyboard: [ + [ + { text: '๐Ÿ—‘๏ธ Clear All Data', callback_data: 'confirm_clearall' } + ], + [ + { text: '๐Ÿ”„ Refresh Status', callback_data: 'action_status' } + ], + [ + { text: '๐Ÿ  Back to Main Menu', callback_data: 'menu_main' } + ] + ] + }; +}; + +const createCurrencyKeyboard = () => { + const currencies = ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'JPY', 'CHF', 'ARS', 'NOK', 'DKK', 'PLN', 'CZK', 'NZD', 'RON']; + const keyboard = []; + + // Create rows of 3 currencies each + for (let i = 0; i < currencies.length; i += 3) { + const row = currencies.slice(i, i + 3).map(curr => ({ + text: curr, + callback_data: `select_currency_${curr.toLowerCase()}` + })); + keyboard.push(row); + } + + // Add more currencies and cancel buttons + keyboard.push([ + { text: '๐ŸŒ More Currencies', callback_data: 'show_more_currencies' }, + { text: 'โŒ Cancel', callback_data: 'menu_snipers' } + ]); + + return { inline_keyboard: keyboard }; +}; + +const createPlatformKeyboard = (currency) => { + return { + inline_keyboard: [ + [ + { text: '๐ŸŒ All Platforms', callback_data: `sniper_${currency}_all` } + ], + [ + { text: '๐Ÿ’ณ Revolut', callback_data: `sniper_${currency}_revolut` }, + { text: '๐Ÿฆ Wise', callback_data: `sniper_${currency}_wise` } + ], + [ + { text: '๐Ÿ’ฐ PayPal', callback_data: `sniper_${currency}_paypal` }, + { text: '๐Ÿช Zelle', callback_data: `sniper_${currency}_zelle` } + ], + [ + { text: '๐Ÿ“ฑ CashApp', callback_data: `sniper_${currency}_cashapp` }, + { text: '๐Ÿ’ธ Venmo', callback_data: `sniper_${currency}_venmo` } + ], + [ + { text: '๐Ÿฆ Mercado Pago', callback_data: `sniper_${currency}_mercado pago` }, + { text: '๐Ÿ’ธ Monzo', callback_data: `sniper_${currency}_monzo` } + ], + [ + { text: '๐Ÿ’ธ PayPal', callback_data: `sniper_${currency}_paypal` } + ], + [ + { text: '๐Ÿ”™ Back to Currencies', callback_data: 'prompt_sniper_add' } + ] + ] + }; +}; + +const createConfirmKeyboard = (action) => { + return { + inline_keyboard: [ + [ + { text: 'โœ… Yes, Confirm', callback_data: `confirm_${action}` }, + { text: 'โŒ Cancel', callback_data: 'menu_main' } + ] + ] + }; +}; + +// Store user states for multi-step interactions +const userStates = new Map(); + +// Update the /start command to show the main menu +bot.onText(/\/start/, async (msg) => { const chatId = msg.chat.id; - const helpMessage = ` -๐Ÿค– *ZKP2P Tracker Commands:* + await db.initUser(chatId, msg.from.username, msg.from.first_name, msg.from.last_name); + + const welcomeMessage = ` +๐Ÿค– **Welcome to ZKP2P Tracker!** -**Deposit Tracking:** -- \`/deposit all\` - Listen to ALL deposits (every event) -- \`/deposit stop\` - Stop listening to all deposits -- \`/deposit 123\` - Track a specific deposit -- \`/deposit 123,456,789\` - Track multiple deposits -- \`/remove 123\` - Stop tracking specific deposit(s) +Track ZKP2P deposits and get arbitrage alerts in real-time. Use the menu below to get started: -**Sniper (Arbitrage Alerts):** -- \`/sniper eur\` - Snipe EUR on ALL platforms -- \`/sniper eur revolut\` - Snipe EUR only on Revolut -- \`/sniper usd zelle\` - Snipe USD only on Zelle -- \`/sniper threshold 0.5\` - Set your alert threshold to 0.5% -- \`/sniper list\` - Show active sniper settings -- \`/sniper clear\` - Clear all sniper settings -- \`/unsnipe eur\` - Stop sniping EUR (all platforms) -- \`/unsnipe eur wise\` - Stop sniping EUR on Wise only +โ€ข **Deposit Tracking** - Monitor specific deposits or all activity +โ€ข **Sniper Setup** - Get alerts for profitable arbitrage opportunities +โ€ข **My Status** - View your current tracking settings +โ€ข **Settings** - Configure thresholds and manage your data -**General:** -- \`/list\` - Show all tracking status (deposits + snipers) -- \`/clearall\` - Stop all tracking and clear everything -- \`/status\` - Check WebSocket connection and settings -- \`/help\` - Show this help message +Choose an option below to begin: +`.trim(); -*Note: Each user has their own settings. Sniper alerts you when deposits offer better exchange rates than market!* + bot.sendMessage(chatId, welcomeMessage, { + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); +}); + +// Add /menu command for easy access +bot.onText(/\/menu/, (msg) => { + const chatId = msg.chat.id; + + bot.sendMessage(chatId, '๐Ÿ“‹ **Main Menu**\n\nChoose what you\'d like to do:', { + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); +}); + +// Handle callback queries (button presses) +bot.on('callback_query', async (callbackQuery) => { + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + const data = callbackQuery.data; + + // Initialize user + await db.initUser(chatId, callbackQuery.from.username, callbackQuery.from.first_name, callbackQuery.from.last_name); + + try { + // Handle menu navigation + if (data === 'menu_main') { + await bot.editMessageText('๐Ÿ“‹ **Main Menu**\n\nChoose what you\'d like to do:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); + } + + else if (data === 'menu_deposits') { + await bot.editMessageText('๐Ÿ“Š **Deposit Tracking**\n\nManage your deposit notifications:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createDepositMenu() + }); + } + + else if (data === 'menu_snipers') { + await bot.editMessageText('๐ŸŽฏ **Sniper Setup**\n\nConfigure arbitrage alerts for better exchange rates:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createSniperMenu() + }); + } + + else if (data === 'menu_settings') { + await bot.editMessageText('๐Ÿ”ง **Settings**\n\nManage your bot configuration:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createSettingsMenu() + }); + } + + // Handle deposit actions + else if (data === 'action_deposit_all') { + await db.setUserListenAll(chatId, true); + await bot.editMessageText('๐ŸŒ **Now listening to ALL deposits!**\n\nYou will receive notifications for every event on every deposit.\n\nUse the menu to manage other settings.', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createDepositMenu() + }); + } + + else if (data === 'action_deposit_stop') { + await db.setUserListenAll(chatId, false); + await bot.editMessageText('๐Ÿ›‘ **Stopped listening to all deposits.**\n\nYou will now only receive notifications for specifically tracked deposits.', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createDepositMenu() + }); + } + + // Handle prompts for user input + else if (data === 'prompt_deposit_add') { + userStates.set(chatId, { action: 'waiting_deposit_add', messageId }); + await bot.editMessageText('โž• **Add Specific Deposit**\n\nPlease send the deposit ID(s) you want to track.\n\nExamples:\nโ€ข `123` - track single deposit\nโ€ข `123,456,789` - track multiple deposits\n\nSend your message now:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: { + inline_keyboard: [[ + { text: 'โŒ Cancel', callback_data: 'menu_deposits' } + ]] + } + }); + } + + else if (data === 'prompt_deposit_remove') { + userStates.set(chatId, { action: 'waiting_deposit_remove', messageId }); + await bot.editMessageText('โž– **Remove Specific Deposit**\n\nPlease send the deposit ID(s) you want to stop tracking.\n\nExamples:\nโ€ข `123` - remove single deposit\nโ€ข `123,456,789` - remove multiple deposits\n\nSend your message now:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: { + inline_keyboard: [[ + { text: 'โŒ Cancel', callback_data: 'menu_deposits' } + ]] + } + }); + } + + // Handle sniper actions + else if (data === 'prompt_sniper_add') { + await bot.editMessageText('๐ŸŽฏ **Add Currency Sniper**\n\nSelect a currency to snipe for arbitrage opportunities:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createCurrencyKeyboard() + }); + } + + else if (data === 'show_more_currencies') { + const moreCurrencies = ['AED', 'CNY', 'SEK', 'ILS', 'INR', 'KES', 'MXN', 'HKD', 'MYR', 'PHP', 'SAR', 'SGD', 'HUF', 'THB', 'TRY', 'VND', 'IDR', 'ZAR']; + const keyboard = []; + + for (let i = 0; i < moreCurrencies.length; i += 3) { + const row = moreCurrencies.slice(i, i + 3).map(curr => ({ + text: curr, + callback_data: `select_currency_${curr.toLowerCase()}` + })); + keyboard.push(row); + } + + keyboard.push([ + { text: '๐Ÿ”™ Back', callback_data: 'prompt_sniper_add' }, + { text: 'โŒ Cancel', callback_data: 'menu_snipers' } + ]); + + await bot.editMessageText('๐ŸŒ **More Currencies**\n\nSelect a currency:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: { inline_keyboard: keyboard } + }); + } + + // Handle currency selection + else if (data.startsWith('select_currency_')) { + const currency = data.replace('select_currency_', '').toUpperCase(); + await bot.editMessageText(`๐ŸŽฏ **Snipe ${currency}**\n\nChoose which platform(s) to monitor for ${currency} arbitrage opportunities:`, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createPlatformKeyboard(currency.toLowerCase()) + }); + } + + // Handle sniper setup + else if (data.startsWith('sniper_')) { + const parts = data.split('_'); + const currency = parts[1].toUpperCase(); + const platform = parts[2] === 'all' ? null : parts[2]; + + await db.setUserSniper(chatId, currency, platform); + + const platformText = platform ? ` on ${platform}` : ' (all platforms)'; + await bot.editMessageText(`๐ŸŽฏ **Sniper activated for ${currency}${platformText}!**\n\nYou'll be alerted when new deposits offer better rates than market.\n\nConfigure more settings below:`, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createSniperMenu() + }); + } + + else if (data === 'prompt_threshold') { + userStates.set(chatId, { action: 'waiting_threshold', messageId }); + const currentThreshold = await db.getUserThreshold(chatId); + await bot.editMessageText(`๐Ÿ“Š **Set Alert Threshold**\n\nCurrent threshold: **${currentThreshold}%**\n\nEnter your new threshold percentage (e.g., 0.5 for 0.5%):\n\n*You'll be alerted when deposits offer rates this much better than market rates.*\n\nSend your message now:`, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: { + inline_keyboard: [[ + { text: 'โŒ Cancel', callback_data: 'menu_snipers' } + ]] + } + }); + } + + else if (data === 'action_sniper_list') { + const snipers = await db.getUserSnipers(chatId); + if (snipers.length === 0) { + await bot.editMessageText('๐ŸŽฏ **No Sniper Currencies Set**\n\nYou haven\'t configured any currency snipers yet.', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createSniperMenu() + }); + } else { + let message = `๐ŸŽฏ **Active Snipers:**\n\n`; + snipers.forEach(sniper => { + const platformText = sniper.platform ? ` on ${sniper.platform}` : ' (all platforms)'; + message += `โ€ข ${sniper.currency}${platformText}\n`; + }); + + await bot.editMessageText(message, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createSniperMenu() + }); + } + } + + else if (data === 'prompt_sniper_remove') { + userStates.set(chatId, { action: 'waiting_sniper_remove', messageId }); + await bot.editMessageText('๐Ÿ—‘๏ธ **Remove Sniper**\n\nEnter the currency (and optionally platform) to remove:\n\nExamples:\nโ€ข `EUR` - remove EUR from all platforms\nโ€ข `EUR revolut` - remove EUR only from Revolut\n\nSend your message now:', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: { + inline_keyboard: [[ + { text: 'โŒ Cancel', callback_data: 'menu_snipers' } + ]] + } + }); + } + + else if (data === 'action_sniper_clear') { + await bot.editMessageText('๐Ÿงน **Clear All Snipers**\n\nAre you sure you want to remove ALL sniper configurations?', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createConfirmKeyboard('sniper_clear_confirmed') + }); + } + + // Handle list action + else if (data === 'action_list') { + const userDeposits = await db.getUserDeposits(chatId); + const userStates = await db.getUserDepositStates(chatId); + const listeningAll = await db.getUserListenAll(chatId); + const snipers = await db.getUserSnipers(chatId); + const threshold = await db.getUserThreshold(chatId); + + let message = '๐Ÿ“‹ **Your Current Status:**\n\n'; + + if (listeningAll) { + message += `๐ŸŒ **Listening to ALL deposits**\n\n`; + } + + if (snipers.length > 0) { + message += `๐ŸŽฏ **Active Snipers:** (${threshold}% threshold)\n`; + snipers.forEach(sniper => { + const platformText = sniper.platform ? ` on ${sniper.platform}` : ' (all platforms)'; + message += `โ€ข ${sniper.currency}${platformText}\n`; + }); + message += `\n`; + } + + const idsArray = Array.from(userDeposits).sort((a, b) => a - b); + if (idsArray.length > 0) { + message += `๐Ÿ“Š **Tracking ${idsArray.length} specific deposits:**\n\n`; + idsArray.slice(0, 10).forEach(id => { // Show max 10 + const state = userStates.get(id); + const status = state ? state.status : 'tracking'; + const emoji = status === 'fulfilled' ? 'โœ…' : + status === 'pruned' ? '๐ŸŸก' : '๐Ÿ‘€'; + message += `${emoji} \`${id}\` - ${status}\n`; + }); + + if (idsArray.length > 10) { + message += `\n... and ${idsArray.length - 10} more\n`; + } + } + + if (idsArray.length === 0 && !listeningAll && snipers.length === 0) { + message += `No active tracking or snipers configured.`; + } + + await bot.editMessageText(message, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); + } + + // Handle help action + else if (data === 'action_help') { + const helpMessage = ` +๐Ÿค– **ZKP2P Tracker Help** + +**๐Ÿ”น Deposit Tracking:** +โ€ข Track specific deposit IDs for targeted notifications +โ€ข Listen to ALL deposits for complete market monitoring +โ€ข Get real-time alerts for order creation, fulfillment, and cancellation + +**๐Ÿ”น Sniper Alerts:** +โ€ข Monitor specific currencies for arbitrage opportunities +โ€ข Set custom profit thresholds (default 0.2%) +โ€ข Choose specific platforms or monitor all +โ€ข Get instant alerts when profitable rates appear + +**๐Ÿ”น Example Scenarios:** +โ€ข Track deposit #123 to see when orders are filled +โ€ข Snipe EUR on Revolut for arbitrage opportunities +โ€ข Listen to all deposits to monitor market activity +โ€ข Set 0.5% threshold to only see highly profitable opportunities + +**๐Ÿ”น Commands Available:** +You can still use text commands if preferred: +โ€ข \`/deposit 123\` - Track specific deposit +โ€ข \`/sniper eur revolut\` - Snipe EUR on Revolut +โ€ข \`/list\` - Show current status +โ€ข \`/menu\` - Show this menu anytime + +Questions? The menu system makes everything easier! ๐Ÿš€ `.trim(); + + await bot.editMessageText(helpMessage, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); + } + + // Handle status action + else if (data === 'action_status') { + const wsConnected = resilientProvider?.isConnected || false; + const wsStatus = wsConnected ? '๐ŸŸข Connected' : '๐Ÿ”ด Disconnected'; + + let dbStatus = '๐Ÿ”ด Disconnected'; + try { + const { data, error } = await supabase.from('users').select('chat_id').limit(1); + if (!error) dbStatus = '๐ŸŸข Connected'; + } catch (error) { + console.error('Database test failed:', error); + } + + let botStatus = '๐Ÿ”ด Disconnected'; + try { + await bot.getMe(); + botStatus = '๐ŸŸข Connected'; + } catch (error) { + console.error('Bot test failed:', error); + } + + let message = `๐Ÿ”ง **System Status:**\n\n`; + message += `โ€ข **WebSocket:** ${wsStatus}\n`; + message += `โ€ข **Database:** ${dbStatus}\n`; + message += `โ€ข **Telegram:** ${botStatus}\n\n`; + + if (!wsConnected && resilientProvider) { + message += `โš ๏ธ **WebSocket reconnection attempts:** ${resilientProvider.reconnectAttempts}/${resilientProvider.maxReconnectAttempts}\n\n`; + } + + message += `All systems operational! ๐Ÿš€`; + + await bot.editMessageText(message, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); + } + + // Handle confirmation actions + else if (data === 'confirm_clearall') { + await bot.editMessageText('๐Ÿ—‘๏ธ **Clear All Data**\n\nโš ๏ธ This will remove:\nโ€ข All tracked deposits\nโ€ข All sniper configurations\nโ€ข All your settings\n\nAre you absolutely sure?', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createConfirmKeyboard('clearall_confirmed') + }); + } + + else if (data === 'confirm_clearall_confirmed') { + await db.clearUserData(chatId); + await bot.editMessageText('๐Ÿ—‘๏ธ **All Data Cleared**\n\nCleared all tracked deposit IDs, stopped listening to all deposits, and cleared all sniper settings.\n\nYou can start fresh now!', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); + } + + else if (data === 'confirm_sniper_clear_confirmed') { + await db.removeUserSniper(chatId); + await bot.editMessageText('๐Ÿงน **All Snipers Cleared**\n\nRemoved all sniper configurations. You can set up new ones anytime.', { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createSniperMenu() + }); + } + + // Answer the callback query to remove loading state + await bot.answerCallbackQuery(callbackQuery.id); + + } catch (error) { + console.error('Error handling callback query:', error); + await bot.answerCallbackQuery(callbackQuery.id, { text: 'โŒ An error occurred. Please try again.' }); + } +}); + +// Handle text messages based on user state +bot.on('message', async (msg) => { + const chatId = msg.chat.id; + const text = msg.text; - bot.sendMessage(chatId, helpMessage, { parse_mode: 'Markdown' }); + // Skip if it's a command (already handled) + if (text?.startsWith('/')) return; + + const userState = userStates.get(chatId); + if (!userState) return; + + const { action, messageId } = userState; + + try { + if (action === 'waiting_deposit_add') { + const newIds = text.split(/[,\s]+/).map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + + if (newIds.length === 0) { + bot.sendMessage(chatId, 'โŒ No valid deposit IDs provided. Please try again with numbers only.', { + reply_markup: { + inline_keyboard: [[ + { text: '๐Ÿ”™ Back to Menu', callback_data: 'menu_deposits' } + ]] + } + }); + return; + } + + for (const id of newIds) { + await db.addUserDeposit(chatId, id); + } + + const userDeposits = await db.getUserDeposits(chatId); + const idsArray = Array.from(userDeposits).sort((a, b) => a - b); + + // Update the original menu message + await bot.editMessageText(`โœ… **Successfully Added!**\n\nNow tracking deposit IDs: \`${idsArray.join(', ')}\`\n\nYou'll receive notifications for all events on these deposits.`, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createDepositMenu() + }); + + // Delete user's input message + try { + await bot.deleteMessage(chatId, msg.message_id); + } catch (e) { + // Ignore if can't delete + } + } + + else if (action === 'waiting_deposit_remove') { + const idsToRemove = text.split(/[,\s]+/).map(id => parseInt(id.trim())).filter(id => !isNaN(id)); + + if (idsToRemove.length === 0) { + bot.sendMessage(chatId, 'โŒ No valid deposit IDs provided. Please try again with numbers only.', { + reply_markup: { + inline_keyboard: [[ + { text: '๐Ÿ”™ Back to Menu', callback_data: 'menu_deposits' } + ]] + } + }); + return; + } + + for (const id of idsToRemove) { + await db.removeUserDeposit(chatId, id); + } + + const userDeposits = await db.getUserDeposits(chatId); + const remainingIds = Array.from(userDeposits).sort((a, b) => a - b); + + let message = 'โœ… **Successfully Removed!**\n\n'; + if (remainingIds.length > 0) { + message += `Still tracking: \`${remainingIds.join(', ')}\``; + } else { + message += `No deposits currently being tracked.`; + } + + await bot.editMessageText(message, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createDepositMenu() + }); + + try { + await bot.deleteMessage(chatId, msg.message_id); + } catch (e) { + // Ignore if can't delete + } + } + + else if (action === 'waiting_threshold') { + const threshold = parseFloat(text.trim()); + + if (isNaN(threshold)) { + bot.sendMessage(chatId, 'โŒ Invalid threshold. Please provide a number (e.g., 0.5 for 0.5%)', { + reply_markup: { + inline_keyboard: [[ + { text: '๐Ÿ”™ Back to Menu', callback_data: 'menu_snipers' } + ]] + } + }); + return; + } + + await db.setUserThreshold(chatId, threshold); + + await bot.editMessageText(`๐Ÿ“Š **Threshold Updated!**\n\nSniper threshold set to **${threshold}%**\n\nYou'll now be alerted when deposits offer rates ${threshold}% or better than market rates.`, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createSniperMenu() + }); + + try { + await bot.deleteMessage(chatId, msg.message_id); + } catch (e) { + // Ignore if can't delete + } + } + + else if (action === 'waiting_sniper_remove') { + const parts = text.trim().toLowerCase().split(' '); + const currency = parts[0].toUpperCase(); + const platform = parts[1] ? parts[1].toLowerCase() : null; + + await db.removeUserSniper(chatId, currency, platform); + + const platformText = platform ? ` on ${platform}` : ' (all platforms)'; + + await bot.editMessageText(`โœ… **Sniper Removed!**\n\nStopped sniping ${currency}${platformText}.`, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'Markdown', + reply_markup: createSniperMenu() + }); + + try { + await bot.deleteMessage(chatId, msg.message_id); + } catch (e) { + // Ignore if can't delete + } + } + + // Clear user state after handling + userStates.delete(chatId); + + } catch (error) { + console.error('Error handling user input:', error); + bot.sendMessage(chatId, 'โŒ An error occurred. Please try again.', { + reply_markup: { + inline_keyboard: [[ + { text: '๐Ÿ”™ Back to Menu', callback_data: 'menu_main' } + ]] + } + }); + userStates.delete(chatId); + } +}); + +// Enhanced notification messages to include menu options +const createDepositKeyboard = (depositId) => { + return { + inline_keyboard: [ + [ + { + text: `๐Ÿ”— View Deposit ${depositId}`, + url: depositLink(depositId) + } + ], + [ + { + text: 'โš™๏ธ Manage Tracking', + callback_data: 'menu_deposits' + } + ] + ] + }; +}; + +// Update the existing help command to redirect to menu +bot.onText(/\/help/, (msg) => { + const chatId = msg.chat.id; + + bot.sendMessage(chatId, 'โ“ **Need Help?**\n\nUse the interactive menu below for easy navigation, or type `/menu` anytime to access it.', { + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); +}); + +// Add persistent menu command +bot.onText(/\/quickmenu/, (msg) => { + const chatId = msg.chat.id; + + const quickMenuKeyboard = { + keyboard: [ + ['๐Ÿ“Š Deposits', '๐ŸŽฏ Snipers'], + ['๐Ÿ“‹ Status', 'โš™๏ธ Settings'], + ['๐Ÿ”ง System', 'โ“ Help'] + ], + resize_keyboard: true, + one_time_keyboard: false + }; + + bot.sendMessage(chatId, '๐ŸŽ›๏ธ **Quick Access Menu**\n\nUse these buttons for quick access to main functions:', { + parse_mode: 'Markdown', + reply_markup: quickMenuKeyboard + }); +}); + +// Handle persistent menu button presses +bot.onText(/^๐Ÿ“Š Deposits$/, (msg) => { + const chatId = msg.chat.id; + bot.sendMessage(chatId, '๐Ÿ“Š **Deposit Tracking**\n\nManage your deposit notifications:', { + parse_mode: 'Markdown', + reply_markup: createDepositMenu() + }); }); +bot.onText(/^๐ŸŽฏ Snipers$/, (msg) => { + const chatId = msg.chat.id; + bot.sendMessage(chatId, '๐ŸŽฏ **Sniper Setup**\n\nConfigure arbitrage alerts:', { + parse_mode: 'Markdown', + reply_markup: createSniperMenu() + }); +}); + +bot.onText(/^๐Ÿ“‹ Status$/, async (msg) => { + const chatId = msg.chat.id; + + // Reuse the existing /list functionality + const userDeposits = await db.getUserDeposits(chatId); + const userStatesMap = await db.getUserDepositStates(chatId); + const listeningAll = await db.getUserListenAll(chatId); + const snipers = await db.getUserSnipers(chatId); + const threshold = await db.getUserThreshold(chatId); + + let message = '๐Ÿ“‹ **Your Current Status:**\n\n'; + + if (listeningAll) { + message += `๐ŸŒ **Listening to ALL deposits**\n\n`; + } + + if (snipers.length > 0) { + message += `๐ŸŽฏ **Active Snipers:** (${threshold}% threshold)\n`; + snipers.forEach(sniper => { + const platformText = sniper.platform ? ` on ${sniper.platform}` : ' (all platforms)'; + message += `โ€ข ${sniper.currency}${platformText}\n`; + }); + message += `\n`; + } + + const idsArray = Array.from(userDeposits).sort((a, b) => a - b); + if (idsArray.length > 0) { + message += `๐Ÿ“Š **Tracking ${idsArray.length} specific deposits:**\n\n`; + idsArray.slice(0, 10).forEach(id => { + const state = userStatesMap.get(id); + const status = state ? state.status : 'tracking'; + const emoji = status === 'fulfilled' ? 'โœ…' : + status === 'pruned' ? '๐ŸŸก' : '๐Ÿ‘€'; + message += `${emoji} \`${id}\` - ${status}\n`; + }); + + if (idsArray.length > 10) { + message += `\n... and ${idsArray.length - 10} more\n`; + } + } + + if (idsArray.length === 0 && !listeningAll && snipers.length === 0) { + message += `No active tracking or snipers configured.\n\nUse the menu below to get started!`; + } + + bot.sendMessage(chatId, message, { + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); +}); + +bot.onText(/^โš™๏ธ Settings$/, (msg) => { + const chatId = msg.chat.id; + bot.sendMessage(chatId, 'โš™๏ธ **Settings**\n\nManage your bot configuration:', { + parse_mode: 'Markdown', + reply_markup: createSettingsMenu() + }); +}); + +bot.onText(/^๐Ÿ”ง System$/, async (msg) => { + const chatId = msg.chat.id; + + // Reuse the existing /status functionality + const wsConnected = resilientProvider?.isConnected || false; + const wsStatus = wsConnected ? '๐ŸŸข Connected' : '๐Ÿ”ด Disconnected'; + + let dbStatus = '๐Ÿ”ด Disconnected'; + try { + const { data, error } = await supabase.from('users').select('chat_id').limit(1); + if (!error) dbStatus = '๐ŸŸข Connected'; + } catch (error) { + console.error('Database test failed:', error); + } + + let botStatus = '๐Ÿ”ด Disconnected'; + try { + await bot.getMe(); + botStatus = '๐ŸŸข Connected'; + } catch (error) { + console.error('Bot test failed:', error); + } + + let message = `๐Ÿ”ง **System Status:**\n\n`; + message += `โ€ข **WebSocket:** ${wsStatus}\n`; + message += `โ€ข **Database:** ${dbStatus}\n`; + message += `โ€ข **Telegram:** ${botStatus}\n\n`; + + if (!wsConnected && resilientProvider) { + message += `โš ๏ธ **WebSocket reconnection attempts:** ${resilientProvider.reconnectAttempts}/${resilientProvider.maxReconnectAttempts}\n\n`; + } + + message += `All systems operational! ๐Ÿš€`; + + bot.sendMessage(chatId, message, { + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); +}); + +bot.onText(/^โ“ Help$/, (msg) => { + const chatId = msg.chat.id; + + const helpMessage = ` +๐Ÿค– **ZKP2P Tracker Help** + +**๐Ÿ”น Deposit Tracking:** +โ€ข Track specific deposit IDs for targeted notifications +โ€ข Listen to ALL deposits for complete market monitoring +โ€ข Get real-time alerts for order creation, fulfillment, and cancellation + +**๐Ÿ”น Sniper Alerts:** +โ€ข Monitor specific currencies for arbitrage opportunities +โ€ข Set custom profit thresholds (default 0.2%) +โ€ข Choose specific platforms or monitor all +โ€ข Get instant alerts when profitable rates appear + +**๐Ÿ”น Example Scenarios:** +โ€ข Track deposit #123 to see when orders are filled +โ€ข Snipe EUR on Revolut for arbitrage opportunities +โ€ข Listen to all deposits to monitor market activity +โ€ข Set 0.5% threshold to only see highly profitable opportunities + +**๐Ÿ”น Available Commands:** +โ€ข \`/menu\` - Show interactive menu (recommended) +โ€ข \`/quickmenu\` - Show persistent button menu +โ€ข \`/deposit 123\` - Track specific deposit +โ€ข \`/sniper eur revolut\` - Snipe EUR on Revolut +โ€ข \`/list\` - Show current status +โ€ข \`/status\` - Check system status +โ€ข \`/clearall\` - Reset all settings + +**๐Ÿ”น Pro Tips:** +โ€ข Use the menu system for easier navigation +โ€ข Set reasonable thresholds (0.1-1%) for sniper alerts +โ€ข Monitor multiple currencies for more opportunities +โ€ข Track specific deposits for important transactions + +Questions? The interactive menus make everything easier! ๐Ÿš€ +`.trim(); + + bot.sendMessage(chatId, helpMessage, { + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); +}); + +// Add command to remove persistent keyboard +bot.onText(/^\/hidemenu$/, (msg) => { + const chatId = msg.chat.id; + bot.sendMessage(chatId, '๐Ÿ‘‹ Persistent menu hidden.\n\nUse `/menu` or `/quickmenu` anytime to access the interactive menus.', { + parse_mode: 'Markdown', + reply_markup: { remove_keyboard: true } + }); +}); + +// Auto-show menu for new users or when they seem lost +bot.on('message', async (msg) => { + const chatId = msg.chat.id; + const text = msg.text?.toLowerCase(); + + // Skip if it's a command or callback response + if (text?.startsWith('/') || userStates.has(chatId)) return; + + // Show menu if user sends common confused messages + const confusedPhrases = ['help', 'menu', 'what', 'how', '?', 'commands', 'start', 'hi', 'hello']; + + if (confusedPhrases.some(phrase => text?.includes(phrase))) { + bot.sendMessage(chatId, '๐Ÿ‘‹ **Need help?**\n\nUse the interactive menu below to easily navigate all features:', { + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); + } +}); + +// Enhanced welcome message for group/channel usage +bot.on('new_chat_members', async (msg) => { + // Only respond in private chats to avoid spam + if (msg.chat.type !== 'private') return; + + const chatId = msg.chat.id; + const newMembers = msg.new_chat_members; + + // Check if our bot was added + const botInfo = await bot.getMe(); + const botAdded = newMembers.some(member => member.id === botInfo.id); + + if (botAdded) { + setTimeout(() => { + bot.sendMessage(chatId, ` +๐ŸŽ‰ **Welcome to ZKP2P Tracker!** + +I'm here to help you track ZKP2P deposits and find arbitrage opportunities. + +**Quick Start:** +โ€ข Use the menu below to get started +โ€ข Track specific deposits for notifications +โ€ข Set up snipers for arbitrage alerts +โ€ข Monitor the entire ZKP2P ecosystem + +Ready to begin? +`.trim(), { + parse_mode: 'Markdown', + reply_markup: createMainMenu() + }); + }, 1000); + } +}); + +console.log('โœ… Interactive menu system loaded successfully!'); + +// // Handle /start command - show help +// bot.onText(/\/start/, (msg) => { +// const chatId = msg.chat.id; +// const helpMessage = ` +// ๐Ÿค– *ZKP2P Tracker Commands:* + +// **Deposit Tracking:** +// - \`/deposit all\` - Listen to ALL deposits (every event) +// - \`/deposit stop\` - Stop listening to all deposits +// - \`/deposit 123\` - Track a specific deposit +// - \`/deposit 123,456,789\` - Track multiple deposits +// - \`/remove 123\` - Stop tracking specific deposit(s) + +// **Sniper (Arbitrage Alerts):** +// - \`/sniper eur\` - Snipe EUR on ALL platforms +// - \`/sniper eur revolut\` - Snipe EUR only on Revolut +// - \`/sniper usd zelle\` - Snipe USD only on Zelle +// - \`/sniper threshold 0.5\` - Set your alert threshold to 0.5% +// - \`/sniper list\` - Show active sniper settings +// - \`/sniper clear\` - Clear all sniper settings +// - \`/unsnipe eur\` - Stop sniping EUR (all platforms) +// - \`/unsnipe eur wise\` - Stop sniping EUR on Wise only + +// **General:** +// - \`/list\` - Show all tracking status (deposits + snipers) +// - \`/clearall\` - Stop all tracking and clear everything +// - \`/status\` - Check WebSocket connection and settings +// - \`/help\` - Show this help message + +// *Note: Each user has their own settings. Sniper alerts you when deposits offer better exchange rates than market!* +// `.trim(); + +// bot.sendMessage(chatId, helpMessage, { parse_mode: 'Markdown' }); +// }); + bot.onText(/\/help/, (msg) => { const chatId = msg.chat.id;