@adriangalilea/utils 0.6.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.
package/README.md CHANGED
@@ -262,6 +262,7 @@ pnpm add @adriangalilea/utils gramio @gramio/storage @gramio/session
262
262
  |---|---|
263
263
  | `@adriangalilea/utils/bot/kit` | `gracefulStart(bot)` — SIGINT/SIGTERM → `bot.stop()` → exit; force-kills if shutdown hangs.<br>`adminContext({ adminId? })` — reads `TELEGRAM_ADMIN_ID` from `kev` (with optional hardcoded fallback), decorates `ctx.adminId` + `ctx.isAdmin`. |
264
264
  | `@adriangalilea/utils/bot/access-control` | Personal-bot ACL — gates non-admin/non-default users; admin gets DM with `[✅ Aprobar][❌ Denegar]` on first attempt; `/access` opens a persistent menu (revoke / reapprove / list pending). Backed by `@gramio/session` per-user + a small index. |
265
+ | `@adriangalilea/utils/bot/coalesce` | Joins client-split inbound messages back into one. When a user pastes >4096 chars, Telegram clients fragment it into separate `message` updates with no marker. Middleware detects the burst and emits one combined event. |
265
266
  | `@adriangalilea/utils/bot/llm-stream` | `ctx.startStream()` for LLM token streams. Debounced `editMessageText`, splits at 4000 chars on paragraph/line/word boundary, parses Markdown locally so malformed mid-stream markup degrades to plain text instead of failing. |
266
267
 
267
268
  Standard wiring:
