@adriangalilea/utils 1.10.1 → 1.11.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
@@ -444,6 +444,7 @@ Then `pnpm install`. Every `ctx.send` / `ctx.sendDocument` / `ctx.reply` / etc.
444
444
  | `@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. **Native alternative**: BotFather → Bot Settings → Access → "Restrict bot usage" — flat allow-list at Telegram. Use this plugin when you want in-bot approval flow instead of a BotFather round-trip; both can coexist. |
445
445
  | `@adriangalilea/utils/bot/allow-list` | Static allow-list by **id and/or @username** — stateless, no session/storage. `allowList({ ids?, usernames? })` is a plugin that decorates `ctx.allowed` (boolean); gate in your handlers (`if (!ctx.allowed) return`). `makeAllowList(...)` is the pure framework-free predicate. The light counterpart to `access-control` (which adds an approve/deny flow + revocable store, needing session+storage). Username caveat: a `@username` is optional and mutable, and the Bot API can't resolve username→id ahead of time — prefer `ids` when known, `usernames` is the pragmatic fallback. |
446
446
  | `@adriangalilea/utils/bot/groups` | Group-chat identity as plain functions: `chatIdOf(ctx)` (the chat id on any ctx flavour), `isGroupChat(ctx)` / `isPrivateChat(ctx)`, and `isGroupAdmin(ctx, { chatId?, userId? }?)` — the `getChatMember` creator/administrator check behind every admin-gated group setting (auto-summary toggles, "dismiss" buttons on group welcomes), chat/user defaulted from the ctx. Every reader resolves both gramio spellings — message ctxs carry `chat`, callback ctxs (inline-button taps) only `chatId`/`message.chat` — so the same gate works in handlers and taps; read chat ids via `chatIdOf`, never `ctx.chat?.id`. Takes real gramio ctxs and `MenuCtx` with no cast; a miswired ctx panics, an API rejection fails closed (`false`). Composition over policy: bot-owner override stays at your call site (`ctx.isAdmin \|\| await isGroupAdmin(ctx)`). |
447
+ | `@adriangalilea/utils/bot/user` | `userLabel(u)` — the conditional "[name] [@username] [id]" line every bot re-rolls for admin DMs and logs: `Ada Lovelace (@ada · 42)` / `Ada (42)` / `@ada (42)` / `id 42`, missing pieces drop instead of padding. Reads both gramio spellings (`firstName` / `first_name`); plain text by design. |
447
448
  | `@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. |
