telegram-shop-api / src /scenes /admin /broadcast.js
Deploy Bot
Maximal_Localization_And_Logic_Refactor
e81c9a5
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;