@adriangalilea/utils 0.5.0 → 0.7.0

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.
@@ -0,0 +1,516 @@
1
+ /**
2
+ * Access control for personal GramIO bots — a one-stop guard +
3
+ * approve/deny + revocable allow-list with an inline admin menu.
4
+ *
5
+ * stranger DMs your bot
6
+ * │
7
+ * ▼
8
+ * ┌──── plugin gate (this file) ────────────────────┐
9
+ * │ ctx.from.id ∈ admin / defaults / approved? │
10
+ * │ yes → next() │
11
+ * │ no → drop + notify admin (rate-limited) │
12
+ * └─────────────────────────────────────────────────┘
13
+ * │
14
+ * admin gets DM with [✅ Aprobar] [❌ Denegar]
15
+ * │
16
+ * admin taps
17
+ * │
18
+ * stranger's session updated · stranger gets DM
19
+ *
20
+ * **Storage layout.** Per-user state lives in its own key, written
21
+ * through `@gramio/session` so the gate read on the hot path costs
22
+ * nothing extra (session is loaded for the user already). A single
23
+ * tiny index key keeps track of who's pending / approved / denied so
24
+ * the `/access` admin menu can list without scanning the whole DB.
25
+ *
26
+ * storage:
27
+ * access:<userId> → AccessRecord (the user's session)
28
+ * ac:index → { pending, approved, denied }
29
+ *
30
+ * **Cross-user mutations.** When you tap `[✅ Aprobar]` on Pepe's
31
+ * notification, ctx is *yours* (the admin), so `ctx.access` is your
32
+ * own record. To mutate Pepe's record we hit the storage at the same
33
+ * key format we registered the session with (`access:<id>`) and
34
+ * update the index. This isn't a hack — it's our own module
35
+ * coordinating with itself.
36
+ *
37
+ * **Composes with `adminContext`** (kit.ts) — that plugin must be
38
+ * extended first or `bot.start()` throws. Inside this plugin,
39
+ * `ctx.adminId` and `ctx.isAdmin` are typed.
40
+ *
41
+ * Peer deps: `gramio`, `@gramio/storage`, `@gramio/session`.
42
+ *
43
+ * @example
44
+ * import { Bot } from 'gramio'
45
+ * import { redisStorage } from '@gramio/storage-redis'
46
+ * import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
47
+ * import { accessControl } from '@adriangalilea/utils/bot/access-control'
48
+ *
49
+ * const storage = redisStorage()
50
+ *
51
+ * const bot = new Bot(process.env.BOT_TOKEN!)
52
+ * .extend(adminContext({ adminId: 190202471 }))
53
+ * .extend(accessControl({ storage, defaults: [1158734055] }))
54
+ * .command('start', (ctx) => ctx.send(`hola, source=${ctx.access.source}`))
55
+ *
56
+ * await gracefulStart(bot)
57
+ */
58
+ import { CallbackData, InlineKeyboard, Plugin, } from 'gramio';
59
+ import { session } from '@gramio/session';
60
+ import { inMemoryStorage } from '@gramio/storage';
61
+ const SESSION_KEY_PREFIX = 'access:';
62
+ const INDEX_KEY = 'ac:index';
63
+ const FIRST_MSG_LIMIT = 200;
64
+ const DEFAULT_DENY_MSG = 'Este bot es privado. Tu solicitud se ha enviado al admin.';
65
+ const DEFAULT_THROTTLE_MS = 6 * 60 * 60 * 1000;
66
+ const userSessionKey = (userId) => `${SESSION_KEY_PREFIX}${userId}`;
67
+ // ─── callback schemas ──────────────────────────────────────────────
68
+ //
69
+ // Short `nameId`s keep callback_data under Telegram's 64-byte cap.
70
+ // `v` (optional) carries the originating list view ('pending' | 'denied'
71
+ // | 'approved'). When present, the handler refreshes that list after
72
+ // the action; absent = original notification, edits the message inline.
73
+ const acApprove = new CallbackData('acA').number('uid').string('v', { optional: true });
74
+ const acDeny = new CallbackData('acD').number('uid').string('v', { optional: true });
75
+ const acRevoke = new CallbackData('acR').number('uid');
76
+ const acView = new CallbackData('acV').string('v'); // main | approved | pending | denied
77
+ const acClose = new CallbackData('acC');
78
+ // ─── small helpers ─────────────────────────────────────────────────
79
+ const formatUser = (u, fallbackId) => {
80
+ if (!u)
81
+ return `id ${fallbackId}`;
82
+ const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || `id ${u.id}`;
83
+ const handle = u.username ? `@${u.username}` : `id ${u.id}`;
84
+ return `${name} (${handle})`;
85
+ };
86
+ const fmtAge = (ms) => {
87
+ const s = Math.floor(ms / 1000);
88
+ if (s < 60)
89
+ return `${s}s`;
90
+ const m = Math.floor(s / 60);
91
+ if (m < 60)
92
+ return `${m}min`;
93
+ const h = Math.floor(m / 60);
94
+ if (h < 24)
95
+ return `${h}h`;
96
+ return `${Math.floor(h / 24)}d`;
97
+ };
98
+ const requestNotificationText = (uid, r, repeat) => {
99
+ const parts = [
100
+ repeat ? '🔁 Acceso re-solicitado' : '🔔 Acceso solicitado',
101
+ '',
102
+ `👤 ${formatUser(r.user, uid)}`,
103
+ `🆔 ${uid}`,
104
+ `⏰ hace ${fmtAge(Date.now() - (r.requestedAt ?? Date.now()))}`,
105
+ ];
106
+ if (repeat)
107
+ parts.push(`🔁 intentos: ${(r.rejectedAttempts ?? 0) + 1}`);
108
+ if (r.firstMessage)
109
+ parts.push('', `💬 "${r.firstMessage}"`);
110
+ return parts.join('\n');
111
+ };
112
+ const requestKeyboard = (uid) => new InlineKeyboard()
113
+ .text('✅ Aprobar', acApprove.pack({ uid }))
114
+ .text('❌ Denegar', acDeny.pack({ uid }));
115
+ let warnedMemory = false;
116
+ const warnedMemoryStorage = () => {
117
+ if (!warnedMemory) {
118
+ warnedMemory = true;
119
+ console.warn('[access-control] using inMemoryStorage — approvals will not survive restarts. ' +
120
+ 'Pass `storage: redisStorage()` (or sqliteStorage / cloudflareStorage) for persistence.');
121
+ }
122
+ return inMemoryStorage();
123
+ };
124
+ // ─── index helpers ─────────────────────────────────────────────────
125
+ const emptyIndex = () => ({ pending: [], approved: [], denied: [] });
126
+ const loadIndex = async (storage) => {
127
+ const raw = (await storage.get(INDEX_KEY));
128
+ return {
129
+ pending: raw?.pending ?? [],
130
+ approved: raw?.approved ?? [],
131
+ denied: raw?.denied ?? [],
132
+ };
133
+ };
134
+ const saveIndex = (storage, idx) => storage.set(INDEX_KEY, idx);
135
+ const indexAdd = async (storage, bucket, uid) => {
136
+ const idx = await loadIndex(storage);
137
+ if (!idx[bucket].includes(uid))
138
+ idx[bucket].push(uid);
139
+ await saveIndex(storage, idx);
140
+ };
141
+ const indexMove = async (storage, uid, from, to) => {
142
+ const idx = await loadIndex(storage);
143
+ const remove = (list) => {
144
+ const i = list.indexOf(uid);
145
+ if (i >= 0)
146
+ list.splice(i, 1);
147
+ };
148
+ if (from === 'any') {
149
+ remove(idx.pending);
150
+ remove(idx.approved);
151
+ remove(idx.denied);
152
+ }
153
+ else {
154
+ remove(idx[from]);
155
+ }
156
+ if (!idx[to].includes(uid))
157
+ idx[to].push(uid);
158
+ await saveIndex(storage, idx);
159
+ };
160
+ // ─── per-user record helpers (cross-user storage access) ───────────
161
+ const loadRecord = async (storage, userId) => (await storage.get(userSessionKey(userId)));
162
+ const saveRecord = (storage, userId, rec) => storage.set(userSessionKey(userId), rec);
163
+ // ─── plugin ────────────────────────────────────────────────────────
164
+ export const accessControl = (opts = {}) => {
165
+ const storage = opts.storage ?? warnedMemoryStorage();
166
+ const defaults = new Set(opts.defaults ?? []);
167
+ const denyMessage = opts.denyMessage === false ? null : (opts.denyMessage ?? DEFAULT_DENY_MSG);
168
+ const throttleMs = opts.notifyThrottleMs ?? DEFAULT_THROTTLE_MS;
169
+ return (new Plugin('@adriangalilea/utils/bot/access-control', {
170
+ dependencies: ['@adriangalilea/utils/bot/admin'],
171
+ })
172
+ // Per-user record lives in this session (storage key `access:<senderId>`).
173
+ // The internal name `_accessSession` is plumbing — consumers read
174
+ // `ctx.access` (computed below) instead.
175
+ .extend(session({
176
+ storage,
177
+ key: '_accessSession',
178
+ getSessionKey: (ctx) => userSessionKey(ctx.senderId ?? 0),
179
+ initial: () => ({ status: 'unknown' }),
180
+ }))
181
+ // Compute the gate decision so handlers can read `ctx.access` ergonomically.
182
+ .derive((ctx) => {
183
+ // Only message + callback_query carry a senderId we can gate on.
184
+ if (!ctx.is('message') && !ctx.is('callback_query')) {
185
+ return { access: { allowed: false, reason: 'no-sender' } };
186
+ }
187
+ const senderId = ctx.from.id;
188
+ if (senderId === ctx.adminId) {
189
+ return { access: { allowed: true, source: 'admin' } };
190
+ }
191
+ if (defaults.has(senderId)) {
192
+ return { access: { allowed: true, source: 'default' } };
193
+ }
194
+ const rec = ctx._accessSession;
195
+ if (rec.status === 'approved') {
196
+ return { access: { allowed: true, source: 'store', record: rec } };
197
+ }
198
+ return {
199
+ access: { allowed: false, reason: rec.status },
200
+ };
201
+ })
202
+ // Gate. Authorized passes through; unauthorized triggers admin notify
203
+ // and silent stranger reply, then drops.
204
+ .use(async (ctx, next) => {
205
+ if (ctx.access.allowed) {
206
+ // Activity bump (only for store-approved users — admins/defaults
207
+ // don't have a session record we want to clutter).
208
+ if (ctx.access.source === 'store' && ctx.is('message')) {
209
+ ctx._accessSession.lastActivityAt = Date.now();
210
+ ctx._accessSession.messageCount = (ctx._accessSession.messageCount ?? 0) + 1;
211
+ }
212
+ return next();
213
+ }
214
+ // Acknowledge unauthorized callback queries so the spinner clears.
215
+ if (ctx.is('callback_query')) {
216
+ await ctx.answer({ text: 'Sin acceso.', show_alert: false });
217
+ return;
218
+ }
219
+ // Only message-shaped events have .text/.chat for our notification.
220
+ if (!ctx.is('message'))
221
+ return;
222
+ const userId = ctx.from.id;
223
+ const rec = ctx._accessSession;
224
+ const now = Date.now();
225
+ const isFirstRequest = rec.status === 'unknown';
226
+ if (isFirstRequest) {
227
+ rec.status = 'pending';
228
+ rec.user = {
229
+ id: userId,
230
+ firstName: ctx.from.firstName,
231
+ lastName: ctx.from.lastName,
232
+ username: ctx.from.username,
233
+ };
234
+ rec.chatId = ctx.chat.id;
235
+ rec.requestedAt = now;
236
+ rec.firstMessage = ctx.text?.slice(0, FIRST_MSG_LIMIT);
237
+ rec.messageCount = 0;
238
+ rec.rejectedAttempts = 0;
239
+ await indexAdd(storage, 'pending', userId);
240
+ }
241
+ else {
242
+ rec.rejectedAttempts = (rec.rejectedAttempts ?? 0) + 1;
243
+ }
244
+ const shouldNotify = isFirstRequest || now - (rec.lastNotifiedAt ?? 0) > throttleMs;
245
+ if (shouldNotify) {
246
+ rec.lastNotifiedAt = now;
247
+ try {
248
+ await ctx.bot.api.sendMessage({
249
+ chat_id: ctx.adminId,
250
+ text: requestNotificationText(userId, rec, !isFirstRequest),
251
+ reply_markup: requestKeyboard(userId),
252
+ });
253
+ }
254
+ catch (e) {
255
+ console.error('[access-control] failed to notify admin (have you /started the bot from your account?)', e);
256
+ }
257
+ opts.onAccessRequest?.({ user: rec.user, firstMessage: rec.firstMessage });
258
+ }
259
+ if (denyMessage && isFirstRequest) {
260
+ try {
261
+ await ctx.send(denyMessage);
262
+ }
263
+ catch {
264
+ // user blocked the bot — irrelevant
265
+ }
266
+ }
267
+ // do NOT call next — drop
268
+ })
269
+ // ─── admin actions ────────────────────────────────────────
270
+ .callbackQuery(acApprove, async (ctx) => {
271
+ if (!ctx.isAdmin)
272
+ return ctx.answer({ text: 'Solo admin.', show_alert: true });
273
+ const uid = ctx.queryData.uid;
274
+ const rec = await loadRecord(storage, uid);
275
+ if (!rec)
276
+ return ctx.answer({ text: 'No encontrado.' });
277
+ const wasDenied = rec.status === 'denied';
278
+ const wasPending = rec.status === 'pending';
279
+ rec.status = 'approved';
280
+ rec.approvedAt = Date.now();
281
+ rec.approvedBy = ctx.adminId;
282
+ rec.deniedAt = undefined;
283
+ rec.deniedBy = undefined;
284
+ await saveRecord(storage, uid, rec);
285
+ await indexMove(storage, uid, wasPending ? 'pending' : wasDenied ? 'denied' : 'any', 'approved');
286
+ if (rec.chatId !== undefined) {
287
+ try {
288
+ await ctx.bot.api.sendMessage({
289
+ chat_id: rec.chatId,
290
+ text: wasDenied
291
+ ? '✅ El admin reconsideró: ya tienes acceso.'
292
+ : '✅ Acceso concedido. Ya puedes usar el bot.',
293
+ });
294
+ }
295
+ catch {
296
+ // user blocked / chat gone
297
+ }
298
+ }
299
+ await ctx.answer({ text: '✅ Aprobado' });
300
+ if (ctx.queryData.v) {
301
+ await renderView(ctx, storage, defaults, ctx.queryData.v);
302
+ }
303
+ else {
304
+ try {
305
+ await ctx.editText(`✅ Aprobado · ${formatUser(rec.user, uid)}`);
306
+ }
307
+ catch {
308
+ // not always editable
309
+ }
310
+ }
311
+ opts.onApprove?.({ userId: uid, approvedBy: ctx.adminId });
312
+ })
313
+ .callbackQuery(acDeny, async (ctx) => {
314
+ if (!ctx.isAdmin)
315
+ return ctx.answer({ text: 'Solo admin.', show_alert: true });
316
+ const uid = ctx.queryData.uid;
317
+ const rec = await loadRecord(storage, uid);
318
+ if (!rec)
319
+ return ctx.answer({ text: 'No encontrado.' });
320
+ const wasPending = rec.status === 'pending';
321
+ rec.status = 'denied';
322
+ rec.deniedAt = Date.now();
323
+ rec.deniedBy = ctx.adminId;
324
+ await saveRecord(storage, uid, rec);
325
+ await indexMove(storage, uid, wasPending ? 'pending' : 'any', 'denied');
326
+ if (rec.chatId !== undefined) {
327
+ try {
328
+ await ctx.bot.api.sendMessage({
329
+ chat_id: rec.chatId,
330
+ text: '❌ Acceso denegado.',
331
+ });
332
+ }
333
+ catch {
334
+ // ignore
335
+ }
336
+ }
337
+ await ctx.answer({ text: '❌ Denegado' });
338
+ if (ctx.queryData.v) {
339
+ await renderView(ctx, storage, defaults, ctx.queryData.v);
340
+ }
341
+ else {
342
+ try {
343
+ await ctx.editText(`❌ Denegado · ${formatUser(rec.user, uid)}`);
344
+ }
345
+ catch {
346
+ // ignore
347
+ }
348
+ }
349
+ opts.onDeny?.({ userId: uid, deniedBy: ctx.adminId });
350
+ })
351
+ .callbackQuery(acRevoke, async (ctx) => {
352
+ if (!ctx.isAdmin)
353
+ return ctx.answer({ text: 'Solo admin.', show_alert: true });
354
+ const uid = ctx.queryData.uid;
355
+ const rec = await loadRecord(storage, uid);
356
+ if (!rec)
357
+ return ctx.answer({ text: 'No encontrado.' });
358
+ rec.status = 'denied';
359
+ rec.deniedAt = Date.now();
360
+ rec.deniedBy = ctx.adminId;
361
+ await saveRecord(storage, uid, rec);
362
+ await indexMove(storage, uid, 'approved', 'denied');
363
+ if (rec.chatId !== undefined) {
364
+ try {
365
+ await ctx.bot.api.sendMessage({
366
+ chat_id: rec.chatId,
367
+ text: '↩️ Tu acceso al bot ha sido revocado.',
368
+ });
369
+ }
370
+ catch {
371
+ // ignore
372
+ }
373
+ }
374
+ await ctx.answer({ text: '↩️ Revocado' });
375
+ await renderView(ctx, storage, defaults, 'approved');
376
+ })
377
+ .callbackQuery(acView, async (ctx) => {
378
+ if (!ctx.isAdmin)
379
+ return ctx.answer({ text: 'Solo admin.', show_alert: true });
380
+ await ctx.answer({});
381
+ await renderView(ctx, storage, defaults, ctx.queryData.v);
382
+ })
383
+ .callbackQuery(acClose, async (ctx) => {
384
+ if (!ctx.isAdmin)
385
+ return ctx.answer({ text: 'Solo admin.', show_alert: true });
386
+ await ctx.answer({});
387
+ try {
388
+ await ctx.message?.delete();
389
+ }
390
+ catch {
391
+ // ignore
392
+ }
393
+ })
394
+ .command('access', async (ctx) => {
395
+ if (!ctx.isAdmin)
396
+ return;
397
+ const v = await mainView(storage, defaults);
398
+ await ctx.send(v.text, { reply_markup: v.keyboard });
399
+ }));
400
+ };
401
+ const renderView = async (ctx, storage, defaults, view) => {
402
+ const v = view === 'approved'
403
+ ? await listView(storage, 'approved', defaults)
404
+ : view === 'pending'
405
+ ? await listView(storage, 'pending', defaults)
406
+ : view === 'denied'
407
+ ? await listView(storage, 'denied', defaults)
408
+ : await mainView(storage, defaults);
409
+ try {
410
+ await ctx.editText(v.text, { reply_markup: v.keyboard });
411
+ }
412
+ catch {
413
+ // editText only works while message is recent enough; ignore
414
+ }
415
+ };
416
+ const mainView = async (storage, defaults) => {
417
+ const idx = await loadIndex(storage);
418
+ const text = [
419
+ '🔐 Access Control',
420
+ '',
421
+ `✅ Aprobados: ${idx.approved.length}`,
422
+ `⏳ Pendientes: ${idx.pending.length}`,
423
+ `❌ Denegados: ${idx.denied.length}`,
424
+ `👑 Defaults: ${defaults.size} (hardcoded)`,
425
+ ].join('\n');
426
+ const keyboard = new InlineKeyboard()
427
+ .text(`✅ Aprobados (${idx.approved.length})`, acView.pack({ v: 'approved' }))
428
+ .text(`⏳ Pendientes (${idx.pending.length})`, acView.pack({ v: 'pending' }))
429
+ .row()
430
+ .text(`❌ Denegados (${idx.denied.length})`, acView.pack({ v: 'denied' }))
431
+ .text('🔄 Refresh', acView.pack({ v: 'main' }))
432
+ .row()
433
+ .text('✖️ Cerrar', acClose.pack({}));
434
+ return { text, keyboard };
435
+ };
436
+ const listView = async (storage, filter, defaults) => {
437
+ const idx = await loadIndex(storage);
438
+ const ids = idx[filter];
439
+ // Cap at 20 to keep callback_data + rendering sane.
440
+ const shownIds = ids.slice(0, 20);
441
+ const records = await Promise.all(shownIds.map(async (id) => ({ id, rec: await loadRecord(storage, id) })));
442
+ const headerEmoji = filter === 'approved' ? '✅' : filter === 'pending' ? '⏳' : '❌';
443
+ const headerLabel = filter === 'approved' ? 'Aprobados' : filter === 'pending' ? 'Pendientes' : 'Denegados';
444
+ if (ids.length === 0) {
445
+ const text = `${headerEmoji} ${headerLabel} (0)\n\n(vacío)`;
446
+ const keyboard = new InlineKeyboard().text('⬅️ Volver', acView.pack({ v: 'main' }));
447
+ return { text, keyboard };
448
+ }
449
+ const lines = [`${headerEmoji} ${headerLabel} (${ids.length})`, ''];
450
+ const keyboard = new InlineKeyboard();
451
+ for (let i = 0; i < records.length; i++) {
452
+ const { id, rec } = records[i];
453
+ if (!rec) {
454
+ // index referenced a missing record — show as placeholder
455
+ lines.push(`${i + 1}. id ${id} (datos perdidos)`);
456
+ continue;
457
+ }
458
+ const ageRef = rec.approvedAt ?? rec.deniedAt ?? rec.requestedAt ?? Date.now();
459
+ lines.push(`${i + 1}. ${formatUser(rec.user, id)} · hace ${fmtAge(Date.now() - ageRef)}` +
460
+ (rec.messageCount ? ` · ${rec.messageCount} msgs` : ''));
461
+ if (filter === 'pending') {
462
+ keyboard
463
+ .text(`✅ ${i + 1}`, acApprove.pack({ uid: id, v: 'pending' }))
464
+ .text(`❌ ${i + 1}`, acDeny.pack({ uid: id, v: 'pending' }))
465
+ .row();
466
+ }
467
+ else if (filter === 'approved') {
468
+ keyboard.text(`↩️ Revocar #${i + 1}`, acRevoke.pack({ uid: id })).row();
469
+ }
470
+ else if (filter === 'denied') {
471
+ keyboard
472
+ .text(`✅ Reaprobar #${i + 1}`, acApprove.pack({ uid: id, v: 'denied' }))
473
+ .row();
474
+ }
475
+ }
476
+ if (ids.length > shownIds.length) {
477
+ lines.push('', `(+${ids.length - shownIds.length} más, no mostrados)`);
478
+ }
479
+ if (filter === 'approved' && defaults.size > 0) {
480
+ lines.push('', `+ ${defaults.size} hardcoded defaults`);
481
+ }
482
+ keyboard.text('⬅️ Volver', acView.pack({ v: 'main' }));
483
+ return { text: lines.join('\n'), keyboard };
484
+ };
485
+ // ─── test helper ───────────────────────────────────────────────────
486
+ /**
487
+ * Inject a synthetic access request — for tests/demos when you can't
488
+ * easily spin up a second Telegram account. Writes a `pending` record
489
+ * to storage at the same key the plugin's session would, updates the
490
+ * index, then DMs the admin with the real
491
+ * `[✅ Aprobar][❌ Denegar]` keyboard. Tapping those buttons exercises
492
+ * the real callback handlers end-to-end.
493
+ *
494
+ * Pass the SAME `storage` instance you passed to `accessControl({ storage })`.
495
+ */
496
+ export const simulateAccessRequest = async (bot, storage, adminId, fakeUser, message) => {
497
+ const now = Date.now();
498
+ const rec = {
499
+ status: 'pending',
500
+ user: fakeUser,
501
+ chatId: fakeUser.id,
502
+ requestedAt: now,
503
+ firstMessage: message?.slice(0, FIRST_MSG_LIMIT),
504
+ messageCount: 0,
505
+ rejectedAttempts: 0,
506
+ lastNotifiedAt: now,
507
+ };
508
+ await saveRecord(storage, fakeUser.id, rec);
509
+ await indexAdd(storage, 'pending', fakeUser.id);
510
+ await bot.api.sendMessage({
511
+ chat_id: adminId,
512
+ text: requestNotificationText(fakeUser.id, rec, false),
513
+ reply_markup: requestKeyboard(fakeUser.id),
514
+ });
515
+ };
516
+ //# sourceMappingURL=access-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-control.js","sourceRoot":"","sources":["../../src/bot/access-control.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Coalesce client-split inbound messages.
3
+ *
4
+ * Telegram clients (Desktop / iOS / web) split a single message > 4096
5
+ * chars into multiple `sendMessage` calls before they ever reach the
6
+ * server. The bot receives them as **separate** `message` updates with
7
+ * no marker linking them. This middleware joins them back into one
8
+ * event so your handlers see the full text.
9
+ *
10
+ * user pastes 8000 chars → client splits in 2 → bot gets 2 updates
11
+ * │
12
+ * ▼
13
+ * coalesce middleware
14
+ * │
15
+ * hold + join
16
+ * │
17
+ * ▼
18
+ * handler sees ONE event
19
+ * with full ctx.text
20
+ *
21
+ * ## Detection rule (strict)
22
+ *
23
+ * We coalesce only when ALL hold. Otherwise fragments pass through
24
+ * as separate events — false negatives are preferred over silently
25
+ * merging unrelated messages.
26
+ *
27
+ * 1. Same chat.
28
+ * 2. Same user (override with `acrossUsers: true`).
29
+ * 3. Leading fragment length ≥ `minLeadingLength` (a current
30
+ * guess — see the type definition for the default and the
31
+ * reasoning). Short messages never start a real client split.
32
+ * 4. Each subsequent fragment within `windowMs` of the previous.
33
+ *
34
+ * ## Known caveats
35
+ *
36
+ * - `ctx.entities` is cleared on coalesced messages — per-fragment
37
+ * entity offsets would point at the wrong characters once joined.
38
+ * Plain-text consumers don't care; formatted-input consumers
39
+ * should disable this plugin.
40
+ * - In-memory buffer; doesn't survive bot restart mid-burst.
41
+ *
42
+ * Peer deps: `gramio`.
43
+ *
44
+ * @example
45
+ * import { Bot } from 'gramio'
46
+ * import { coalesceLongMessages } from '@adriangalilea/utils/bot/coalesce'
47
+ *
48
+ * const bot = new Bot(process.env.BOT_TOKEN!)
49
+ * .extend(coalesceLongMessages()) // ← before .on / .command handlers
50
+ * .on('message', (ctx) => {
51
+ * // ctx.text is the full pasted text even if Telegram split it
52
+ * return ctx.send(`got ${ctx.text?.length} chars`)
53
+ * })
54
+ *
55
+ * @example Power-user escape hatch
56
+ *
57
+ * import { isCoalescent } from '@adriangalilea/utils/bot/coalesce'
58
+ *
59
+ * if (isCoalescent(prev, curr)) {
60
+ * // do your own thing
61
+ * }
62
+ */
63
+ import { Plugin } from 'gramio';
64
+ export type CoalesceCriteria = {
65
+ /**
66
+ * Minimum length of the leading fragment to consider a possible
67
+ * client split. Below this → never coalesce, zero latency. Once
68
+ * the buffer is open, follow-up fragments of any size join.
69
+ */
70
+ minLeadingLength?: number;
71
+ /**
72
+ * Max ms between consecutive fragments to consider them part of
73
+ * one client-split burst.
74
+ */
75
+ windowMs?: number;
76
+ /**
77
+ * If true, fragments from different users (same chat) can coalesce.
78
+ * Useful for "user forwarded a multi-author conversation as one
79
+ * logical block."
80
+ */
81
+ acrossUsers?: boolean;
82
+ };
83
+ export type CoalesceLongMessagesOptions = CoalesceCriteria & {
84
+ /**
85
+ * Log each fragment + buffer transition to stderr for debugging.
86
+ * Off by default.
87
+ */
88
+ log?: boolean;
89
+ };
90
+ /**
91
+ * Pure check: are these two fragments part of the same client-split
92
+ * burst? Use this if you want to make your own decision instead of
93
+ * letting the middleware do it.
94
+ *
95
+ * The two fragments are passed as plain objects so this function is
96
+ * decoupled from gramio's context type. Adapt your context as needed.
97
+ */
98
+ export type CoalesceFragment = {
99
+ text: string;
100
+ chatId: number;
101
+ userId: number;
102
+ /** Timestamp in **milliseconds** (use `Date.now()` or `msg.date * 1000`). */
103
+ dateMs: number;
104
+ };
105
+ export declare const isCoalescent: (prev: CoalesceFragment, curr: CoalesceFragment, opts?: CoalesceCriteria) => boolean;
106
+ export declare const coalesceLongMessages: (opts?: CoalesceLongMessagesOptions) => Plugin<{}, import("gramio").DeriveDefinitions, {}>;
107
+ //# sourceMappingURL=coalesce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coalesce.d.ts","sourceRoot":"","sources":["../../src/bot/coalesce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAW/B,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,gBAAgB,GAAG;IAC3D;;;OAGG;IACH,GAAG,CAAC,EAAE,OAAO,CAAA;CACd,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,eAAO,MAAM,YAAY,GACvB,MAAM,gBAAgB,EACtB,MAAM,gBAAgB,EACtB,OAAM,gBAAqB,KAC1B,OAUF,CAAA;AAWD,eAAO,MAAM,oBAAoB,GAAI,OAAM,2BAAgC,uDA8E1E,CAAA"}