Deploy Bot
Fix SyntaxError in main.js: duplicate bot declaration
4961765
const { Telegraf, session, Scenes, Markup } = require('telegraf');
console.log("πŸš€ Bot Version 2.0: FINAL DEPLOY (Cache Bust)");
const config = require('./config');
const connectDB = require('./database/db');
const userController = require('./controllers/userController');
const User = require('./models/User');
const Order = require('./models/Order');
const https = require('https');
const dns = require('dns');
const path = require('path');
// --- API & Express Integration ---
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const apiRoutes = require('./api/routes');
const app = express();
app.use(cors());
app.use(bodyParser.json());
// Inject Bot into Request
// Inject Bot into Request (Moved below)
// const bot = require('../main'); // CIRCULAR DEPENDENCY & ERROR
// app.use((req, res, next) => { ... });
app.use('/api', apiRoutes);
app.use('/public', express.static(path.join(__dirname, '../public'))); // Serve Static Files (APK, etc.)
app.get('/', (req, res) => res.send('Bot & API are running!')); // Health Check for Hugging Face
const PORT = process.env.PORT || 7860; // Use 7860 for HF Spaces
app.listen(PORT, () => {
console.log(`🌍 API Server running on port ${PORT}`);
});
// --------------------------------
if (!config.BOT_TOKEN) {
console.error('BOT_TOKEN is missing in .env');
process.exit(1);
}
// Connect to Database
connectDB();
const telegramAgent = require('./utils/telegramAgent');
const bot = new Telegraf(config.BOT_TOKEN, {
telegram: { agent: telegramAgent }
});
// Inject Bot into Request (Fixed)
app.use((req, res, next) => {
req.bot = bot;
next();
});
module.exports = bot; // Export bot instance
// Middleware
// Middleware: Check Block Status
const checkUserStatus = async (ctx, next) => {
if (ctx.from) {
const user = await User.findOne({ id: ctx.from.id });
if (user && user.isBlocked) {
return ctx.reply("🚫 <b>Siz bloklangansiz.</b>\n\nAdmin bilan bog'laning.", { parse_mode: 'HTML' });
}
}
return next();
};
bot.use(checkUserStatus);
bot.use(session());
// Global i18n & User Middleware
bot.use(async (ctx, next) => {
try {
if (ctx.from) {
const User = require('./models/User');
const locales = require('./locales');
// Optimistic check: maybe session has language? (For speed)
// But for correctness, let's fetch DB or rely on cached user if possible.
// For now, fast fetch.
const user = await User.findOne({ id: ctx.from.id });
const lang = (user && user.language) ? user.language : 'uz';
ctx.i18n = locales[lang] || locales.uz;
ctx.dbUser = user;
}
} catch (e) {
console.error("Middleware Error:", e);
const locales = require('./locales');
ctx.i18n = locales.uz; // Fallback
}
return next();
});
// Scenes
const stage = require('./scenes/index');
bot.use(stage.middleware());
const adminController = require('./controllers/adminController');
// Admin Commands
bot.command('admin', (ctx) => {
if (!config.ADMIN_IDS.includes(ctx.from.id.toString())) return;
adminController.showDashboard(ctx);
});
// Admin Callbacks
bot.action('admin_dashboard', (ctx) => adminController.showDashboard(ctx));
// Admin Callbacks
bot.action('admin_dashboard', (ctx) => adminController.showDashboard(ctx));
bot.action('admin_api', (ctx) => adminController.showApiMenu(ctx));
// --- CONTACT HANDLER (For Phone Login) ---
bot.on('contact', async (ctx) => {
try {
const contact = ctx.message.contact;
// Check if contact belongs to the user
if (contact.user_id !== ctx.from.id) {
return ctx.reply("πŸ”’ Iltimos, o'zingizning raqamingizni yuboring (tugmani bosing).");
}
let user = await User.findOne({ id: ctx.from.id });
if (!user) {
user = new User({
id: ctx.from.id,
first_name: ctx.from.first_name,
username: ctx.from.username,
phone: contact.phone_number.replace('+', '')
});
await user.save();
} else {
user.phone = contact.phone_number.replace('+', '');
await user.save();
}
ctx.reply(`βœ… <b>Raqam tasdiqlandi!</b>\n\nEndi ilovaga kirib, <b>${user.phone}</b> raqamini kiritsangiz, men sizga kod yuboraman.`, { parse_mode: 'HTML' });
} catch (e) {
console.error("Contact Error:", e);
ctx.reply("Xatolik yuz berdi.");
}
});
bot.action('admin_api_generate', (ctx) => adminController.generateApiKey(ctx)); // NEW
bot.action('admin_users', (ctx) => adminController.showUsers(ctx));
bot.action(/admin_user_manage_(.+)/, (ctx) => adminController.manageUser(ctx, ctx.match[1]));
bot.action(/user_block_(.+)/, (ctx) => adminController.toggleBlockUser(ctx, ctx.match[1], true));
bot.action(/user_unblock_(.+)/, (ctx) => adminController.toggleBlockUser(ctx, ctx.match[1], false));
bot.action('admin_flash_sale', (ctx) => ctx.scene.enter('admin_flash_sale'));
bot.action('admin_inventory', (ctx) => adminController.startMassPriceUpdate(ctx)); // Reuse Inventory button for Mass Update temporarily or create new scene
// Mass Update Text Handler
bot.on('text', async (ctx, next) => {
// Check for Mass Update State
if (ctx.session && ctx.session.isMassUpdating) {
const handled = await adminController.handleMassUpdate(ctx);
if (handled) return;
}
// Check for Name Change State
if (ctx.session && ctx.session.isChangingName) {
const handled = await userController.handleNameChange(ctx);
if (handled) return;
}
// Check for Search State
if (ctx.session && ctx.session.isSearching) {
const handled = await userController.handleSearch(ctx);
if (handled) return;
}
// Default flow
return next();
});
bot.action('admin_delete_product', (ctx) => adminController.showDeleteProductList(ctx)); // Ensure this exists
bot.action('admin_edit_product_list', (ctx) => adminController.showEditProductList(ctx));
bot.action('admin_excel_export', (ctx) => adminController.exportOrders(ctx));
bot.action('admin_stats', (ctx) => adminController.showStats(ctx));
bot.action('admin_orders_new', (ctx) => adminController.showNewOrders(ctx));
bot.action('admin_add_product', (ctx) => {
ctx.deleteMessage(); // Remove dashboard to clear screen
ctx.scene.enter('ADD_PRODUCT_SCENE');
});
bot.action('admin_broadcast', (ctx) => {
ctx.deleteMessage();
ctx.scene.enter('BROADCAST_SCENE');
});
bot.action('admin_settings', (ctx) => {
ctx.deleteMessage();
ctx.scene.enter('SETTINGS_SCENE');
});
bot.action(/edit_prod_(.+)/, (ctx) => {
const prodId = parseInt(ctx.match[1]);
ctx.scene.enter('EDIT_PRODUCT_SCENE', { prodId: prodId });
});
bot.action(/admin_order_(.+)/, (ctx) => adminController.viewOrder(ctx, ctx.match[1]));
bot.action(/order_accept_(.+)/, (ctx) => adminController.acceptOrder(ctx, ctx.match[1]));
bot.action(/order_reject_(.+)/, (ctx) => adminController.rejectOrder(ctx, ctx.match[1]));
bot.action(/order_ship_(.+)/, (ctx) => adminController.shipOrder(ctx, ctx.match[1])); // NEW
bot.action(/order_deliver_(.+)/, (ctx) => adminController.deliverOrder(ctx, ctx.match[1])); // NEW
bot.action(/gen_invoice_(.+)/, (ctx) => adminController.showInvoice(ctx, ctx.match[1]));
bot.action(/delete_prod_(.+)/, (ctx) => adminController.deleteProduct(ctx, ctx.match[1]));
// User Actions
bot.action(/user_cancel_order_(.+)/, (ctx) => userController.cancelUserOrder(ctx, ctx.match[1]));
// User Commands
bot.start(async (ctx) => {
try {
const user = ctx.from;
let dbUser = await User.findOne({ id: user.id });
// Handle Referral
const payload = ctx.startPayload;
if (!dbUser) {
// NEW USER: Create with null language to force selection
dbUser = new User({
id: user.id,
first_name: user.first_name,
username: user.username,
language: null // Explicitly null
});
await dbUser.save();
// If referral exists, handle it
if (payload && payload !== user.id.toString()) {
userController.handleReferral(ctx, payload);
}
}
// FORCE LANGUAGE SELECTION IF NULL
if (!dbUser.language) {
return ctx.reply("πŸ‡ΊπŸ‡Ώ Iltimos, tilni tanlang:\nπŸ‡·πŸ‡Ί ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ язык:\nπŸ‡¬πŸ‡§ Please select a language:", Markup.inlineKeyboard([
[Markup.button.callback("πŸ‡ΊπŸ‡Ώ O'zbekcha", "set_lang_uz")],
[Markup.button.callback("πŸ‡·πŸ‡Ί Русский", "set_lang_ru")],
[Markup.button.callback("πŸ‡¬πŸ‡§ English", "set_lang_en")]
]));
}
// If language exists, proceed to menu
// Attach locale using the user's language
const locales = require('./locales');
ctx.i18n = locales[dbUser.language] || locales.uz;
userController.start(ctx);
} catch (err) {
console.error(err);
}
});
// Middleware: Global Checks (Blocking, Language, etc.)
bot.use(async (ctx, next) => {
try {
if (!ctx.from) return next();
const User = require('./models/User');
const locales = require('./locales');
// 1. Find or Create User (Lightweight check)
let user = await User.findOne({ id: ctx.from.id });
// If user completely doesn't exist, we usually handle it in /start,
// but for safety in other updates, we might ignore or let /start handle it.
// However, if we want strict logic, we can create them here or wait for /start.
// 2. Blocked Check
if (user && user.isBlocked) {
return ctx.reply("🚫 <b>Siz bloklangansiz.</b>\n\nAdmin bilan bog'laning.", { parse_mode: 'HTML' });
}
// 3. Language Check (The "Maximal Logic")
// If user exists but has no language, FORCE selection.
// Exception: logic shouldn't block the CALLBACK events using to SET the language.
const isLanguageSetAction = ctx.callbackQuery && ['set_lang_uz', 'set_lang_ru', 'set_lang_en'].includes(ctx.callbackQuery.data);
const isStart = ctx.message && ctx.message.text && ctx.message.text.startsWith('/start');
if (user && !user.language && !isLanguageSetAction && !isStart) {
return ctx.reply("πŸ‡ΊπŸ‡Ώ Iltimos, oldin tilni tanlang:\nπŸ‡·πŸ‡Ί ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, сначала Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ язык:\nπŸ‡¬πŸ‡§ Please select a language first:", Markup.inlineKeyboard([
[Markup.button.callback("πŸ‡ΊπŸ‡Ώ O'zbekcha", "set_lang_uz")],
[Markup.button.callback("πŸ‡·πŸ‡Ί Русский", "set_lang_ru")],
[Markup.button.callback("πŸ‡¬πŸ‡§ English", "set_lang_en")]
]));
}
// 4. Attach i18n
// If user has language, use it. If not, default to 'uz' (but /start will force selection)
const lang = (user && user.language) ? user.language : 'uz';
ctx.i18n = locales[lang] || locales.uz;
return next();
} catch (err) {
console.error("Middleware Error:", err);
return next();
}
});
// Language Selection Handlers
const setLanguage = async (ctx, lang) => {
try {
await ctx.answerCbQuery(); // Stop loading animation
// 1. Update Database
await User.updateOne({ id: ctx.from.id }, { language: lang });
// 2. Update Context Locale Immediately
const locales = require('./locales');
ctx.i18n = locales[lang];
// 3. Clean up previous valid/invalid messages
try {
await ctx.deleteMessage();
} catch (e) {
// Message might be too old or already deleted
}
// 4. Send Confirmation in NEW Language
await ctx.reply(`βœ… ${ctx.i18n.language_set}`, { parse_mode: 'HTML' });
// 5. Refresh Main Menu with NEW Language Buttons
// We pass a flag or text to start to indicate a refresh,
// but userController.start(ctx) usually handles simple menu generation.
// Let's ensure it sends the FULL welcome text again or just the menu.
// For "Maximal Logic", resetting to the main menu with a fresh "Welcome back" or just the menu is best.
userController.start(ctx);
} catch (e) {
console.error("Language Change Error:", e);
ctx.reply("⚠️ Error changing language. Please try again or /start");
}
};
bot.action('set_lang_uz', (ctx) => setLanguage(ctx, 'uz'));
bot.action('set_lang_ru', (ctx) => setLanguage(ctx, 'ru'));
bot.action('set_lang_en', (ctx) => setLanguage(ctx, 'en'));
// Settings: Change Language Action
bot.action('change_lang', (ctx) => {
ctx.reply("πŸ‡ΊπŸ‡Ώ Iltimos, tilni tanlang:\nπŸ‡·πŸ‡Ί ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ язык:\nπŸ‡¬πŸ‡§ Please select a language:", Markup.inlineKeyboard([
[Markup.button.callback("πŸ‡ΊπŸ‡Ώ O'zbekcha", "set_lang_uz")],
[Markup.button.callback("πŸ‡·πŸ‡Ί Русский", "set_lang_ru")],
[Markup.button.callback("πŸ‡¬πŸ‡§ English", "set_lang_en")]
]));
});
// User Interactions
// User Interactions
// Match Helper to get keywords from all languages
const match = (key) => {
const locales = require('./locales');
const triggers = Object.values(locales).map(l => l[key]).filter(v => v); // Filter out undefined/null
return triggers.length > 0 ? triggers : [new RegExp(`^${key}$`)]; // Fallback if empty (shouldn't happen)
};
bot.hears(match('btn_catalog'), (ctx) => userController.showCategories(ctx));
// Syntax Fix Applied
bot.hears(match('btn_cart'), (ctx) => userController.showCart(ctx));
bot.hears(match('btn_orders'), (ctx) => userController.showOrderHistory(ctx)); // Fixed Name
bot.hears(match('btn_contact'), (ctx) => userController.showContact(ctx)); // Use Controller
bot.hears(match('btn_search'), (ctx) => ctx.reply(ctx.i18n.search_prompt));
// Language Switch Handler
bot.hears(match('btn_lang'), (ctx) => {
ctx.reply("πŸ‡ΊπŸ‡Ώ Iltimos, tilni tanlang:\nπŸ‡·πŸ‡Ί ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ язык:\nπŸ‡¬πŸ‡§ Please select a language:", Markup.inlineKeyboard([
[Markup.button.callback("πŸ‡ΊπŸ‡Ώ O'zbekcha", "set_lang_uz")],
[Markup.button.callback("πŸ‡·πŸ‡Ί Русский", "set_lang_ru")],
[Markup.button.callback("πŸ‡¬πŸ‡§ English", "set_lang_en")]
]));
});
bot.hears("πŸ‘¨β€πŸ’Ό Admin Panel", (ctx) => {
if (!config.ADMIN_IDS.includes(ctx.from.id.toString())) return;
adminController.showDashboard(ctx);
});
bot.hears(match('btn_contact'), (ctx) => {
ctx.reply(ctx.i18n.contact_info, Markup.inlineKeyboard([
[Markup.button.url("πŸ“¦ Mahsulotlar bo'yicha Admin", "https://t.me/isfandiyor_3")],
[Markup.button.url("πŸ€– Bot bo'yicha Admin", "https://t.me/IBROHM_7")]
]));
});
bot.hears(match('btn_channel'), (ctx) => {
ctx.reply(ctx.i18n.btn_channel, Markup.inlineKeyboard([
[Markup.button.url("πŸ“’ Kanalga o'tish", "https://t.me/Pc_Store_Market")]
]));
});
bot.hears(match('btn_group'), (ctx) => {
ctx.reply(ctx.i18n.btn_group, Markup.inlineKeyboard([
[Markup.button.url("πŸ‘₯ Guruhga o'tish", "https://t.me/Pc_Store_Market1")]
]));
});
bot.hears(match('btn_invite'), (ctx) => userController.inviteFriends(ctx));
bot.hears(match('btn_settings'), (ctx) => userController.settings(ctx));
bot.hears(match('btn_back'), (ctx) => userController.start(ctx));
// Language Change Button Handler
bot.hears(match('btn_lang'), (ctx) => {
ctx.reply("πŸ‡ΊπŸ‡Ώ Iltimos, tilni tanlang:\nπŸ‡·πŸ‡Ί ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ язык:\nπŸ‡¬πŸ‡§ Please select a language:", Markup.inlineKeyboard([
[Markup.button.callback("πŸ‡ΊπŸ‡Ώ O'zbekcha", "set_lang_uz")],
[Markup.button.callback("πŸ‡·πŸ‡Ί Русский", "set_lang_ru")],
[Markup.button.callback("πŸ‡¬πŸ‡§ English", "set_lang_en")]
]));
});
// Handle /start with Payload
bot.start((ctx) => {
// Check for payload
const payload = ctx.startPayload; // Telegraf extracts this automatically "start 123" -> "123"
// Register User logic here (or ensure it's done) - Middlewares usually trigger first, ensuring user exists.
// Register User logic here
// Pass payload to controller
if (payload) {
if (payload.startsWith('login_')) {
// --- NEW: LOGIN HANDLER ---
const token = payload.replace('login_', '');
const LoginRequest = require('./models/LoginRequest');
// Update the login request with user info
LoginRequest.findOneAndUpdate(
{ token: token },
{
userId: user.id.toString(),
firstName: user.first_name,
username: user.username,
status: 'approved'
},
{ upsert: true } // Create if doesn't exist (though App should have created it, or we rely on token uniqueness)
).then(() => {
ctx.reply(`βœ… <b>Muvaffaqiyatli kirish!</b>\n\nIlovaga qaytishingiz mumkin.`, { parse_mode: 'HTML' });
}).catch(err => {
console.error("Login Error:", err);
ctx.reply("⚠️ Xatolik yuz berdi. Qaytadan urunib ko'ring.");
});
return; // Stop here, don't show welcome menu
}
userController.handleReferral(ctx, payload);
}
const user = ctx.from;
const welcomeText = `πŸ‘‹ <b>Assalomu alaykum, ${user.first_name}!</b>\n\nπŸ’» <b>PC Store</b> botiga xush kelibsiz!\nSifati va halolligi kafolatlangan texnikalar makoni.\n\nπŸ‘‡ <i>Quyidagi bo'limlardan birini tanlang:</i>`;
userController.start(ctx, welcomeText);
});
bot.hears("πŸ”Ž Qidiruv", (ctx) => userController.startSearch(ctx));
bot.hears("πŸ“¦ Buyurtmalarim", (ctx) => userController.showOrderHistory(ctx));
bot.hears("βš™οΈ Sozlamalar", (ctx) => userController.settings(ctx));
bot.action("set_name", (ctx) => {
ctx.deleteMessage();
userController.changeName(ctx);
});
// Handle Category Selection (Expanded for Condition Suffix)
bot.action(/cat_(.+)/, async (ctx) => {
// Expected format: cat_123 or cat_123_new or cat_123_used
const fullMatch = ctx.match[1];
let catId = fullMatch;
let condition = null;
if (fullMatch.endsWith('_new')) {
catId = fullMatch.replace('_new', '');
condition = 'new';
} else if (fullMatch.endsWith('_used')) {
catId = fullMatch.replace('_used', '');
condition = 'used';
} else if (fullMatch.endsWith('_all')) {
catId = fullMatch.replace('_all', '');
condition = 'all';
}
const Category = require('./models/Category');
const userController = require('./controllers/userController');
// Check if subcategories exist
const subCount = await Category.countDocuments({ parent: catId });
if (subCount > 0) {
// Show Subcategories (passing condition filter)
return userController.showSubCategories(ctx, catId, condition);
} else {
// Show Products (Leaf Category) -> Skip asking if condition is already set
if (condition) {
return userController.browseCategory(ctx, `cat_${catId}`, 0, 0, condition);
}
// Fallback for old buttons -> Smart Logic in showProducts
return userController.showProducts(ctx, `cat_${catId}`);
}
});
// Carousel Navigation
// Condition Filter Selection
bot.action(/cond_(.+)_(.+)/, (ctx) => {
const catId = "cat_" + ctx.match[1];
const condition = ctx.match[2];
userController.filterByCondition(ctx, catId, condition);
});
// Carousel Navigation (New)
bot.action(/br_(.+)/, (ctx) => {
// pattern: br_catId_prodIndex_mediaIndex_condition
// We split manually because condition is optional and captured groups are tricky
const text = ctx.match[1];
const parts = text.split('_');
// Safety check
if (parts.length < 3) return ctx.answerCbQuery("Error");
const catId = "cat_" + parts[0];
const prodIndex = parseInt(parts[1]);
const mediaIndex = parseInt(parts[2]);
const condition = parts[3] || 'all'; // Default to all if missing
userController.browseCategory(ctx, catId, prodIndex, mediaIndex, condition);
});
// Backward compatibility or redirect
bot.action(/browse_(.+)_(.+)/, (ctx) => {
const catId = "cat_" + ctx.match[1];
const index = parseInt(ctx.match[2]);
userController.browseCategory(ctx, catId, index, 0);
});
bot.action(/rate_ask_(.+)/, (ctx) => userController.askRating(ctx, ctx.match[1]));
bot.action(/rate_save_(.+)_(.+)/, (ctx) => userController.saveRating(ctx, ctx.match[1], ctx.match[2]));
bot.action("back_to_cats", (ctx) => {
ctx.deleteMessage();
userController.showCategories(ctx);
});
bot.action("noop", (ctx) => ctx.answerCbQuery()); // Do nothing for page counter
bot.action(/prod_(.+)/, (ctx) => {
ctx.answerCbQuery();
userController.showProduct(ctx, ctx.match[1]);
});
bot.action(/add_cart_(.+)/, (ctx) => {
userController.addToCart(ctx, ctx.match[1]);
});
bot.action("clear_cart", (ctx) => userController.clearCart(ctx));
bot.action(/cart_incr_(.+)/, (ctx) => userController.cartAction(ctx, 'incr', ctx.match[1]));
bot.action(/cart_decr_(.+)/, (ctx) => userController.cartAction(ctx, 'decr', ctx.match[1]));
bot.action(/cart_del_(.+)/, (ctx) => userController.cartAction(ctx, 'del', ctx.match[1]));
bot.action(/remove_item_(.+)/, (ctx) => userController.removeItem(ctx, parseInt(ctx.match[1])));
bot.action("checkout", (ctx) => {
ctx.answerCbQuery();
ctx.deleteMessage(); // Clear cart message
ctx.scene.enter('CHECKOUT_SCENE');
});
bot.action("my_orders_list", (ctx) => {
ctx.deleteMessage();
userController.showOrderHistory(ctx);
});
bot.action("back_to_menu", (ctx) => {
ctx.deleteMessage();
ctx.reply("Asosiy menyu:", Markup.keyboard(userController.getMenuButtons(ctx)).resize());
// Wait, getMenuButtons is not exported or defined nicely. userController.start(ctx) sends the menu.
// Let's just call userController.start(ctx);
userController.start(ctx);
});
bot.action(/my_order_(.+)/, (ctx) => userController.showOrderDetails(ctx, ctx.match[1]));
bot.action(/cancel_order_(.+)/, (ctx) => userController.cancelOrder(ctx, ctx.match[1])); // New Action
// Handle Text (Search & Settings)
// This MUST be the last text handler
bot.on('text', async (ctx, next) => {
// 1. Check Search
let isHandled = await userController.handleSearch(ctx);
if (isHandled) return;
// 2. Check Name Change
isHandled = await userController.handleNameChange(ctx);
if (isHandled) return;
return next();
});
// Launch with Retry Logic and DNS Check
// const dns = require('dns'); // Already imported at the top
// Launch Strategy (Polling vs Webhook)
// Launch Strategy (Polling vs Webhook)
const launchBot = async () => {
try {
const PORT = process.env.PORT || 7860; // Render/HF default
// Force Webhook if explicitly requested or if on Render/Railway
const USE_WEBHOOK = process.env.USE_WEBHOOK === 'true' || process.env.RENDER_EXTERNAL_URL || process.env.RAILWAY_PUBLIC_DOMAIN;
const DOMAIN = process.env.RENDER_EXTERNAL_URL || process.env.RAILWAY_PUBLIC_DOMAIN || process.env.WEBHOOK_DOMAIN;
if (USE_WEBHOOK && DOMAIN) {
// WEBHOOK MODE (Production)
console.log(`πŸš€ Starting in WEBHOOK mode on port ${PORT}`);
console.log(`πŸ”— Domain: ${DOMAIN}`);
// Delete any existing webhook first (optional, but good for restart)
// await bot.telegram.deleteWebhook();
await bot.launch({
webhook: {
domain: DOMAIN,
port: PORT
},
dropPendingUpdates: true
});
console.log(`βœ… Webhook Active: ${DOMAIN}/telegraf/<token>`);
} else {
// POLLING MODE (Local / Dev)
console.log(`πŸš€ Starting in POLLING mode`);
// Clear any stuck webhooks
await bot.telegram.deleteWebhook({ drop_pending_updates: true });
console.log('πŸ”„ Webhook tozalandi (Polling uchun)...');
await bot.launch({
dropPendingUpdates: true
});
console.log('βœ… Bot (Polling) ishga tushdi!');
}
} catch (err) {
console.error('❌ Botni ishga tushirishda xatolik:', err.message);
if (err.description && err.description.includes('Conflict')) {
console.warn("⚠️ MUAZZAM DIQQAT: Bot allaqachon boshqa joyda ishlamoqda!");
console.warn("Agar bu server bo'lsa, demak lokal (kompyuterda) botni o'chirish kerak.");
console.warn("Yechim: Bitta botni to'xtating.");
// Increase delay significantly to break the loop
console.log('⏳ Conflict: 30 soniya kuting...');
setTimeout(launchBot, 30000);
return;
}
// Retry logic for Polling stability
const jitter = Math.floor(Math.random() * 5000); // 0-5s random delay
const delay = 10000 + jitter;
console.log(`⏳ ${delay / 1000} soniyadan keyin qayta urunib ko'ramiz (Jitter: ${jitter}ms)...`);
setTimeout(launchBot, delay);
}
};
launchBot();
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));
module.exports = bot;