@@ -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"}
@@ -0,0 +1,155 @@
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
+ // Current guess. Real Telegram clients split at natural boundaries
65
+ // (newline / paragraph / sentence) before the 4096 cap, but we don't
66
+ // yet have a solid dataset of where they actually land. Adjust as
67
+ // real-world data comes in. Single source of truth — both
68
+ // `isCoalescent` and the middleware read from here.
69
+ const DEFAULT_MIN_LEADING_LENGTH = 3750;
70
+ const DEFAULT_WINDOW_MS = 500;
71
+ const DEFAULT_ACROSS_USERS = false;
72
+ export const isCoalescent = (prev, curr, opts = {}) => {
73
+ const minLeadingLength = opts.minLeadingLength ?? DEFAULT_MIN_LEADING_LENGTH;
74
+ const windowMs = opts.windowMs ?? DEFAULT_WINDOW_MS;
75
+ const acrossUsers = opts.acrossUsers ?? DEFAULT_ACROSS_USERS;
76
+ if (prev.chatId !== curr.chatId)
77
+ return false;
78
+ if (!acrossUsers && prev.userId !== curr.userId)
79
+ return false;
80
+ if (prev.text.length < minLeadingLength)
81
+ return false;
82
+ if (curr.dateMs - prev.dateMs > windowMs)
83
+ return false;
84
+ return true;
85
+ };
86
+ export const coalesceLongMessages = (opts = {}) => {
87
+ const minLeadingLength = opts.minLeadingLength ?? DEFAULT_MIN_LEADING_LENGTH;
88
+ const windowMs = opts.windowMs ?? DEFAULT_WINDOW_MS;
89
+ const acrossUsers = opts.acrossUsers ?? DEFAULT_ACROSS_USERS;
90
+ const log = opts.log ?? false;
91
+ const dbg = (msg) => {
92
+ if (log)
93
+ console.error(`[coalesce] ${msg}`);
94
+ };
95
+ // Keyed per `<chatId>:<userId>` (or `<chatId>` when acrossUsers).
96
+ // Buffer lives only as long as fragments are still arriving — gets
97
+ // deleted on flush.
98
+ const buffers = new Map();
99
+ const keyFor = (chatId, userId) => acrossUsers ? `${chatId}` : `${chatId}:${userId}`;
100
+ return new Plugin('@adriangalilea/utils/bot/coalesce').use(async (ctx, next) => {
101
+ if (!ctx.is('message') || ctx.text === undefined)
102
+ return next();
103
+ const key = keyFor(ctx.chat.id, ctx.from.id);
104
+ const existing = buffers.get(key);
105
+ if (existing) {
106
+ // Continuation fragment — fold into the held buffer and reset
107
+ // the timer. No length check on continuations: once a buffer
108
+ // is open, anything within the window joins (the tail of a
109
+ // split is typically short). We don't call next(); this update
110
+ // is swallowed. The first fragment held by `existing.flush`
111
+ // will eventually fire next() with the combined text.
112
+ dbg(`join key=${key} len=${ctx.text.length} buffered=${existing.text.length}→${existing.text.length + ctx.text.length}`);
113
+ clearTimeout(existing.timer);
114
+ existing.text += ctx.text;
115
+ existing.timer = setTimeout(existing.flush, windowMs);
116
+ return;
117
+ }
118
+ if (ctx.text.length < minLeadingLength) {
119
+ // Short message → can never be the leading fragment of a real
120
+ // client split. Zero-latency passthrough.
121
+ dbg(`passthrough key=${key} len=${ctx.text.length} (<${minLeadingLength})`);
122
+ return next();
123
+ }
124
+ // Suspicious leading fragment: hold + start the wait window.
125
+ // Returns a Promise that only resolves after the buffer flushes
126
+ // (next() of THIS ctx is called with the combined text). This
127
+ // keeps gramio's middleware chain awaiting until we're done.
128
+ dbg(`open key=${key} len=${ctx.text.length} (≥${minLeadingLength}, wait ${windowMs}ms)`);
129
+ return new Promise((resolve, reject) => {
130
+ const buffered = {
131
+ text: ctx.text,
132
+ timer: setTimeout(() => buffered.flush(), windowMs),
133
+ flush: () => {
134
+ // Detach from the map FIRST so any fragment arriving mid-flush
135
+ // starts a fresh buffer instead of re-entering this one.
136
+ dbg(`flush key=${key} total=${buffered.text.length}`);
137
+ buffers.delete(key);
138
+ // gramio's MessageContext exposes `text` as an accessor
139
+ // with both `get` and `set` — assignment is the supported
140
+ // way to override. We don't touch `entities`: fragment-1
141
+ // entities reference fragment-1 text only, but plain-text
142
+ // consumers (the sensible use of this plugin) ignore them.
143
+ // Formatted-input consumers should disable the plugin.
144
+ ctx.text = buffered.text;
145
+ // Hand off to the rest of the chain. Resolve outer Promise
146
+ // once the chain (and any downstream awaits) settles, so
147
+ // gramio considers this middleware fully done.
148
+ Promise.resolve(next()).then(() => resolve(), reject);
149
+ },
150
+ };
151
+ buffers.set(key, buffered);
152
+ });
153
+ });
154
+ };
155
+ //# sourceMappingURL=coalesce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coalesce.js","sourceRoot":"","sources":["../../src/bot/coalesce.ts"],"names":[],"mappings":"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"}
@@ -13,5 +13,6 @@
13
13
  */
14
14
  export * from './kit.js';
15
15
  export * from './access-control.js';
16
+ export * from './coalesce.js';
16
17
  export * from './llm-stream.js';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,UAAU,CAAA;AACxB,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,UAAU,CAAA;AACxB,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA"}
package/dist/bot/index.js CHANGED
@@ -13,5 +13,6 @@
13
13
  */
14
14
  export * from './kit.js';
15
15
  export * from './access-control.js';
16
+ export * from './coalesce.js';
16
17
  export * from './llm-stream.js';
17
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,UAAU,CAAA;AACxB,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,UAAU,CAAA;AACxB,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriangalilea/utils",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "TypeScript utilities - logger, currency, formatter, GramIO bot plugins, and more",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -36,6 +36,10 @@
36
36
  "types": "./dist/bot/access-control.d.ts",
37
37
  "default": "./dist/bot/access-control.js"
38
38
  },
39
+ "./bot/coalesce": {
40
+ "types": "./dist/bot/coalesce.d.ts",
41
+ "default": "./dist/bot/coalesce.js"
42
+ },
39
43
  "./bot/kit": {
40
44
  "types": "./dist/bot/kit.d.ts",
41
45
  "default": "./dist/bot/kit.js"