import { Markup } from "telegraf"; import { countryData } from "./country"; import { formatPrice } from "./priceUtils"; import { BotContext } from "../types/botTypes"; import { messageManager } from "./messageManager"; import fiveSimProducts from "./5sim_products.json"; import { VirtualNumberService } from "../services/VirtualNumberService"; import { createLogger } from "../../utils/logger"; const logger = createLogger('KeyboardUtils'); export const getMainMenuKeyboard = () => { return Markup.inlineKeyboard([ [Markup.button.callback(messageManager.getMessage('btn_login'), 'login')], [Markup.button.callback(messageManager.getMessage('btn_terms'), 'terms')], [Markup.button.callback(messageManager.getMessage('btn_new_members'), 'new_members')], // [Markup.button.callback(messageManager.getMessage('btn_stats'), 'stats')], [Markup.button.callback(messageManager.getMessage('btn_change_language'), 'change_language')], ]); }; export const getLoggedInMenuKeyboard = () => { return Markup.inlineKeyboard([ [Markup.button.callback('🔍 Browse Services', 'browse_services')], [ Markup.button.callback(messageManager.getMessage('btn_profile'), 'profile'), Markup.button.callback(messageManager.getMessage('btn_change_language'), 'change_language') ], [ Markup.button.callback(messageManager.getMessage('btn_top_up'), 'top_up_balance'), Markup.button.callback(messageManager.getMessage('btn_history'), 'history') ], // [Markup.button.callback('💰 Buy with Balance', 'buy_with_balance')], [ Markup.button.callback(messageManager.getMessage('btn_back'), 'main_menu') ], ]); }; export const getServicesKeyboard = (page: number = 0, sortBy?: 'az' | 'za') => { const buttons = []; const services = Object.entries(fiveSimProducts); const rowSize = 2; // 2 buttons per row const servicesPerPage = 20; // Apply sorting for all services first let sortedServices = [...services]; if (sortBy === 'az') { sortedServices.sort((a, b) => a[1].label_en.localeCompare(b[1].label_en)); } else if (sortBy === 'za') { sortedServices.sort((a, b) => b[1].label_en.localeCompare(a[1].label_en)); } const totalPages = Math.ceil(sortedServices.length / servicesPerPage); // Get services for current page from the sorted array const startIndex = page * servicesPerPage; const endIndex = Math.min(startIndex + servicesPerPage, sortedServices.length); const pageServices = sortedServices.slice(startIndex, endIndex); // Add sort button const sortButtonText = sortBy === 'az' ? 'Sort Z-A ⬇️' : 'Sort A-Z ⬆️'; const sortButtonCallback = sortBy === 'az' ? `sort_services_za_${page}` : `sort_services_az_${page}`; buttons.push([Markup.button.callback(sortButtonText, sortButtonCallback)]); // Generate service buttons in pairs for (let i = 0; i < pageServices.length; i += rowSize) { const row = []; for (let j = 0; j < rowSize && i + j < pageServices.length; j++) { const [serviceId, serviceData] = pageServices[i + j]; row.push( Markup.button.callback( `${serviceData.icon} ${serviceData.label_en}`, `service_${serviceId}` ) ); } buttons.push(row); } // Add pagination buttons const paginationRow = []; if (page > 0) { paginationRow.push( Markup.button.callback( '⬅️ Previous', `services_page_${page - 1}${sortBy ? `_${sortBy}` : ''}` ) ); } paginationRow.push( Markup.button.callback( `📄 ${page + 1}/${totalPages}`, 'noop' ) ); if (page < totalPages - 1) { paginationRow.push( Markup.button.callback( 'Next ➡️', `services_page_${page + 1}${sortBy ? `_${sortBy}` : ''}` ) ); } if (paginationRow.length > 0) { buttons.push(paginationRow); } // Add search button buttons.push([Markup.button.callback('🔍 Search Product', 'search_product')]); // Add back button to return to main menu buttons.push([ Markup.button.callback('🔙 Back to Menu', 'logged_in_menu') ]); return Markup.inlineKeyboard(buttons); }; export const getServicesPaginationInfo = (page: number = 0) => { const services = Object.entries(fiveSimProducts); const servicesPerPage = 10; const totalPages = Math.ceil(services.length / servicesPerPage); const startIndex = page * servicesPerPage; const endIndex = Math.min(startIndex + servicesPerPage, services.length); return `Services List\n` + `📋 Showing services ${startIndex + 1}-${endIndex} of ${services.length}\n` + `📄 Page ${page + 1} of ${totalPages}`; }; export const getBackToMainMenuButton = () => { return Markup.inlineKeyboard([ [Markup.button.callback(messageManager.getMessage('btn_back'), 'main_menu')] ]); }; export const mapApiCountriesToButtons = (apiCountries: any[], service: string) => { const buttons = []; const rowSize = 2; // 2 buttons per row // Sort countries by name for better UX const sortedCountries = apiCountries.sort((a, b) => a.name.localeCompare(b.name)); for (let i = 0; i < sortedCountries.length; i += rowSize) { const row = []; for (let j = 0; j < rowSize && i + j < sortedCountries.length; j++) { const country = sortedCountries[i + j]; const countryInfo = countryData[country.id.toLowerCase()]; if (countryInfo) { row.push( Markup.button.callback( `${countryInfo.label} ${countryInfo.flag} ${countryInfo.code}`, `country_${service}_${country.id}` ) ); } } if (row.length > 0) { buttons.push(row); } } return buttons; }; export const getCountriesKeyboard = async (service: string, page: number = 0) => { const buttons = []; const countriesPerPage = 15; try { // Get countries from API const virtualNumberService = VirtualNumberService.getInstance(); const apiCountries = await virtualNumberService.getAvailableCountries(service); const mappedButtons = mapApiCountriesToButtons(apiCountries, service); const totalPages = Math.ceil(mappedButtons.length / countriesPerPage); const startIndex = page * countriesPerPage; const endIndex = Math.min(startIndex + countriesPerPage, mappedButtons.length); const pageButtons = mappedButtons.slice(startIndex, endIndex); buttons.push(...pageButtons); const paginationRow = []; if (page > 0) { paginationRow.push( Markup.button.callback( messageManager.getMessage('btn_previous'), `page_${service}_${page - 1}` ) ); } paginationRow.push( Markup.button.callback( messageManager.getMessage('btn_page_info') .replace('{current}', (page + 1).toString()) .replace('{total}', totalPages.toString()), 'noop' ) ); if (page < totalPages - 1) { paginationRow.push( Markup.button.callback( messageManager.getMessage('btn_next'), `page_${service}_${page + 1}` ) ); } if (paginationRow.length > 0) { buttons.push(paginationRow); } buttons.push([ Markup.button.callback( messageManager.getMessage('btn_main_menu'), 'main_menu' ) ]); } catch (error: any) { logger.error(`Error fetching countries: ${error.message}`); buttons.push([ Markup.button.callback( messageManager.getMessage('btn_error_loading_countries'), 'main_menu' ) ]); } return Markup.inlineKeyboard(buttons); }; export const getServicePricesKeyboard = ( prices: any, service: string, country: string, ctx: BotContext ) => { const buttons = []; const hasValidPrices = prices && Object.values(prices).some( (priceInfo: any) => priceInfo && priceInfo.count > 0 ); if (!hasValidPrices) { buttons.push([ Markup.button.callback( messageManager.getMessage('btn_no_prices'), 'noop' ), ]); } else { Object.entries(prices).forEach(([operator, priceInfo]: [string, any]) => { if (priceInfo.count > 0) { buttons.push([ Markup.button.callback( messageManager.getMessage('btn_buy_format') .replace('{price}', priceInfo.cost.toFixed(2)) .replace('{count}', priceInfo.count.toString()), `buy_${service}_${country}_${operator}` ), ]); } }); } buttons.push([ Markup.button.callback( messageManager.getMessage('btn_back_to_services'), `service_${service}` ), ]); buttons.push([ Markup.button.callback( messageManager.getMessage('btn_main_menu'), 'main_menu' ) ]); return Markup.inlineKeyboard(buttons); }; export const getHistoryKeyboard = () => { return Markup.inlineKeyboard([ [Markup.button.callback( messageManager.getMessage('btn_numbers_history'), 'numbers_history' )], [Markup.button.callback( messageManager.getMessage('btn_purchases_history'), 'purchases_history' )], [Markup.button.callback( messageManager.getMessage('btn_back_to_main'), 'main_menu' )] ]); }; export const getLanguageSelectionKeyboard = (isLoggedIn: boolean = false) => { const buttons = [ [ Markup.button.callback( messageManager.getMessage('btn_lang_english'), 'set_language_en' ), Markup.button.callback( messageManager.getMessage('btn_lang_arabic'), 'set_language_ar' ) ] ]; buttons.push([ Markup.button.callback( messageManager.getMessage('btn_back'), isLoggedIn ? 'logged_in_menu' : 'main_menu' ) ]); return Markup.inlineKeyboard(buttons); }; export const getProfileKeyboard = () => { return Markup.inlineKeyboard([ [Markup.button.callback( messageManager.getMessage('btn_change_email'), 'change_email' )], [Markup.button.callback( messageManager.getMessage('btn_change_password'), 'change_password' )], [Markup.button.callback( messageManager.getMessage('btn_account_info'), 'account_info' )], // [Markup.button.callback( // messageManager.getMessage('btn_gift_balance'), // 'gift_balance' // )], [Markup.button.callback( messageManager.getMessage('btn_back_to_logged_in'), 'logged_in_menu' )] ]); };