Spaces:
Paused
Paused
| const { Scenes, Markup } = require('telegraf'); | |
| const User = require('../../models/User'); | |
| const userController = require('../../controllers/userController'); | |
| const broadcastScene = new Scenes.WizardScene( | |
| 'BROADCAST_SCENE', | |
| // Step 1: Ask for content | |
| async (ctx) => { | |
| // Ensure localization | |
| if (!ctx.i18n) { | |
| const locales = require('../../locales'); | |
| const User = require('../../models/User'); | |
| const user = await User.findOne({ id: ctx.from.id }); | |
| ctx.i18n = locales[user ? user.language : 'uz']; | |
| } | |
| const i18n = ctx.i18n; | |
| ctx.reply(i18n.admin.broadcast_prompt, Markup.keyboard([[i18n.btn_cancel]]).resize()); | |
| return ctx.wizard.next(); | |
| }, | |
| // Step 2: Confirm | |
| (ctx) => { | |
| const i18n = ctx.i18n; | |
| if (ctx.message && (ctx.message.text === '/cancel' || ctx.message.text === i18n.btn_cancel)) { | |
| ctx.scene.leave(); | |
| userController.start(ctx, i18n.cancel_process); | |
| return; | |
| } | |
| // Handle Back from Step 3 (Logic if needed, but Step 3 handles it mostly) | |
| if (ctx.message && ctx.message.text === i18n.btn_back_nav) { | |
| ctx.reply(i18n.admin.broadcast_prompt, Markup.keyboard([[i18n.btn_cancel]]).resize()); | |
| return ctx.wizard.back(); | |
| } | |
| ctx.wizard.state.message = ctx.message; // Save the message object | |
| // Show Preview (Forward copy) | |
| ctx.copyMessage(ctx.from.id, ctx.message.message_id); | |
| ctx.reply(i18n.admin.broadcast_confirm, Markup.inlineKeyboard([ | |
| Markup.button.callback(i18n.admin.broadcast_send, "confirm_send"), | |
| Markup.button.callback(i18n.admin.broadcast_change, "back_to_start"), | |
| Markup.button.callback(i18n.btn_cancel, "cancel_send") | |
| ])); | |
| return ctx.wizard.next(); | |
| }, | |
| // Step 3: Action Handler | |
| async (ctx) => { | |
| const i18n = ctx.i18n; | |
| if (ctx.callbackQuery) { | |
| const action = ctx.callbackQuery.data; | |
| if (action === 'cancel_send') { | |
| ctx.deleteMessage(); | |
| ctx.scene.leave(); | |
| userController.start(ctx, i18n.cancel_process); | |
| return; | |
| } else if (action === 'back_to_start') { | |
| ctx.deleteMessage(); | |
| ctx.reply(i18n.admin.broadcast_prompt, Markup.keyboard([[i18n.btn_cancel]]).resize()); | |
| return ctx.wizard.back(); | |
| } else if (action === 'confirm_send') { | |
| await ctx.deleteMessage(); | |
| ctx.reply(i18n.admin.broadcast_sending); | |
| // Start sending | |
| const users = await User.find(); | |
| let success = 0; | |
| let blocked = 0; | |
| const messageId = ctx.wizard.state.message.message_id; | |
| // Optimized Broadcast with Batching | |
| const sleep = (ms) => new Promise(r => setTimeout(r, ms)); | |
| const BATCH_SIZE = 20; | |
| for (let i = 0; i < users.length; i += BATCH_SIZE) { | |
| const batch = users.slice(i, i + BATCH_SIZE); | |
| await Promise.all(batch.map(async (user) => { | |
| try { | |
| await ctx.copyMessage(user.id, messageId); | |
| success++; | |
| } catch (err) { | |
| blocked++; | |
| } | |
| })); | |
| await sleep(1000); // 1 sec limit to respect telegram API limits | |
| } | |
| ctx.reply(`${i18n.admin.broadcast_finish}\n\n✅: ${success}\n🚫: ${blocked}`); | |
| userController.start(ctx); // Return to menu | |
| return ctx.scene.leave(); | |
| } | |
| } else { | |
| // Handle text input during buttonwait? | |
| if (ctx.message && ctx.message.text === i18n.btn_cancel) { | |
| ctx.scene.leave(); | |
| userController.start(ctx, i18n.cancel_process); | |
| return; | |
| } | |
| } | |
| } | |
| ); | |
| module.exports = broadcastScene; | |