telegix 1.1.1
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 -0
- package/README.md +1534 -0
- package/index.d.ts +539 -0
- package/index.js +38 -0
- package/lib/album.js +57 -0
- package/lib/api.js +1840 -0
- package/lib/chataction.js +40 -0
- package/lib/cluster.js +68 -0
- package/lib/composer.js +419 -0
- package/lib/context.js +970 -0
- package/lib/errors.js +67 -0
- package/lib/format.js +115 -0
- package/lib/i18n.js +158 -0
- package/lib/inline-debounce.js +49 -0
- package/lib/inline.js +79 -0
- package/lib/markdownv2.js +29 -0
- package/lib/markup.js +321 -0
- package/lib/payment.js +91 -0
- package/lib/polling.js +101 -0
- package/lib/prompt.js +62 -0
- package/lib/ratelimit.js +59 -0
- package/lib/rich.js +609 -0
- package/lib/scenes.js +206 -0
- package/lib/serialize.js +141 -0
- package/lib/session.js +145 -0
- package/lib/telegix.js +176 -0
- package/lib/webapp.js +65 -0
- package/lib/webhook.js +86 -0
- package/package.json +42 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - Chat Action Auto-Sender Middleware & Helper
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Middleware that automatically sends a chat action (e.g. 'typing') periodically while processing a handler
|
|
7
|
+
* @param {string} [action='typing'] - Chat action type ('typing', 'upload_photo', etc.)
|
|
8
|
+
* @param {object} [options] - { intervalMs?: number } (default 4000ms)
|
|
9
|
+
*/
|
|
10
|
+
export function chatActionMiddleware(action = 'typing', options = {}) {
|
|
11
|
+
const intervalMs = options.intervalMs || 4000;
|
|
12
|
+
|
|
13
|
+
return async (ctx, next) => {
|
|
14
|
+
const chatId = ctx.chat?.id;
|
|
15
|
+
if (!chatId) {
|
|
16
|
+
return next();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Attach convenience helper to ctx
|
|
20
|
+
ctx.sendChatAction = (act = action, extra = {}) => {
|
|
21
|
+
return ctx.telegram.sendChatAction(chatId, act, extra);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Send initial action
|
|
25
|
+
let active = true;
|
|
26
|
+
ctx.sendChatAction(action).catch(() => {});
|
|
27
|
+
|
|
28
|
+
const timer = setInterval(() => {
|
|
29
|
+
if (!active) return;
|
|
30
|
+
ctx.sendChatAction(action).catch(() => {});
|
|
31
|
+
}, intervalMs);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await next();
|
|
35
|
+
} finally {
|
|
36
|
+
active = false;
|
|
37
|
+
clearInterval(timer);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
package/lib/cluster.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - Multi-Bot Manager / Cluster Support
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Telegix } from './telegix.js';
|
|
6
|
+
|
|
7
|
+
export class TelegixManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.bots = new Map();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Add a bot instance to the manager
|
|
14
|
+
* @param {string} name - Identifier for the bot
|
|
15
|
+
* @param {string|object} tokenOrOptions - Bot token string or options object
|
|
16
|
+
* @returns {Telegix} Telegix instance
|
|
17
|
+
*/
|
|
18
|
+
add(name, tokenOrOptions) {
|
|
19
|
+
if (this.bots.has(name)) {
|
|
20
|
+
return this.bots.get(name);
|
|
21
|
+
}
|
|
22
|
+
const bot = new Telegix(tokenOrOptions);
|
|
23
|
+
this.bots.set(name, bot);
|
|
24
|
+
return bot;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get(name) {
|
|
28
|
+
return this.bots.get(name);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
remove(name) {
|
|
32
|
+
const bot = this.bots.get(name);
|
|
33
|
+
if (bot) {
|
|
34
|
+
bot.stop();
|
|
35
|
+
this.bots.delete(name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Launch all managed bots
|
|
41
|
+
* @param {object} [options] - Launch options
|
|
42
|
+
*/
|
|
43
|
+
async launchAll(options = {}) {
|
|
44
|
+
const promises = [];
|
|
45
|
+
for (const [name, bot] of this.bots.entries()) {
|
|
46
|
+
promises.push(
|
|
47
|
+
bot.launch(options).catch((err) => {
|
|
48
|
+
console.error(`Failed to launch bot "${name}":`, err.message);
|
|
49
|
+
throw err;
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return Promise.all(promises);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Stop all managed bots
|
|
58
|
+
*/
|
|
59
|
+
stopAll() {
|
|
60
|
+
for (const [name, bot] of this.bots.entries()) {
|
|
61
|
+
try {
|
|
62
|
+
bot.stop();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`Error stopping bot "${name}":`, err.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
package/lib/composer.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - Middleware Composer and Router
|
|
3
|
+
* @module telegix/composer
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compose multiple middlewares into a single middleware function
|
|
8
|
+
* @param {Array<Function>} middlewares
|
|
9
|
+
* @returns {Function}
|
|
10
|
+
*/
|
|
11
|
+
export function compose(middlewares) {
|
|
12
|
+
if (!Array.isArray(middlewares)) {
|
|
13
|
+
throw new TypeError('Middleware stack must be an array of functions');
|
|
14
|
+
}
|
|
15
|
+
for (const fn of middlewares) {
|
|
16
|
+
if (typeof fn !== 'function') {
|
|
17
|
+
throw new TypeError('Middleware must be a function');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return function (context, next) {
|
|
22
|
+
let index = -1;
|
|
23
|
+
function dispatch(i) {
|
|
24
|
+
if (i <= index) {
|
|
25
|
+
return Promise.reject(new Error('next() called multiple times in middleware'));
|
|
26
|
+
}
|
|
27
|
+
index = i;
|
|
28
|
+
let fn = middlewares[i];
|
|
29
|
+
if (i === middlewares.length) fn = next;
|
|
30
|
+
if (!fn) return Promise.resolve();
|
|
31
|
+
try {
|
|
32
|
+
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return Promise.reject(err);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return dispatch(0);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if trigger (string, RegExp, or Function) matches text
|
|
43
|
+
* @param {string|RegExp|Function} trigger
|
|
44
|
+
* @param {string} text
|
|
45
|
+
* @returns {any} Match result or boolean
|
|
46
|
+
*/
|
|
47
|
+
function matchTrigger(trigger, text) {
|
|
48
|
+
if (!text && text !== '') return null;
|
|
49
|
+
if (typeof trigger === 'string') {
|
|
50
|
+
return text === trigger ? [text] : null;
|
|
51
|
+
}
|
|
52
|
+
if (trigger instanceof RegExp) {
|
|
53
|
+
return text.match(trigger);
|
|
54
|
+
}
|
|
55
|
+
if (typeof trigger === 'function') {
|
|
56
|
+
return trigger(text);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class Composer {
|
|
62
|
+
constructor(...middlewares) {
|
|
63
|
+
this.middlewares = [];
|
|
64
|
+
this.use(...middlewares);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Register one or more middleware functions
|
|
69
|
+
* @param {...Function} middlewares
|
|
70
|
+
* @returns {this}
|
|
71
|
+
*/
|
|
72
|
+
use(...middlewares) {
|
|
73
|
+
for (const mw of middlewares) {
|
|
74
|
+
if (mw instanceof Composer) {
|
|
75
|
+
this.middlewares.push(mw.middleware());
|
|
76
|
+
} else if (typeof mw === 'function') {
|
|
77
|
+
this.middlewares.push(mw);
|
|
78
|
+
} else {
|
|
79
|
+
throw new TypeError('Composer.use() expects functions or Composer instances');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns composed middleware function
|
|
87
|
+
* @returns {Function}
|
|
88
|
+
*/
|
|
89
|
+
middleware() {
|
|
90
|
+
return (ctx, next) => {
|
|
91
|
+
const fn = compose(this.middlewares);
|
|
92
|
+
return fn(ctx, next);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Filter updates based on updateType or sub-filters (e.g. 'text', 'photo', 'callback_query')
|
|
98
|
+
* @param {string|Array<string>} updateTypes
|
|
99
|
+
* @param {...Function} middlewares
|
|
100
|
+
* @returns {this}
|
|
101
|
+
*/
|
|
102
|
+
on(updateTypes, ...middlewares) {
|
|
103
|
+
const types = Array.isArray(updateTypes) ? updateTypes : [updateTypes];
|
|
104
|
+
const handler = compose(middlewares);
|
|
105
|
+
|
|
106
|
+
return this.use((ctx, next) => {
|
|
107
|
+
for (const type of types) {
|
|
108
|
+
if (this._matchesUpdateType(ctx, type)) {
|
|
109
|
+
return handler(ctx, next);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return next();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
_matchesUpdateType(ctx, type) {
|
|
120
|
+
if (!type) return false;
|
|
121
|
+
|
|
122
|
+
// Check top-level updateType
|
|
123
|
+
if (ctx.updateType === type || ctx.update[type]) return true;
|
|
124
|
+
|
|
125
|
+
const [mainType, subType] = type.split(':');
|
|
126
|
+
|
|
127
|
+
// If matching shorthand like 'text', 'photo', 'document', 'sticker', 'video', etc.
|
|
128
|
+
if (!subType) {
|
|
129
|
+
if (ctx.message && mainType in ctx.message) return true;
|
|
130
|
+
if (mainType === 'text' && typeof ctx.message?.text === 'string') return true;
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Match sub-filter like 'message:text', 'message:photo', 'channel_post:text'
|
|
135
|
+
const targetObj = ctx.update[mainType];
|
|
136
|
+
if (targetObj && typeof targetObj === 'object') {
|
|
137
|
+
if (subType in targetObj) return true;
|
|
138
|
+
if (subType === 'text' && typeof targetObj.text === 'string') return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Handle Telegram slash commands (e.g. /start, /help, /settings)
|
|
146
|
+
* @param {string|RegExp|Array<string|RegExp>} commands
|
|
147
|
+
* @param {...Function} middlewares
|
|
148
|
+
* @returns {this}
|
|
149
|
+
*/
|
|
150
|
+
command(commands, ...middlewares) {
|
|
151
|
+
const list = Array.isArray(commands) ? commands : [commands];
|
|
152
|
+
const handler = compose(middlewares);
|
|
153
|
+
|
|
154
|
+
return this.use((ctx, next) => {
|
|
155
|
+
const text = ctx.message?.text || ctx.channelPost?.text;
|
|
156
|
+
if (!text || !text.startsWith('/')) return next();
|
|
157
|
+
|
|
158
|
+
const [rawCommandWithEntity, ...args] = text.trim().split(/\s+/);
|
|
159
|
+
const rawCommand = rawCommandWithEntity.slice(1);
|
|
160
|
+
const [cmdName, botUsername] = rawCommand.split('@');
|
|
161
|
+
|
|
162
|
+
// Check if command is addressed to another bot specifically
|
|
163
|
+
if (botUsername && ctx.botInfo?.username) {
|
|
164
|
+
if (botUsername.toLowerCase() !== ctx.botInfo.username.toLowerCase()) {
|
|
165
|
+
return next();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const trigger of list) {
|
|
170
|
+
let isMatch = false;
|
|
171
|
+
let matchResult = null;
|
|
172
|
+
|
|
173
|
+
if (typeof trigger === 'string') {
|
|
174
|
+
const cleanTrigger = trigger.startsWith('/') ? trigger.slice(1) : trigger;
|
|
175
|
+
if (cmdName.toLowerCase() === cleanTrigger.toLowerCase()) {
|
|
176
|
+
isMatch = true;
|
|
177
|
+
matchResult = [rawCommandWithEntity, args.join(' ')];
|
|
178
|
+
}
|
|
179
|
+
} else if (trigger instanceof RegExp) {
|
|
180
|
+
matchResult = cmdName.match(trigger);
|
|
181
|
+
if (matchResult) isMatch = true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (isMatch) {
|
|
185
|
+
ctx.command = cmdName;
|
|
186
|
+
ctx.payload = args.join(' ');
|
|
187
|
+
ctx.match = matchResult;
|
|
188
|
+
return handler(ctx, next);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return next();
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Match string or RegExp against message text or caption
|
|
198
|
+
* @param {string|RegExp|Function|Array<string|RegExp>} triggers
|
|
199
|
+
* @param {...Function} middlewares
|
|
200
|
+
* @returns {this}
|
|
201
|
+
*/
|
|
202
|
+
hears(triggers, ...middlewares) {
|
|
203
|
+
const list = Array.isArray(triggers) ? triggers : [triggers];
|
|
204
|
+
const handler = compose(middlewares);
|
|
205
|
+
|
|
206
|
+
return this.use((ctx, next) => {
|
|
207
|
+
const text = ctx.message?.text || ctx.message?.caption;
|
|
208
|
+
if (!text) return next();
|
|
209
|
+
|
|
210
|
+
for (const trigger of list) {
|
|
211
|
+
const match = matchTrigger(trigger, text);
|
|
212
|
+
if (match) {
|
|
213
|
+
ctx.match = match;
|
|
214
|
+
return handler(ctx, next);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return next();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Match string or RegExp against callbackQuery.data
|
|
224
|
+
* @param {string|RegExp|Function|Array<string|RegExp>} triggers
|
|
225
|
+
* @param {...Function} middlewares
|
|
226
|
+
* @returns {this}
|
|
227
|
+
*/
|
|
228
|
+
action(triggers, ...middlewares) {
|
|
229
|
+
const list = Array.isArray(triggers) ? triggers : [triggers];
|
|
230
|
+
const handler = compose(middlewares);
|
|
231
|
+
|
|
232
|
+
return this.use((ctx, next) => {
|
|
233
|
+
const data = ctx.callbackQuery?.data;
|
|
234
|
+
if (data === undefined || data === null) return next();
|
|
235
|
+
|
|
236
|
+
for (const trigger of list) {
|
|
237
|
+
const match = matchTrigger(trigger, data);
|
|
238
|
+
if (match) {
|
|
239
|
+
ctx.match = match;
|
|
240
|
+
return handler(ctx, next);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return next();
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Match string or RegExp against inlineQuery.query
|
|
250
|
+
* @param {string|RegExp|Function|Array<string|RegExp>} triggers
|
|
251
|
+
* @param {...Function} middlewares
|
|
252
|
+
* @returns {this}
|
|
253
|
+
*/
|
|
254
|
+
inlineQuery(triggers, ...middlewares) {
|
|
255
|
+
const list = Array.isArray(triggers) ? triggers : [triggers];
|
|
256
|
+
const handler = compose(middlewares);
|
|
257
|
+
|
|
258
|
+
return this.use((ctx, next) => {
|
|
259
|
+
const query = ctx.inlineQuery?.query;
|
|
260
|
+
if (query === undefined || query === null) return next();
|
|
261
|
+
|
|
262
|
+
for (const trigger of list) {
|
|
263
|
+
const match = matchTrigger(trigger, query);
|
|
264
|
+
if (match) {
|
|
265
|
+
ctx.match = match;
|
|
266
|
+
return handler(ctx, next);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return next();
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Conditional middleware execution
|
|
276
|
+
* @param {Function} predicate
|
|
277
|
+
* @param {...Function} middlewares
|
|
278
|
+
*/
|
|
279
|
+
filter(predicate, ...middlewares) {
|
|
280
|
+
const handler = compose(middlewares);
|
|
281
|
+
return this.use(async (ctx, next) => {
|
|
282
|
+
const pass = await predicate(ctx);
|
|
283
|
+
if (pass) {
|
|
284
|
+
return handler(ctx, next);
|
|
285
|
+
}
|
|
286
|
+
return next();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Drop updates that match predicate
|
|
292
|
+
* @param {Function} predicate
|
|
293
|
+
* @param {...Function} middlewares
|
|
294
|
+
*/
|
|
295
|
+
drop(predicate, ...middlewares) {
|
|
296
|
+
return this.filter(async (ctx) => !(await predicate(ctx)), ...middlewares);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Branch middleware based on predicate
|
|
301
|
+
* @param {Function} predicate
|
|
302
|
+
* @param {Function} trueMiddleware
|
|
303
|
+
* @param {Function} [falseMiddleware]
|
|
304
|
+
*/
|
|
305
|
+
branch(predicate, trueMiddleware, falseMiddleware = (ctx, next) => next()) {
|
|
306
|
+
return this.use(async (ctx, next) => {
|
|
307
|
+
const pass = await predicate(ctx);
|
|
308
|
+
return pass ? trueMiddleware(ctx, next) : falseMiddleware(ctx, next);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Filter updates by chat type ('private', 'group', 'supergroup', 'channel')
|
|
314
|
+
* @param {string|Array<string>} types
|
|
315
|
+
* @param {...Function} middlewares
|
|
316
|
+
*/
|
|
317
|
+
chatType(types, ...middlewares) {
|
|
318
|
+
const list = Array.isArray(types) ? types : [types];
|
|
319
|
+
return this.filter((ctx) => {
|
|
320
|
+
const chatType = ctx.chat?.type;
|
|
321
|
+
return chatType && list.includes(chatType);
|
|
322
|
+
}, ...middlewares);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Filter Telegram Business updates (business_connection, business_message, edited_business_message)
|
|
327
|
+
* @param {...Function} middlewares
|
|
328
|
+
*/
|
|
329
|
+
business(...middlewares) {
|
|
330
|
+
return this.filter((ctx) => {
|
|
331
|
+
return Boolean(
|
|
332
|
+
ctx.businessConnection ||
|
|
333
|
+
ctx.businessMessage ||
|
|
334
|
+
ctx.editedBusinessMessage ||
|
|
335
|
+
ctx.deletedBusinessMessages
|
|
336
|
+
);
|
|
337
|
+
}, ...middlewares);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Filter emoji reaction updates (message_reaction, message_reaction_count)
|
|
342
|
+
* @param {...Function} middlewares
|
|
343
|
+
*/
|
|
344
|
+
reaction(...middlewares) {
|
|
345
|
+
return this.filter((ctx) => {
|
|
346
|
+
return Boolean(ctx.messageReaction || ctx.messageReactionCount);
|
|
347
|
+
}, ...middlewares);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Filter chat boost updates (chat_boost, removed_chat_boost)
|
|
352
|
+
* @param {...Function} middlewares
|
|
353
|
+
*/
|
|
354
|
+
boost(...middlewares) {
|
|
355
|
+
return this.filter((ctx) => {
|
|
356
|
+
return Boolean(ctx.chatBoost || ctx.removedChatBoost);
|
|
357
|
+
}, ...middlewares);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Filter forum topic updates or messages inside forum topics
|
|
362
|
+
* @param {...Function} middlewares
|
|
363
|
+
*/
|
|
364
|
+
forumTopic(...middlewares) {
|
|
365
|
+
return this.filter((ctx) => {
|
|
366
|
+
return Boolean(
|
|
367
|
+
ctx.topicId ||
|
|
368
|
+
ctx.message?.forum_topic_created ||
|
|
369
|
+
ctx.message?.forum_topic_edited ||
|
|
370
|
+
ctx.message?.forum_topic_closed ||
|
|
371
|
+
ctx.message?.forum_topic_reopened ||
|
|
372
|
+
ctx.message?.general_forum_topic_hidden ||
|
|
373
|
+
ctx.message?.general_forum_topic_unhidden
|
|
374
|
+
);
|
|
375
|
+
}, ...middlewares);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Filter paid media updates (purchased_paid_media or messages with paid_media)
|
|
380
|
+
* @param {...Function} middlewares
|
|
381
|
+
*/
|
|
382
|
+
paidMedia(...middlewares) {
|
|
383
|
+
return this.filter((ctx) => {
|
|
384
|
+
return Boolean(
|
|
385
|
+
ctx.purchasedPaidMedia ||
|
|
386
|
+
ctx.message?.paid_media ||
|
|
387
|
+
ctx.channelPost?.paid_media
|
|
388
|
+
);
|
|
389
|
+
}, ...middlewares);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Filter messages containing specific entity types (e.g. 'url', 'mention', 'hashtag', 'email', 'custom_emoji')
|
|
394
|
+
* @param {string|Array<string>} entityTypes
|
|
395
|
+
* @param {...Function} middlewares
|
|
396
|
+
*/
|
|
397
|
+
entity(entityTypes, ...middlewares) {
|
|
398
|
+
const list = Array.isArray(entityTypes) ? entityTypes : [entityTypes];
|
|
399
|
+
const handler = compose(middlewares);
|
|
400
|
+
|
|
401
|
+
return this.use((ctx, next) => {
|
|
402
|
+
const entities = ctx.entities;
|
|
403
|
+
if (!entities || entities.length === 0) return next();
|
|
404
|
+
|
|
405
|
+
const matched = entities.filter((e) => list.includes(e.type));
|
|
406
|
+
if (matched.length > 0) {
|
|
407
|
+
ctx.matchedEntities = matched;
|
|
408
|
+
return handler(ctx, next);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return next();
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Static helper to compose middlewares without creating a Composer instance
|
|
417
|
+
*/
|
|
418
|
+
static compose = compose;
|
|
419
|
+
}
|