448
449
  | `@adriangalilea/utils/bot/llm` | The full LLM-chatbot pipeline in one module. **Input:** `streamChat(response)` parses OpenAI-compatible SSE (OpenAI, vllm, mlx-lm, llama.cpp, Together, Groq, …) into a typed `AsyncGenerator<{type: 'content' \| 'reasoning', text}>`. **Output:** `ctx.startStream()` (low-level: debounced markdown to Telegram, 4000-char split, exposes `wasPartial` after `.end()`). `ctx.startChatStream(response)` (high-level: consumes the stream, renders reasoning as a Telegram `expandable_blockquote` entity + content as streamed markdown — both go through `markdownToFormattable` with graceful degradation — returns `{ content, reasoning }`). **History:** `llmHistory({...})` returns `.plugin` (decorates `ctx.llm` with `.add() / .get() / .clear() / .all() / .clearAll()`, per-(user, thread) OpenAI `ChatMessage` shape, persisted in the shared session record so the menu's 🗑 Forget wipes it automatically) AND `.menuItem` (drop-in "🗑 Delete this thread" for `botMenu` — wipes the LLM history AND calls `deleteForumTopic` so the Telegram thread + all its messages disappear from the chat; falls back to Redis-only clear when no `threadId` is present). |
449
450
  | `@adriangalilea/utils/bot/menu` | `botMenu({ command, description, items, privacy?, personalData?, adminContact })` — `/settings` command + InlineKeyboard router. Root view always renders a `🛡️ Privacy & data` submenu button that wraps the privacy policy link plus (if `personalData: { storage }`) 🗑 Forget + 📥 Export buttons. Items take `keepRow` (render on the same row as the next item — e.g. a two-per-row language picker) and `rootExtra` (render at the bottom of the root menu, below Privacy & data). `label` / `header` / `style` / `visible` resolvers may be **async** (read your db at render time — never cache render strings in the session); `parseMode: 'HTML'` renders the header formatted (you own escaping); `personalData.onForget(ctx, userId)` wipes YOUR tables inside the same try as the session delete, so Forget either forgets everything or reports failure. `toggleMenuItem({ id, read, write, label: { off, on }, toast? })` — convenience factory for boolean-toggle items with dynamic label + optional toast, storage-agnostic via `read`/`write` closures. |
@@ -25,4 +25,5 @@ export * from "./menu.js";
25
25
  export * from "./notify.js";
26
26
  export * from "./payments/index.js";
27
27
  export * from "./storage.js";
28
+ export * from "./user.js";
28
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
package/dist/bot/index.js CHANGED
@@ -25,4 +25,5 @@ export * from "./menu.js";
25
25
  export * from "./notify.js";
26
26
  export * from "./payments/index.js";
27
27
  export * from "./storage.js";
28
+ export * from "./user.js";
28
29
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * User identity formatting — the "[name] [@username] [id]" composition every bot
3
+ * re-rolls for admin DMs, logs, and access panels. Worker-safe (no env, no OS,
4
+ * zero deps).
5
+ *
6
+ * A Telegram user MAY have a username and MAY expose a name; the honest label
7
+ * shows what exists and never pads what doesn't:
8
+ *
9
+ * name + username → Ada Lovelace (@ada · 42)
10
+ * name only → Ada Lovelace (42)
11
+ * username only → @ada (42)
12
+ * id only → id 42
13
+ * nothing → unknown
14
+ *
15
+ * Both gramio spellings are read (wrapper camelCase `firstName`, raw payload
16
+ * `first_name`), so contexts, payloads, and stored rows all format alike. Plain
17
+ * text by design: these lines land in admin DMs and logs sent without a
18
+ * parse_mode, where a name containing `<` or `&` must stay literal.
19
+ *
20
+ * @example
21
+ * await notifyAdmins(bot, ids, `👥 ${userLabel(ctx.from)} added me to ${title}`)
22
+ */
23
+ /** The fields a user label is built from. Every field optional — pass any of a
24
+ * gramio `from`, a raw Telegram payload user, or your own stored row. */
25
+ export type UserRef = {
26
+ id?: number;
27
+ username?: string;
28
+ firstName?: string;
29
+ lastName?: string;
30
+ first_name?: string;
31
+ last_name?: string;
32
+ };
33
+ /** One line naming a user by whatever identity they actually have. */
34
+ export declare function userLabel(u: UserRef): string;
35
+ //# sourceMappingURL=user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/bot/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;0EAC0E;AAC1E,MAAM,MAAM,OAAO,GAAG;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAa5C"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * User identity formatting — the "[name] [@username] [id]" composition every bot
3
+ * re-rolls for admin DMs, logs, and access panels. Worker-safe (no env, no OS,
4
+ * zero deps).
5
+ *
6
+ * A Telegram user MAY have a username and MAY expose a name; the honest label
7
+ * shows what exists and never pads what doesn't:
8
+ *
9
+ * name + username → Ada Lovelace (@ada · 42)
10
+ * name only → Ada Lovelace (42)
11
+ * username only → @ada (42)
12
+ * id only → id 42
13
+ * nothing → unknown
14
+ *
15
+ * Both gramio spellings are read (wrapper camelCase `firstName`, raw payload
16
+ * `first_name`), so contexts, payloads, and stored rows all format alike. Plain
17
+ * text by design: these lines land in admin DMs and logs sent without a
18
+ * parse_mode, where a name containing `<` or `&` must stay literal.
19
+ *
20
+ * @example
21
+ * await notifyAdmins(bot, ids, `👥 ${userLabel(ctx.from)} added me to ${title}`)
22
+ */
23
+ /** One line naming a user by whatever identity they actually have. */
24
+ export function userLabel(u) {
25
+ const name = [u.firstName ?? u.first_name, u.lastName ?? u.last_name]
26
+ .filter(Boolean)
27
+ .join(" ")
28
+ .trim();
29
+ const at = u.username ? `@${u.username}` : "";
30
+ const id = u.id !== undefined ? String(u.id) : "";
31
+ if (name) {
32
+ const detail = [at, id].filter(Boolean).join(" · ");
33
+ return detail ? `${name} (${detail})` : name;
34
+ }
35
+ if (at)
36
+ return id ? `${at} (${id})` : at;
37
+ return id ? `id ${id}` : "unknown";
38
+ }
39
+ //# sourceMappingURL=user.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/bot/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAaH,sEAAsE;AACtE,MAAM,UAAU,SAAS,CAAC,CAAU;IACnC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC;SACnE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC;SACT,IAAI,EAAE,CAAC;IACT,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,IAAI,IAAI,EAAE,CAAC;QACV,MAAM,MAAM,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpD,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9C,CAAC;IACD,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACpC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriangalilea/utils",
3
- "version": "1.10.1",
3
+ "version": "1.11.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",
@@ -131,6 +131,10 @@
131
131
  "./bot/groups": {
132
132
  "types": "./dist/bot/groups.d.ts",
133
133
  "default": "./dist/bot/groups.js"
134
+ },
135
+ "./bot/user": {
136
+ "types": "./dist/bot/user.d.ts",
137
+ "default": "./dist/bot/user.js"
134
138
  }
135
139
  },
136
140
  "files": [
@@ -156,7 +160,8 @@
156
160
  "demo:bot": "tsx tests/create-demo.ts",
157
161
  "test:tg-html": "tsx tests/tg-html-test.ts",
158
162
  "test:flags": "tsx tests/flags-test.ts",
159
- "test:groups": "tsx tests/groups-test.ts"
163
+ "test:groups": "tsx tests/groups-test.ts",
164
+ "test:user": "tsx tests/user-test.ts"
160
165
  },
161
166
  "keywords": [
162
167
  "typescript",