grammy-broadcast 2.0.2 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/Readme.md +603 -603
- package/dist/index.d.mts +0 -0
- package/dist/index.d.ts +0 -0
- package/dist/index.js +28 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +33 -33
package/Readme.md
CHANGED
|
@@ -1,603 +1,603 @@
|
|
|
1
|
-
# 📢 Grammy Broadcast Plugin
|
|
2
|
-
|
|
3
|
-
<div align="center">
|
|
4
|
-
|
|
5
|
-
**A powerful and professional plugin for broadcasting messages to all Telegram bot chats**
|
|
6
|
-
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
8
|
-
[](https://grammy.dev/)
|
|
9
|
-
[](LICENSE)
|
|
10
|
-
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## ✨ Key Features
|
|
16
|
-
|
|
17
|
-
### 🚀 Performance & Speed
|
|
18
|
-
- ⚡ **Smart Rate Limiting**: Automatic Telegram rate limit management with auto-throttling on errors
|
|
19
|
-
- 🔥 **Paid Broadcast Support**: Support for paid broadcasts at 1000 messages per second (requires Telegram Stars)
|
|
20
|
-
- 📊 **Concurrent Sending**: Send messages concurrently with queue management
|
|
21
|
-
- 🎯 **Queue Management**: Broadcast queue management with pause/resume/stop capabilities
|
|
22
|
-
|
|
23
|
-
### 💾 Flexible Storage
|
|
24
|
-
- 🔴 **Redis Storage**: For production environments and clusters
|
|
25
|
-
- 💻 **Memory Storage**: For development and testing
|
|
26
|
-
- 📁 **File Storage**: For local storage without Redis
|
|
27
|
-
|
|
28
|
-
### 🎮 Full Control
|
|
29
|
-
- ⏸️ **Pause/Resume**: Pause and resume sending at any time
|
|
30
|
-
- 🛑 **Stop**: Complete stop and removal of broadcast
|
|
31
|
-
- 📈 **Progress Tracking**: Track progress with percentage, sent count, and errors
|
|
32
|
-
- 🔍 **Preview**: Preview messages before sending
|
|
33
|
-
- 📌 **Pin Support**: Ability to pin messages after sending
|
|
34
|
-
|
|
35
|
-
### 🎨 Send Types
|
|
36
|
-
- 📋 **Copy Messages**: Copy messages while preserving content
|
|
37
|
-
- ➡️ **Forward Messages**: Forward messages
|
|
38
|
-
- ✍️ **Text Messages**: Send plain text
|
|
39
|
-
- 📦 **Multi-Message**: Support for sending multiple messages in one broadcast
|
|
40
|
-
|
|
41
|
-
### 🔐 Security & Access Control
|
|
42
|
-
- 👥 **Sudo Users**: Define authorized users
|
|
43
|
-
- 🔒 **Custom Permission**: Custom permission system
|
|
44
|
-
- 🚫 **Auto Block Management**: Automatic management of blocked users
|
|
45
|
-
|
|
46
|
-
### 🌐 Cluster Support
|
|
47
|
-
- 🔄 **Multi-Instance**: Support for multiple instances
|
|
48
|
-
- 🎯 **Main Instance**: Designate main instance for queue processing
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## 📦 Installation
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npm install grammy-broadcast
|
|
56
|
-
# or
|
|
57
|
-
yarn add grammy-broadcast
|
|
58
|
-
# or
|
|
59
|
-
pnpm add grammy-broadcast
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Requirements
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
npm install grammy ioredis
|
|
66
|
-
# or
|
|
67
|
-
yarn add grammy ioredis
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## 🚀 Quick Start
|
|
73
|
-
|
|
74
|
-
### Basic Example with Redis
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
import { Bot } from "grammy";
|
|
78
|
-
import { createBroadcaster, RedisStorage } from "grammy-broadcast";
|
|
79
|
-
import Redis from "ioredis";
|
|
80
|
-
|
|
81
|
-
const bot = new Bot("YOUR_BOT_TOKEN");
|
|
82
|
-
const redis = new Redis();
|
|
83
|
-
|
|
84
|
-
const broadcaster = createBroadcaster({
|
|
85
|
-
storage: new RedisStorage(redis),
|
|
86
|
-
isMainInstance: true, // For clusters, set true only on the main instance
|
|
87
|
-
sudoUsers: [123456789], // Authorized user IDs
|
|
88
|
-
getApi: async (botId) => bot.api, // or bot.api for single bot
|
|
89
|
-
getBroadcastChats: async (botId, offset, limit, filter) => {
|
|
90
|
-
// You can use any database here
|
|
91
|
-
// Example with MongoDB:
|
|
92
|
-
const users = await UserModel.find({ blocked: { $ne: true } })
|
|
93
|
-
.skip(offset)
|
|
94
|
-
.limit(limit)
|
|
95
|
-
.select("uid")
|
|
96
|
-
.lean();
|
|
97
|
-
return users.map(u => u.uid);
|
|
98
|
-
},
|
|
99
|
-
setRestricted: async (botId, chatId, type) => {
|
|
100
|
-
// Manage blocked users
|
|
101
|
-
if (type === 'block' || type === 'deactivated') {
|
|
102
|
-
await UserModel.updateOne({ uid: chatId }, { blocked: true });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Add middleware to bot
|
|
108
|
-
bot.use(broadcaster.getMiddleware());
|
|
109
|
-
|
|
110
|
-
bot.start();
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Example with Memory Storage (for testing)
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
import { Bot } from "grammy";
|
|
117
|
-
import { createBroadcaster, MemoryStorage } from "grammy-broadcast";
|
|
118
|
-
|
|
119
|
-
const bot = new Bot("YOUR_BOT_TOKEN");
|
|
120
|
-
|
|
121
|
-
const broadcaster = createBroadcaster({
|
|
122
|
-
storage: new MemoryStorage(),
|
|
123
|
-
isMainInstance: true,
|
|
124
|
-
sudoUsers: [123456789],
|
|
125
|
-
getApi: async () => bot.api,
|
|
126
|
-
getBroadcastChats: async (botId, offset, limit, filter) => {
|
|
127
|
-
// Test chat list
|
|
128
|
-
return [123456, 789012, 345678];
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
bot.use(broadcaster.getMiddleware());
|
|
133
|
-
bot.start();
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Example with File Storage
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
import { Bot } from "grammy";
|
|
140
|
-
import { createBroadcaster, FileStorage } from "grammy-broadcast";
|
|
141
|
-
|
|
142
|
-
const bot = new Bot("YOUR_BOT_TOKEN");
|
|
143
|
-
|
|
144
|
-
const broadcaster = createBroadcaster({
|
|
145
|
-
storage: new FileStorage("./broadcast-data"), // Storage path
|
|
146
|
-
isMainInstance: true,
|
|
147
|
-
sudoUsers: [123456789],
|
|
148
|
-
getApi: async () => bot.api,
|
|
149
|
-
getBroadcastChats: async (botId, offset, limit, filter) => {
|
|
150
|
-
// Chat retrieval logic
|
|
151
|
-
return [];
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
bot.use(broadcaster.getMiddleware());
|
|
156
|
-
bot.start();
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## 📖 Usage Guide
|
|
162
|
-
|
|
163
|
-
### Available Commands
|
|
164
|
-
|
|
165
|
-
#### 1️⃣ `/broadcast <type> [filter]`
|
|
166
|
-
Send broadcast with specified type
|
|
167
|
-
|
|
168
|
-
**Parameters:**
|
|
169
|
-
- `type`: Send type (`copy` or `forward`)
|
|
170
|
-
- `filter`: Optional filter for selecting chats (passed to `getBroadcastChats`)
|
|
171
|
-
|
|
172
|
-
**Examples:**
|
|
173
|
-
```
|
|
174
|
-
/broadcast copy users
|
|
175
|
-
/broadcast forward groups
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
#### 2️⃣ `/copy [filter]`
|
|
179
|
-
Shortcut for `/broadcast copy`
|
|
180
|
-
|
|
181
|
-
**Examples:**
|
|
182
|
-
```
|
|
183
|
-
/copy users
|
|
184
|
-
/copy
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
#### 3️⃣ `/forward [filter]`
|
|
188
|
-
Shortcut for `/broadcast forward`
|
|
189
|
-
|
|
190
|
-
**Examples:**
|
|
191
|
-
```
|
|
192
|
-
/forward groups
|
|
193
|
-
/forward
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
#### 4️⃣ `/addmsg <broadcast_id>`
|
|
197
|
-
Add message to existing broadcast
|
|
198
|
-
|
|
199
|
-
**Example:**
|
|
200
|
-
```
|
|
201
|
-
/addmsg abc123
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### Usage Steps
|
|
205
|
-
|
|
206
|
-
1. **Create Broadcast:**
|
|
207
|
-
- Reply to the message you want to broadcast
|
|
208
|
-
- Send `/copy` or `/forward` command
|
|
209
|
-
- You'll receive a broadcast ID
|
|
210
|
-
|
|
211
|
-
2. **Add More Messages (Optional):**
|
|
212
|
-
- Reply to another message
|
|
213
|
-
- Send `/addmsg <broadcast_id>` command
|
|
214
|
-
|
|
215
|
-
3. **Settings (Optional):**
|
|
216
|
-
- **Preview** button for preview
|
|
217
|
-
- **Pin** button to pin messages after sending
|
|
218
|
-
|
|
219
|
-
4. **Start Sending:**
|
|
220
|
-
- Click **Start** button
|
|
221
|
-
- Sending progress is displayed in real-time
|
|
222
|
-
|
|
223
|
-
5. **Control Sending:**
|
|
224
|
-
- **Pause**: Temporary pause
|
|
225
|
-
- **Resume**: Resume sending
|
|
226
|
-
- **Stop**: Complete stop
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## ⚙️ Advanced Configuration
|
|
231
|
-
|
|
232
|
-
### BroadcastOptions
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
interface BroadcastOptions {
|
|
236
|
-
// Storage instance (required)
|
|
237
|
-
storage: Storage;
|
|
238
|
-
|
|
239
|
-
// Function to get chats (required)
|
|
240
|
-
getBroadcastChats: (botId: number, offset: number, limit: number, filter?: string) => Promise<string[] | number[]>;
|
|
241
|
-
|
|
242
|
-
// API instance (required)
|
|
243
|
-
getApi: (botId: number) => Promise<Api> | Api;
|
|
244
|
-
|
|
245
|
-
// Designate main instance (required)
|
|
246
|
-
isMainInstance: boolean;
|
|
247
|
-
|
|
248
|
-
// List of authorized users
|
|
249
|
-
sudoUsers?: number[];
|
|
250
|
-
|
|
251
|
-
// Custom permission check function
|
|
252
|
-
hasPermission?: (ctx: Context) => boolean | Promise<boolean>;
|
|
253
|
-
|
|
254
|
-
// Manage blocked users
|
|
255
|
-
setRestricted?: (botId: number, chatId: string, type: 'block' | 'deactivated' | 'banned' | 'restricted') => Promise<void>;
|
|
256
|
-
|
|
257
|
-
// Number of chats processed per chunk (default: 100)
|
|
258
|
-
chunkSize?: number;
|
|
259
|
-
|
|
260
|
-
// Redis key prefix (default: 'brdc:')
|
|
261
|
-
keyPrefix?: string;
|
|
262
|
-
|
|
263
|
-
// Progress report frequency in milliseconds (default: 60000)
|
|
264
|
-
reportFrequency?: number;
|
|
265
|
-
|
|
266
|
-
// Queue check interval in milliseconds (default: 60000)
|
|
267
|
-
checkQueueInterval?: number;
|
|
268
|
-
|
|
269
|
-
// Progress callback function (optional)
|
|
270
|
-
progressCallback?: (id: string, sent: number, error: number, total: number) => void;
|
|
271
|
-
|
|
272
|
-
// Enable Paid Broadcast (default: false)
|
|
273
|
-
allowPaidBroadcast?: boolean;
|
|
274
|
-
|
|
275
|
-
// Customize command names
|
|
276
|
-
cmds?: {
|
|
277
|
-
broadcast?: string; // default: 'broadcast'
|
|
278
|
-
copy?: string; // default: 'copy'
|
|
279
|
-
forward?: string; // default: 'forward'
|
|
280
|
-
addmsg?: string; // default: 'addmsg'
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### Complete Example with All Settings
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
import { Bot, Context } from "grammy";
|
|
289
|
-
import { createBroadcaster, RedisStorage } from "grammy-broadcast";
|
|
290
|
-
import Redis from "ioredis";
|
|
291
|
-
|
|
292
|
-
const bot = new Bot("YOUR_BOT_TOKEN");
|
|
293
|
-
const redis = new Redis();
|
|
294
|
-
|
|
295
|
-
const broadcaster = createBroadcaster({
|
|
296
|
-
// Storage
|
|
297
|
-
storage: new RedisStorage(redis),
|
|
298
|
-
|
|
299
|
-
// Get chats with filter
|
|
300
|
-
getBroadcastChats: async (botId, offset, limit, filter) => {
|
|
301
|
-
if (filter === 'users') {
|
|
302
|
-
const users = await UserModel.find({ blocked: { $ne: true } })
|
|
303
|
-
.skip(offset)
|
|
304
|
-
.limit(limit)
|
|
305
|
-
.select("uid")
|
|
306
|
-
.lean();
|
|
307
|
-
return users.map(u => u.uid);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (filter === 'groups') {
|
|
311
|
-
const groups = await GroupModel.find({ botRemoved: { $ne: true } })
|
|
312
|
-
.skip(offset)
|
|
313
|
-
.limit(limit)
|
|
314
|
-
.select("chatId")
|
|
315
|
-
.lean();
|
|
316
|
-
return groups.map(g => g.chatId);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (filter === 'premium') {
|
|
320
|
-
const premiumUsers = await UserModel.find({
|
|
321
|
-
premium: true,
|
|
322
|
-
blocked: { $ne: true }
|
|
323
|
-
})
|
|
324
|
-
.skip(offset)
|
|
325
|
-
.limit(limit)
|
|
326
|
-
.select("uid")
|
|
327
|
-
.lean();
|
|
328
|
-
return premiumUsers.map(u => u.uid);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// No filter - all chats
|
|
332
|
-
const allChats = await ChatModel.find({})
|
|
333
|
-
.skip(offset)
|
|
334
|
-
.limit(limit)
|
|
335
|
-
.select("chatId")
|
|
336
|
-
.lean();
|
|
337
|
-
return allChats.map(c => c.chatId);
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
// Manage blocked users
|
|
341
|
-
setRestricted: async (botId, chatId, type) => {
|
|
342
|
-
if (type === 'block' || type === 'deactivated') {
|
|
343
|
-
await UserModel.updateOne(
|
|
344
|
-
{ uid: chatId },
|
|
345
|
-
{ blocked: true, blockedAt: new Date() }
|
|
346
|
-
);
|
|
347
|
-
} else if (type === 'banned' || type === 'restricted') {
|
|
348
|
-
await GroupModel.updateOne(
|
|
349
|
-
{ chatId },
|
|
350
|
-
{ botRemoved: true, removedAt: new Date() }
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
// API instance
|
|
356
|
-
getApi: async (botId) => bot.api,
|
|
357
|
-
|
|
358
|
-
// Settings
|
|
359
|
-
isMainInstance: true,
|
|
360
|
-
sudoUsers: [123456789, 987654321],
|
|
361
|
-
|
|
362
|
-
// Or use custom function for permissions
|
|
363
|
-
// hasPermission: async (ctx: Context) => {
|
|
364
|
-
// const user = await UserModel.findOne({ uid: ctx.from?.id });
|
|
365
|
-
// return user?.isAdmin === true;
|
|
366
|
-
// },
|
|
367
|
-
|
|
368
|
-
// Advanced settings
|
|
369
|
-
chunkSize: 50, // Process 50 chats per batch
|
|
370
|
-
keyPrefix: 'mybot:brdc:', // Custom prefix
|
|
371
|
-
reportFrequency: 30000, // Report every 30 seconds
|
|
372
|
-
checkQueueInterval: 10000, // Check queue every 10 seconds
|
|
373
|
-
allowPaidBroadcast: true, // Enable paid broadcast
|
|
374
|
-
|
|
375
|
-
// Progress callback
|
|
376
|
-
progressCallback: (id, sent, error, total) => {
|
|
377
|
-
console.log(`Broadcast ${id}: ${sent}/${total} sent, ${error} errors`);
|
|
378
|
-
},
|
|
379
|
-
|
|
380
|
-
// Customize commands
|
|
381
|
-
cmds: {
|
|
382
|
-
broadcast: 'bbroadcast',
|
|
383
|
-
copy: 'bcopy',
|
|
384
|
-
forward: 'bforward',
|
|
385
|
-
addmsg: 'baddmsg'
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
bot.use(broadcaster.getMiddleware());
|
|
390
|
-
bot.start();
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
---
|
|
394
|
-
|
|
395
|
-
## 🎯 Practical Examples
|
|
396
|
-
|
|
397
|
-
### Example 1: Broadcast to Specific Users
|
|
398
|
-
|
|
399
|
-
```typescript
|
|
400
|
-
// Send to premium users
|
|
401
|
-
/broadcast copy premium
|
|
402
|
-
|
|
403
|
-
// Send to active groups
|
|
404
|
-
/broadcast forward active_groups
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### Example 2: Using in Cluster
|
|
408
|
-
|
|
409
|
-
```typescript
|
|
410
|
-
// Main instance
|
|
411
|
-
const broadcaster = createBroadcaster({
|
|
412
|
-
// ...
|
|
413
|
-
isMainInstance: true, // Only in this instance
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
// Other instances
|
|
417
|
-
const broadcaster = createBroadcaster({
|
|
418
|
-
// ...
|
|
419
|
-
isMainInstance: false, // In other instances
|
|
420
|
-
});
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
### Example 3: Error Management
|
|
424
|
-
|
|
425
|
-
```typescript
|
|
426
|
-
const broadcaster = createBroadcaster({
|
|
427
|
-
// ...
|
|
428
|
-
setRestricted: async (botId, chatId, type) => {
|
|
429
|
-
console.log(`Chat ${chatId} restricted: ${type}`);
|
|
430
|
-
|
|
431
|
-
// Save to log
|
|
432
|
-
await LogModel.create({
|
|
433
|
-
chatId,
|
|
434
|
-
type,
|
|
435
|
-
timestamp: new Date()
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// Update database
|
|
439
|
-
if (type === 'block') {
|
|
440
|
-
await UserModel.updateOne(
|
|
441
|
-
{ uid: chatId },
|
|
442
|
-
{ blocked: true, blockedReason: 'user_blocked_bot' }
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
### Example 4: Using Progress Callback
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
const broadcaster = createBroadcaster({
|
|
453
|
-
// ...
|
|
454
|
-
progressCallback: async (id, sent, error, total) => {
|
|
455
|
-
// Send report to admin
|
|
456
|
-
const progress = ((sent + error) / total * 100).toFixed(2);
|
|
457
|
-
await bot.api.sendMessage(
|
|
458
|
-
ADMIN_CHAT_ID,
|
|
459
|
-
`📊 Broadcast ${id}\n` +
|
|
460
|
-
`✅ Sent: ${sent}\n` +
|
|
461
|
-
`❌ Errors: ${error}\n` +
|
|
462
|
-
`📈 Progress: ${progress}%`
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
---
|
|
469
|
-
|
|
470
|
-
## 🔧 Storage Implementations
|
|
471
|
-
|
|
472
|
-
### RedisStorage
|
|
473
|
-
|
|
474
|
-
For use in production and cluster environments:
|
|
475
|
-
|
|
476
|
-
```typescript
|
|
477
|
-
import { RedisStorage } from "grammy-broadcast";
|
|
478
|
-
import Redis from "ioredis";
|
|
479
|
-
|
|
480
|
-
const redis = new Redis({
|
|
481
|
-
host: 'localhost',
|
|
482
|
-
port: 6379,
|
|
483
|
-
// or connection string
|
|
484
|
-
// host: 'redis://localhost:6379'
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
const storage = new RedisStorage(redis);
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
### MemoryStorage
|
|
491
|
-
|
|
492
|
-
For development and testing (data stored in memory):
|
|
493
|
-
|
|
494
|
-
```typescript
|
|
495
|
-
import { MemoryStorage } from "grammy-broadcast";
|
|
496
|
-
|
|
497
|
-
const storage = new MemoryStorage();
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### FileStorage
|
|
501
|
-
|
|
502
|
-
For local storage without Redis:
|
|
503
|
-
|
|
504
|
-
```typescript
|
|
505
|
-
import { FileStorage } from "grammy-broadcast";
|
|
506
|
-
|
|
507
|
-
const storage = new FileStorage("./broadcast-storage");
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
---
|
|
511
|
-
|
|
512
|
-
## 📊 Progress Display
|
|
513
|
-
|
|
514
|
-
The plugin automatically displays broadcast progress:
|
|
515
|
-
|
|
516
|
-
```
|
|
517
|
-
⌛ Broadcasting
|
|
518
|
-
████████░░ (80%)
|
|
519
|
-
⌛ Progress: 800/1000
|
|
520
|
-
✅ Sent: 750
|
|
521
|
-
❌ Error: 50 (5%)
|
|
522
|
-
⚡ Rate: 30 msg/sec
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
Control buttons:
|
|
526
|
-
- **Progress Bar**: Show progress details
|
|
527
|
-
- **Pause**: Temporary pause
|
|
528
|
-
- **Stop**: Complete stop
|
|
529
|
-
|
|
530
|
-
---
|
|
531
|
-
|
|
532
|
-
## 🛡️ Error Management
|
|
533
|
-
|
|
534
|
-
The plugin automatically handles the following errors:
|
|
535
|
-
|
|
536
|
-
- ✅ **Blocked Users**: Blocked users
|
|
537
|
-
- ✅ **Deactivated Accounts**: Deactivated accounts
|
|
538
|
-
- ✅ **Kicked from Groups**: Kicked from groups
|
|
539
|
-
- ✅ **Restricted**: Access restrictions
|
|
540
|
-
- ✅ **Rate Limiting**: Automatic Telegram rate limit management
|
|
541
|
-
|
|
542
|
-
Errors are automatically handled in the `setRestricted` callback.
|
|
543
|
-
|
|
544
|
-
---
|
|
545
|
-
|
|
546
|
-
## 🚀 Paid Broadcast
|
|
547
|
-
|
|
548
|
-
To use Paid Broadcast feature (sending at 1000 messages/second):
|
|
549
|
-
|
|
550
|
-
```typescript
|
|
551
|
-
const broadcaster = createBroadcaster({
|
|
552
|
-
// ...
|
|
553
|
-
allowPaidBroadcast: true, // Requires Telegram Stars
|
|
554
|
-
});
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
**Note:** This feature requires Telegram Stars and is paid.
|
|
558
|
-
|
|
559
|
-
---
|
|
560
|
-
|
|
561
|
-
## 📝 Important Notes
|
|
562
|
-
|
|
563
|
-
1. **Main Instance**: In cluster environments, only one instance should have `isMainInstance: true`
|
|
564
|
-
2. **Storage**: Always use Redis for production
|
|
565
|
-
3. **Rate Limiting**: The plugin automatically manages rate limiting
|
|
566
|
-
4. **Error Handling**: Always implement `setRestricted`
|
|
567
|
-
5. **Database**: You can use any database for `getBroadcastChats`
|
|
568
|
-
|
|
569
|
-
---
|
|
570
|
-
|
|
571
|
-
## 🤝 Contributing
|
|
572
|
-
|
|
573
|
-
Contributions are always welcome! Please make sure before submitting a PR that:
|
|
574
|
-
|
|
575
|
-
- ✅ Your code follows the project standards
|
|
576
|
-
- ✅ You've added necessary tests
|
|
577
|
-
- ✅ You've updated the documentation
|
|
578
|
-
|
|
579
|
-
---
|
|
580
|
-
|
|
581
|
-
## 📄 License
|
|
582
|
-
|
|
583
|
-
This project is licensed under the [MIT](LICENSE) License.
|
|
584
|
-
|
|
585
|
-
---
|
|
586
|
-
|
|
587
|
-
## 👤 Author
|
|
588
|
-
|
|
589
|
-
**Aria** - [smaznet98@gmail.com](mailto:smaznet98@gmail.com)
|
|
590
|
-
|
|
591
|
-
---
|
|
592
|
-
|
|
593
|
-
## ⭐ Stars
|
|
594
|
-
|
|
595
|
-
If this project was useful to you, please give it a star ⭐!
|
|
596
|
-
|
|
597
|
-
---
|
|
598
|
-
|
|
599
|
-
<div align="center">
|
|
600
|
-
|
|
601
|
-
**Made with ❤️ for the Grammy community**
|
|
602
|
-
|
|
603
|
-
</div>
|
|
1
|
+
# 📢 Grammy Broadcast Plugin
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
**A powerful and professional plugin for broadcasting messages to all Telegram bot chats**
|
|
6
|
+
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://grammy.dev/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ Key Features
|
|
16
|
+
|
|
17
|
+
### 🚀 Performance & Speed
|
|
18
|
+
- ⚡ **Smart Rate Limiting**: Automatic Telegram rate limit management with auto-throttling on errors
|
|
19
|
+
- 🔥 **Paid Broadcast Support**: Support for paid broadcasts at 1000 messages per second (requires Telegram Stars)
|
|
20
|
+
- 📊 **Concurrent Sending**: Send messages concurrently with queue management
|
|
21
|
+
- 🎯 **Queue Management**: Broadcast queue management with pause/resume/stop capabilities
|
|
22
|
+
|
|
23
|
+
### 💾 Flexible Storage
|
|
24
|
+
- 🔴 **Redis Storage**: For production environments and clusters
|
|
25
|
+
- 💻 **Memory Storage**: For development and testing
|
|
26
|
+
- 📁 **File Storage**: For local storage without Redis
|
|
27
|
+
|
|
28
|
+
### 🎮 Full Control
|
|
29
|
+
- ⏸️ **Pause/Resume**: Pause and resume sending at any time
|
|
30
|
+
- 🛑 **Stop**: Complete stop and removal of broadcast
|
|
31
|
+
- 📈 **Progress Tracking**: Track progress with percentage, sent count, and errors
|
|
32
|
+
- 🔍 **Preview**: Preview messages before sending
|
|
33
|
+
- 📌 **Pin Support**: Ability to pin messages after sending
|
|
34
|
+
|
|
35
|
+
### 🎨 Send Types
|
|
36
|
+
- 📋 **Copy Messages**: Copy messages while preserving content
|
|
37
|
+
- ➡️ **Forward Messages**: Forward messages
|
|
38
|
+
- ✍️ **Text Messages**: Send plain text
|
|
39
|
+
- 📦 **Multi-Message**: Support for sending multiple messages in one broadcast
|
|
40
|
+
|
|
41
|
+
### 🔐 Security & Access Control
|
|
42
|
+
- 👥 **Sudo Users**: Define authorized users
|
|
43
|
+
- 🔒 **Custom Permission**: Custom permission system
|
|
44
|
+
- 🚫 **Auto Block Management**: Automatic management of blocked users
|
|
45
|
+
|
|
46
|
+
### 🌐 Cluster Support
|
|
47
|
+
- 🔄 **Multi-Instance**: Support for multiple instances
|
|
48
|
+
- 🎯 **Main Instance**: Designate main instance for queue processing
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 📦 Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install grammy-broadcast
|
|
56
|
+
# or
|
|
57
|
+
yarn add grammy-broadcast
|
|
58
|
+
# or
|
|
59
|
+
pnpm add grammy-broadcast
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Requirements
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install grammy ioredis
|
|
66
|
+
# or
|
|
67
|
+
yarn add grammy ioredis
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 🚀 Quick Start
|
|
73
|
+
|
|
74
|
+
### Basic Example with Redis
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { Bot } from "grammy";
|
|
78
|
+
import { createBroadcaster, RedisStorage } from "grammy-broadcast";
|
|
79
|
+
import Redis from "ioredis";
|
|
80
|
+
|
|
81
|
+
const bot = new Bot("YOUR_BOT_TOKEN");
|
|
82
|
+
const redis = new Redis();
|
|
83
|
+
|
|
84
|
+
const broadcaster = createBroadcaster({
|
|
85
|
+
storage: new RedisStorage(redis),
|
|
86
|
+
isMainInstance: true, // For clusters, set true only on the main instance
|
|
87
|
+
sudoUsers: [123456789], // Authorized user IDs
|
|
88
|
+
getApi: async (botId) => bot.api, // or bot.api for single bot
|
|
89
|
+
getBroadcastChats: async (botId, offset, limit, filter) => {
|
|
90
|
+
// You can use any database here
|
|
91
|
+
// Example with MongoDB:
|
|
92
|
+
const users = await UserModel.find({ blocked: { $ne: true } })
|
|
93
|
+
.skip(offset)
|
|
94
|
+
.limit(limit)
|
|
95
|
+
.select("uid")
|
|
96
|
+
.lean();
|
|
97
|
+
return users.map(u => u.uid);
|
|
98
|
+
},
|
|
99
|
+
setRestricted: async (botId, chatId, type) => {
|
|
100
|
+
// Manage blocked users
|
|
101
|
+
if (type === 'block' || type === 'deactivated') {
|
|
102
|
+
await UserModel.updateOne({ uid: chatId }, { blocked: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Add middleware to bot
|
|
108
|
+
bot.use(broadcaster.getMiddleware());
|
|
109
|
+
|
|
110
|
+
bot.start();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Example with Memory Storage (for testing)
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { Bot } from "grammy";
|
|
117
|
+
import { createBroadcaster, MemoryStorage } from "grammy-broadcast";
|
|
118
|
+
|
|
119
|
+
const bot = new Bot("YOUR_BOT_TOKEN");
|
|
120
|
+
|
|
121
|
+
const broadcaster = createBroadcaster({
|
|
122
|
+
storage: new MemoryStorage(),
|
|
123
|
+
isMainInstance: true,
|
|
124
|
+
sudoUsers: [123456789],
|
|
125
|
+
getApi: async () => bot.api,
|
|
126
|
+
getBroadcastChats: async (botId, offset, limit, filter) => {
|
|
127
|
+
// Test chat list
|
|
128
|
+
return [123456, 789012, 345678];
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
bot.use(broadcaster.getMiddleware());
|
|
133
|
+
bot.start();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Example with File Storage
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { Bot } from "grammy";
|
|
140
|
+
import { createBroadcaster, FileStorage } from "grammy-broadcast";
|
|
141
|
+
|
|
142
|
+
const bot = new Bot("YOUR_BOT_TOKEN");
|
|
143
|
+
|
|
144
|
+
const broadcaster = createBroadcaster({
|
|
145
|
+
storage: new FileStorage("./broadcast-data"), // Storage path
|
|
146
|
+
isMainInstance: true,
|
|
147
|
+
sudoUsers: [123456789],
|
|
148
|
+
getApi: async () => bot.api,
|
|
149
|
+
getBroadcastChats: async (botId, offset, limit, filter) => {
|
|
150
|
+
// Chat retrieval logic
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
bot.use(broadcaster.getMiddleware());
|
|
156
|
+
bot.start();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 📖 Usage Guide
|
|
162
|
+
|
|
163
|
+
### Available Commands
|
|
164
|
+
|
|
165
|
+
#### 1️⃣ `/broadcast <type> [filter]`
|
|
166
|
+
Send broadcast with specified type
|
|
167
|
+
|
|
168
|
+
**Parameters:**
|
|
169
|
+
- `type`: Send type (`copy` or `forward`)
|
|
170
|
+
- `filter`: Optional filter for selecting chats (passed to `getBroadcastChats`)
|
|
171
|
+
|
|
172
|
+
**Examples:**
|
|
173
|
+
```
|
|
174
|
+
/broadcast copy users
|
|
175
|
+
/broadcast forward groups
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### 2️⃣ `/copy [filter]`
|
|
179
|
+
Shortcut for `/broadcast copy`
|
|
180
|
+
|
|
181
|
+
**Examples:**
|
|
182
|
+
```
|
|
183
|
+
/copy users
|
|
184
|
+
/copy
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### 3️⃣ `/forward [filter]`
|
|
188
|
+
Shortcut for `/broadcast forward`
|
|
189
|
+
|
|
190
|
+
**Examples:**
|
|
191
|
+
```
|
|
192
|
+
/forward groups
|
|
193
|
+
/forward
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### 4️⃣ `/addmsg <broadcast_id>`
|
|
197
|
+
Add message to existing broadcast
|
|
198
|
+
|
|
199
|
+
**Example:**
|
|
200
|
+
```
|
|
201
|
+
/addmsg abc123
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Usage Steps
|
|
205
|
+
|
|
206
|
+
1. **Create Broadcast:**
|
|
207
|
+
- Reply to the message you want to broadcast
|
|
208
|
+
- Send `/copy` or `/forward` command
|
|
209
|
+
- You'll receive a broadcast ID
|
|
210
|
+
|
|
211
|
+
2. **Add More Messages (Optional):**
|
|
212
|
+
- Reply to another message
|
|
213
|
+
- Send `/addmsg <broadcast_id>` command
|
|
214
|
+
|
|
215
|
+
3. **Settings (Optional):**
|
|
216
|
+
- **Preview** button for preview
|
|
217
|
+
- **Pin** button to pin messages after sending
|
|
218
|
+
|
|
219
|
+
4. **Start Sending:**
|
|
220
|
+
- Click **Start** button
|
|
221
|
+
- Sending progress is displayed in real-time
|
|
222
|
+
|
|
223
|
+
5. **Control Sending:**
|
|
224
|
+
- **Pause**: Temporary pause
|
|
225
|
+
- **Resume**: Resume sending
|
|
226
|
+
- **Stop**: Complete stop
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## ⚙️ Advanced Configuration
|
|
231
|
+
|
|
232
|
+
### BroadcastOptions
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
interface BroadcastOptions {
|
|
236
|
+
// Storage instance (required)
|
|
237
|
+
storage: Storage;
|
|
238
|
+
|
|
239
|
+
// Function to get chats (required)
|
|
240
|
+
getBroadcastChats: (botId: number, offset: number, limit: number, filter?: string) => Promise<string[] | number[]>;
|
|
241
|
+
|
|
242
|
+
// API instance (required)
|
|
243
|
+
getApi: (botId: number) => Promise<Api> | Api;
|
|
244
|
+
|
|
245
|
+
// Designate main instance (required)
|
|
246
|
+
isMainInstance: boolean;
|
|
247
|
+
|
|
248
|
+
// List of authorized users
|
|
249
|
+
sudoUsers?: number[];
|
|
250
|
+
|
|
251
|
+
// Custom permission check function
|
|
252
|
+
hasPermission?: (ctx: Context) => boolean | Promise<boolean>;
|
|
253
|
+
|
|
254
|
+
// Manage blocked users
|
|
255
|
+
setRestricted?: (botId: number, chatId: string, type: 'block' | 'deactivated' | 'banned' | 'restricted') => Promise<void>;
|
|
256
|
+
|
|
257
|
+
// Number of chats processed per chunk (default: 100)
|
|
258
|
+
chunkSize?: number;
|
|
259
|
+
|
|
260
|
+
// Redis key prefix (default: 'brdc:')
|
|
261
|
+
keyPrefix?: string;
|
|
262
|
+
|
|
263
|
+
// Progress report frequency in milliseconds (default: 60000)
|
|
264
|
+
reportFrequency?: number;
|
|
265
|
+
|
|
266
|
+
// Queue check interval in milliseconds (default: 60000)
|
|
267
|
+
checkQueueInterval?: number;
|
|
268
|
+
|
|
269
|
+
// Progress callback function (optional)
|
|
270
|
+
progressCallback?: (id: string, sent: number, error: number, total: number) => void;
|
|
271
|
+
|
|
272
|
+
// Enable Paid Broadcast (default: false)
|
|
273
|
+
allowPaidBroadcast?: boolean;
|
|
274
|
+
|
|
275
|
+
// Customize command names
|
|
276
|
+
cmds?: {
|
|
277
|
+
broadcast?: string; // default: 'broadcast'
|
|
278
|
+
copy?: string; // default: 'copy'
|
|
279
|
+
forward?: string; // default: 'forward'
|
|
280
|
+
addmsg?: string; // default: 'addmsg'
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Complete Example with All Settings
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { Bot, Context } from "grammy";
|
|
289
|
+
import { createBroadcaster, RedisStorage } from "grammy-broadcast";
|
|
290
|
+
import Redis from "ioredis";
|
|
291
|
+
|
|
292
|
+
const bot = new Bot("YOUR_BOT_TOKEN");
|
|
293
|
+
const redis = new Redis();
|
|
294
|
+
|
|
295
|
+
const broadcaster = createBroadcaster({
|
|
296
|
+
// Storage
|
|
297
|
+
storage: new RedisStorage(redis),
|
|
298
|
+
|
|
299
|
+
// Get chats with filter
|
|
300
|
+
getBroadcastChats: async (botId, offset, limit, filter) => {
|
|
301
|
+
if (filter === 'users') {
|
|
302
|
+
const users = await UserModel.find({ blocked: { $ne: true } })
|
|
303
|
+
.skip(offset)
|
|
304
|
+
.limit(limit)
|
|
305
|
+
.select("uid")
|
|
306
|
+
.lean();
|
|
307
|
+
return users.map(u => u.uid);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (filter === 'groups') {
|
|
311
|
+
const groups = await GroupModel.find({ botRemoved: { $ne: true } })
|
|
312
|
+
.skip(offset)
|
|
313
|
+
.limit(limit)
|
|
314
|
+
.select("chatId")
|
|
315
|
+
.lean();
|
|
316
|
+
return groups.map(g => g.chatId);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (filter === 'premium') {
|
|
320
|
+
const premiumUsers = await UserModel.find({
|
|
321
|
+
premium: true,
|
|
322
|
+
blocked: { $ne: true }
|
|
323
|
+
})
|
|
324
|
+
.skip(offset)
|
|
325
|
+
.limit(limit)
|
|
326
|
+
.select("uid")
|
|
327
|
+
.lean();
|
|
328
|
+
return premiumUsers.map(u => u.uid);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// No filter - all chats
|
|
332
|
+
const allChats = await ChatModel.find({})
|
|
333
|
+
.skip(offset)
|
|
334
|
+
.limit(limit)
|
|
335
|
+
.select("chatId")
|
|
336
|
+
.lean();
|
|
337
|
+
return allChats.map(c => c.chatId);
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// Manage blocked users
|
|
341
|
+
setRestricted: async (botId, chatId, type) => {
|
|
342
|
+
if (type === 'block' || type === 'deactivated') {
|
|
343
|
+
await UserModel.updateOne(
|
|
344
|
+
{ uid: chatId },
|
|
345
|
+
{ blocked: true, blockedAt: new Date() }
|
|
346
|
+
);
|
|
347
|
+
} else if (type === 'banned' || type === 'restricted') {
|
|
348
|
+
await GroupModel.updateOne(
|
|
349
|
+
{ chatId },
|
|
350
|
+
{ botRemoved: true, removedAt: new Date() }
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
// API instance
|
|
356
|
+
getApi: async (botId) => bot.api,
|
|
357
|
+
|
|
358
|
+
// Settings
|
|
359
|
+
isMainInstance: true,
|
|
360
|
+
sudoUsers: [123456789, 987654321],
|
|
361
|
+
|
|
362
|
+
// Or use custom function for permissions
|
|
363
|
+
// hasPermission: async (ctx: Context) => {
|
|
364
|
+
// const user = await UserModel.findOne({ uid: ctx.from?.id });
|
|
365
|
+
// return user?.isAdmin === true;
|
|
366
|
+
// },
|
|
367
|
+
|
|
368
|
+
// Advanced settings
|
|
369
|
+
chunkSize: 50, // Process 50 chats per batch
|
|
370
|
+
keyPrefix: 'mybot:brdc:', // Custom prefix
|
|
371
|
+
reportFrequency: 30000, // Report every 30 seconds
|
|
372
|
+
checkQueueInterval: 10000, // Check queue every 10 seconds
|
|
373
|
+
allowPaidBroadcast: true, // Enable paid broadcast
|
|
374
|
+
|
|
375
|
+
// Progress callback
|
|
376
|
+
progressCallback: (id, sent, error, total) => {
|
|
377
|
+
console.log(`Broadcast ${id}: ${sent}/${total} sent, ${error} errors`);
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
// Customize commands
|
|
381
|
+
cmds: {
|
|
382
|
+
broadcast: 'bbroadcast',
|
|
383
|
+
copy: 'bcopy',
|
|
384
|
+
forward: 'bforward',
|
|
385
|
+
addmsg: 'baddmsg'
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
bot.use(broadcaster.getMiddleware());
|
|
390
|
+
bot.start();
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 🎯 Practical Examples
|
|
396
|
+
|
|
397
|
+
### Example 1: Broadcast to Specific Users
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// Send to premium users
|
|
401
|
+
/broadcast copy premium
|
|
402
|
+
|
|
403
|
+
// Send to active groups
|
|
404
|
+
/broadcast forward active_groups
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Example 2: Using in Cluster
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// Main instance
|
|
411
|
+
const broadcaster = createBroadcaster({
|
|
412
|
+
// ...
|
|
413
|
+
isMainInstance: true, // Only in this instance
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Other instances
|
|
417
|
+
const broadcaster = createBroadcaster({
|
|
418
|
+
// ...
|
|
419
|
+
isMainInstance: false, // In other instances
|
|
420
|
+
});
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Example 3: Error Management
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
const broadcaster = createBroadcaster({
|
|
427
|
+
// ...
|
|
428
|
+
setRestricted: async (botId, chatId, type) => {
|
|
429
|
+
console.log(`Chat ${chatId} restricted: ${type}`);
|
|
430
|
+
|
|
431
|
+
// Save to log
|
|
432
|
+
await LogModel.create({
|
|
433
|
+
chatId,
|
|
434
|
+
type,
|
|
435
|
+
timestamp: new Date()
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Update database
|
|
439
|
+
if (type === 'block') {
|
|
440
|
+
await UserModel.updateOne(
|
|
441
|
+
{ uid: chatId },
|
|
442
|
+
{ blocked: true, blockedReason: 'user_blocked_bot' }
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Example 4: Using Progress Callback
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
const broadcaster = createBroadcaster({
|
|
453
|
+
// ...
|
|
454
|
+
progressCallback: async (id, sent, error, total) => {
|
|
455
|
+
// Send report to admin
|
|
456
|
+
const progress = ((sent + error) / total * 100).toFixed(2);
|
|
457
|
+
await bot.api.sendMessage(
|
|
458
|
+
ADMIN_CHAT_ID,
|
|
459
|
+
`📊 Broadcast ${id}\n` +
|
|
460
|
+
`✅ Sent: ${sent}\n` +
|
|
461
|
+
`❌ Errors: ${error}\n` +
|
|
462
|
+
`📈 Progress: ${progress}%`
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## 🔧 Storage Implementations
|
|
471
|
+
|
|
472
|
+
### RedisStorage
|
|
473
|
+
|
|
474
|
+
For use in production and cluster environments:
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
import { RedisStorage } from "grammy-broadcast";
|
|
478
|
+
import Redis from "ioredis";
|
|
479
|
+
|
|
480
|
+
const redis = new Redis({
|
|
481
|
+
host: 'localhost',
|
|
482
|
+
port: 6379,
|
|
483
|
+
// or connection string
|
|
484
|
+
// host: 'redis://localhost:6379'
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
const storage = new RedisStorage(redis);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### MemoryStorage
|
|
491
|
+
|
|
492
|
+
For development and testing (data stored in memory):
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import { MemoryStorage } from "grammy-broadcast";
|
|
496
|
+
|
|
497
|
+
const storage = new MemoryStorage();
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### FileStorage
|
|
501
|
+
|
|
502
|
+
For local storage without Redis:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import { FileStorage } from "grammy-broadcast";
|
|
506
|
+
|
|
507
|
+
const storage = new FileStorage("./broadcast-storage");
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## 📊 Progress Display
|
|
513
|
+
|
|
514
|
+
The plugin automatically displays broadcast progress:
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
⌛ Broadcasting
|
|
518
|
+
████████░░ (80%)
|
|
519
|
+
⌛ Progress: 800/1000
|
|
520
|
+
✅ Sent: 750
|
|
521
|
+
❌ Error: 50 (5%)
|
|
522
|
+
⚡ Rate: 30 msg/sec
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Control buttons:
|
|
526
|
+
- **Progress Bar**: Show progress details
|
|
527
|
+
- **Pause**: Temporary pause
|
|
528
|
+
- **Stop**: Complete stop
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## 🛡️ Error Management
|
|
533
|
+
|
|
534
|
+
The plugin automatically handles the following errors:
|
|
535
|
+
|
|
536
|
+
- ✅ **Blocked Users**: Blocked users
|
|
537
|
+
- ✅ **Deactivated Accounts**: Deactivated accounts
|
|
538
|
+
- ✅ **Kicked from Groups**: Kicked from groups
|
|
539
|
+
- ✅ **Restricted**: Access restrictions
|
|
540
|
+
- ✅ **Rate Limiting**: Automatic Telegram rate limit management
|
|
541
|
+
|
|
542
|
+
Errors are automatically handled in the `setRestricted` callback.
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## 🚀 Paid Broadcast
|
|
547
|
+
|
|
548
|
+
To use Paid Broadcast feature (sending at 1000 messages/second):
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
const broadcaster = createBroadcaster({
|
|
552
|
+
// ...
|
|
553
|
+
allowPaidBroadcast: true, // Requires Telegram Stars
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**Note:** This feature requires Telegram Stars and is paid.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## 📝 Important Notes
|
|
562
|
+
|
|
563
|
+
1. **Main Instance**: In cluster environments, only one instance should have `isMainInstance: true`
|
|
564
|
+
2. **Storage**: Always use Redis for production
|
|
565
|
+
3. **Rate Limiting**: The plugin automatically manages rate limiting
|
|
566
|
+
4. **Error Handling**: Always implement `setRestricted`
|
|
567
|
+
5. **Database**: You can use any database for `getBroadcastChats`
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## 🤝 Contributing
|
|
572
|
+
|
|
573
|
+
Contributions are always welcome! Please make sure before submitting a PR that:
|
|
574
|
+
|
|
575
|
+
- ✅ Your code follows the project standards
|
|
576
|
+
- ✅ You've added necessary tests
|
|
577
|
+
- ✅ You've updated the documentation
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## 📄 License
|
|
582
|
+
|
|
583
|
+
This project is licensed under the [MIT](LICENSE) License.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## 👤 Author
|
|
588
|
+
|
|
589
|
+
**Aria** - [smaznet98@gmail.com](mailto:smaznet98@gmail.com)
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## ⭐ Stars
|
|
594
|
+
|
|
595
|
+
If this project was useful to you, please give it a star ⭐!
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
<div align="center">
|
|
600
|
+
|
|
601
|
+
**Made with ❤️ for the Grammy community**
|
|
602
|
+
|
|
603
|
+
</div>
|