File size: 4,144 Bytes
89ec743
 
aff1ffd
89ec743
 
 
 
e81c9a5
 
 
 
 
 
 
 
 
 
 
89ec743
 
 
 
e81c9a5
 
aff1ffd
e81c9a5
aff1ffd
89ec743
 
e81c9a5
 
 
0876dd4
 
 
89ec743
 
 
 
 
e81c9a5
 
 
 
89ec743
 
 
 
 
e81c9a5
89ec743
 
 
 
aff1ffd
e81c9a5
aff1ffd
0876dd4
 
e81c9a5
0876dd4
89ec743
 
e81c9a5
89ec743
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e81c9a5
aff1ffd
89ec743
 
 
aff1ffd
e81c9a5
aff1ffd
e81c9a5
aff1ffd
 
89ec743
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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;