@adriangalilea/utils 2.7.0 → 2.9.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 +2 -0
- package/dist/bot/index.d.ts +1 -0
- package/dist/bot/index.d.ts.map +1 -1
- package/dist/bot/index.js +1 -0
- package/dist/bot/index.js.map +1 -1
- package/dist/bot/metrics.d.ts +106 -0
- package/dist/bot/metrics.d.ts.map +1 -0
- package/dist/bot/metrics.js +91 -0
- package/dist/bot/metrics.js.map +1 -0
- package/dist/bot/worker.d.ts +13 -0
- package/dist/bot/worker.d.ts.map +1 -1
- package/dist/bot/worker.js +30 -0
- package/dist/bot/worker.js.map +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -462,9 +462,11 @@ Then `pnpm install`. Every `ctx.send` / `ctx.sendDocument` / `ctx.reply` / etc.
|
|
|
462
462
|
| `@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)`). |
|
|
463
463
|
| `@adriangalilea/utils/bot/language` (picker surface) | The language-picker vocabulary and surfaces, so bots don't fork label lists: `flagFor` / `autonym` / `languageLabel` ("🇪🇸 Español"), `languagePickerItem({ label, codes, isActive, pick })` (the 2-up primary-highlighted MenuItem factory — storage and policy live in your closures), and `addLanguageRows(kb, { codes, pack, active?, activeStyle? })` (the raw-InlineKeyboard twin for onboarding / group-welcome keyboards; `activeStyle: "success"` when primary already marks something else on the keyboard). |
|
|
464
464
|
| `@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. |
|
|
465
|
+
| `@adriangalilea/utils/bot/inline-feedback` | Behavioral tripwire for BotFather's inline feedback probability (gates `chosen_inline_result`; defaults 0%, silently resets on bot transfer and inline-mode toggling, invisible to `getMe`): served-but-never-chosen crosses a threshold → throttled admin DM naming the exact BotFather switch. Storage injected as three ops; never throws into the answer path. |
|
|
465
466
|
| `@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. |
|
|
466
467
|
| `@adriangalilea/utils/llm` | Multi-provider LLM caller on the Vercel AI SDK — the policy layer the SDK doesn't ship. `createLlm({ providers, health? })` → `.stream(req)` (typed events: `delta` / `reasoning` / `reset` / `tool-call` / `end`) and `.complete(req)`. **Failover:** priority-ordered across OpenAI-compatible, Anthropic-compatible, and OpenRouter endpoints; multiple keys per provider (health tracked per key fingerprint); per-key circuit breaker with exponential cool-down persisted in any KV-shaped `HealthStore`; an attempt that dies after emitting yields `reset` (discard, next candidate regenerates); empty completions count as failures. **Accounting:** tokens summed across billed attempts; `costUsd` is the ACTUAL charge (OpenRouter usage accounting via `providerMetadata`, or a `cost` field on the raw usage frame) — never a price-table estimate. **Knobs:** per-model temperature, maxTokens caps, `disableThinking` mapped to each dialect. **Tools:** AI SDK `tool()` + `toolChoice` pass through (re-exported). Worker-safe; peers: `ai`, `@ai-sdk/openai-compatible`, `@ai-sdk/anthropic`, `@openrouter/ai-sdk-provider`. |
|
|
467
468
|
| `@adriangalilea/utils/bot/llm` | The Telegram side of an LLM chatbot; the model side is `@adriangalilea/utils/llm`. **Output:** `streamChatReply(ctx, events, opts?)` consumes an `AsyncIterable<LlmStreamEvent>` and paints it with Telegram's native message-draft streaming (`sendMessageDraft` full-frame repaints, throttled ~1/s, keepalive under the ~30s draft TTL), then persists the finished markdown via `ctx.send`, entity-split across 4096 by `@gramio/split`. Reasoning models get a thinking phase — streams into the ephemeral draft and evaporates (`reasoning: 'preview'`, default), persists as an expandable blockquote (`'message'`), or never renders at all (`'hidden'`). Upstream `reset` (provider failover) repaints the draft from scratch. Drafts are private-chat-only (+ BotFather forum-topic mode); elsewhere the preview phase is skipped and only the final send happens. Returns `{ content, reasoning, toolCalls, usage, messages }`. The painter underneath is `@adriangalilea/utils/bot/draft` (`createDraftPreview(ctx, { render, throttleMs? })`): dependency-light (no markdown machinery), for producers that push deltas or own their per-frame rendering (plain or rich-message frames) and persist path, with `streamForensics` (render-regression + reset warnings) built in and exported standalone. **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 history-only clear when no `threadId` is present). |
|
|
469
|
+
| `@adriangalilea/utils/bot/metrics` | Self-serve product measurement, the flags pattern applied to counting: `defineMetrics(spec, { write, funnels? })` — declare a metric once (counter / timing, `perUser: true` for uniques·repeat·retention·funnels), `bump()`/`record()` at the call site (fire-and-forget, never throws), `describe()` is the schema panels render generically so a new metric reaches every panel with zero panel edits. Storage = one injected atomic day-bucket upsert (aggregate row + per-user row when `user` given); per-user rows are personal data — wipe on forget, prune by age. Money stays in ledgers. |
|
|
468
470
|
| `@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. |
|
|
469
471
|
| `@adriangalilea/utils/bot/payments` | `botPayments({ session, storage, paysupport, paysupportHint?, legal, waiver, vip?, credits?, perks? })` — Telegram Stars monetization in one drop-in plugin. **Three axes, all optional:** `vip` (positional tier ladder — single rung in v1 is just `vip: [{...}]`, ladder is `vip: [{...}, {...}]`; ids are `vip.1`, `vip.2`, …), `credits` (consumable balance + top-up packs `credits.1`, `credits.2`, …), `perks` (orthogonal one-shot unlocks `perks.<key>`). **Surface:** `ctx.payments.atLeast('vip')` / `atLeast('vip.2')` (typed rank check), `ctx.payments.tier()` / `.tier.level()` / `.tier.label()`, `ctx.payments.credits.{balance, consume, tryConsume}` (throws `InsufficientCredits`), `ctx.payments.has(perkId)`, `await ctx.payments.require('vip', { feature? })` (gate that sends a localized upgrade prompt deep-linked to `/settings → 💎 VIP`), `await ctx.payments.invoice(productKey)` (threads Art. 103(m) TRLGDCU consent inline before `sendInvoice`). **Owns:** waiver consent flow (versioned text → forces re-consent on bump, snapshotted on every charge for audit), `/paysupport` slash command (Telegram ToS §6.5; `paysupportHint` overrides the where-to-manage-charges line when your menu isn't `/settings`), idempotent `successful_payment` fulfillment via `pay:idempotency:{chargeId}` sentinel, lazy subscription expiry (no cron needed), tier upgrade auto-cancel of the lower rung's renewal, and admin-DM refund approval (mirror of `accessControl`'s [✅ Aprobar][❌ Denegar] pattern). **Returns:** `{ plugin, menuItem, payouts, onFulfilled }` — `menuItem` is the drop-in `💎 VIP` entry for `botMenu`; `payouts.{record, list, export, exportForUsers}` is the Fragment payout ledger (you receive TON, log the EUR conversion, export time-windowed CSV/JSON for your gestor); `onFulfilled(productKey \| '*', handler)` / `onRefunded(...)` register fire-and-forget hooks (purchase applied / admin-approved refund — a revenue ledger writes on one, reverses on the other). **Stars-only by design** — Telegram ToS §6.2 forbids third-party payment providers for digital goods. Crypto Pay deferred (MiCA risk); Stripe-outside-Telegram is a future v2 channel. Full compliance memo (Spanish-autónomo seller-of-record analysis, Verifactu vs Crea y Crece, MiCA, Art. 103(m) waiver text, GDPR retention) in `src/bot/payments/CLAUDE.md`. |
|
|
470
472
|
| `@adriangalilea/utils/bot/create` | `createBot<S>({ token?, storage?, initial?, admins?, language?, menu?, access?, payments?, handlers?, worker? })` — the composer (see "One bot file, ideation → production" above). Returns `{ build, session, poll, isMain, fetch }`: `poll()` long-polls, `export default app` is a complete Worker, `app.session(ctx)` / the `handlers` callback's `session(ctx)` is the TYPED accessor for your `S` fields. Owns storage+session wiring; resolves storage per environment (D1 binding → `bot/storage-d1`; `BOT_PERSIST` path → sqlite, `redis://` → redis, lazily-imported optional peers; else announced-ephemeral memory) — or pass `storage: (env) => Storage` when the choice is env-dependent (e.g. a D1 binding not named `DB`). Boot NARRATES the composition (`session: memory …`, `features: language(en,es) · menu(/settings)`) so every implicit decision is visible where you're looking. Runnable demo: `pnpm demo:bot`. |
|
package/dist/bot/index.d.ts
CHANGED
package/dist/bot/index.d.ts.map
CHANGED
|
@@ -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,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,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"}
|
|
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,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
package/dist/bot/index.js
CHANGED
package/dist/bot/index.js.map
CHANGED
|
@@ -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,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,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"}
|
|
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,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metric registry — self-serve product measurement, the `bot/flags` pattern
|
|
3
|
+
* applied to counting. Declare a metric ONCE in code and it exists
|
|
4
|
+
* everywhere: `metrics.<key>.bump()` / `.record(value)` at the call site,
|
|
5
|
+
* generic day-bucketed storage underneath (no migration per metric), and
|
|
6
|
+
* `metrics.describe()` as the schema admin panels render generically — a new
|
|
7
|
+
* metric reaches every panel with zero panel edits.
|
|
8
|
+
*
|
|
9
|
+
* Built to answer PRODUCT questions, not just count:
|
|
10
|
+
* volume how many times → counter
|
|
11
|
+
* averages how long / how big → timing (count + sum → avg)
|
|
12
|
+
* uniques how many distinct users → perUser: true
|
|
13
|
+
* repeat used it again same day? → per-user day rows with count > 1
|
|
14
|
+
* retention came back another day? → same user on ≥ 2 distinct days
|
|
15
|
+
* funnels of those who did A, who did B → declared pairs over the
|
|
16
|
+
* shared user space; panels render conversion % generically
|
|
17
|
+
* depth how far do they go → per-user daily counts distribute
|
|
18
|
+
*
|
|
19
|
+
* Storage is ONE injected write op; the atomic upserts live with the
|
|
20
|
+
* consumer's database. Contract: always add (count, sum) to the day bucket
|
|
21
|
+
* for `key`; when `user` is present, ALSO to the per-user day bucket, e.g.:
|
|
22
|
+
*
|
|
23
|
+
* INSERT INTO metric (key, day, count, sum) VALUES (?1, date('now'), ?2, ?3)
|
|
24
|
+
* ON CONFLICT (key, day) DO UPDATE SET count = count + ?2, sum = sum + ?3;
|
|
25
|
+
* -- and when user is present:
|
|
26
|
+
* INSERT INTO metric_user (key, user, day, count, sum) VALUES (?1, ?4, date('now'), ?2, ?3)
|
|
27
|
+
* ON CONFLICT (key, user, day) DO UPDATE SET count = count + ?2, sum = sum + ?3;
|
|
28
|
+
*
|
|
29
|
+
* Per-user rows are personal data: wipe them in your forget-user path, and
|
|
30
|
+
* prune by age (growth is users × metrics × days). Writes are FIRE-AND-FORGET
|
|
31
|
+
* and never throw — measurement must never break the product. The registry
|
|
32
|
+
* IS the cardinality cap: keys are declared identifiers, never free-form
|
|
33
|
+
* strings. Money stays in ledgers; this is operational/product counting.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const metrics = defineMetrics({
|
|
37
|
+
* chapterOpens: { kind: "counter", label: "chapter opens", perUser: true },
|
|
38
|
+
* chapterPages: { kind: "counter", label: "chapter pages", perUser: true, help: "Back/Next taps" },
|
|
39
|
+
* chapterLatency: { kind: "timing", label: "chapter latency", unit: "ms" },
|
|
40
|
+
* }, {
|
|
41
|
+
* write: (key, addCount, addSum, user) => store.metricBump(key, addCount, addSum, user),
|
|
42
|
+
* funnels: [["chapterOpens", "chapterPages"]],
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* void metrics.chapterOpens.bump({ user: ctx.from.id })
|
|
46
|
+
* void metrics.chapterLatency.record(Date.now() - startedAt)
|
|
47
|
+
* metrics.describe() // → { metrics: [...], funnels: [...] } — panels iterate this
|
|
48
|
+
*/
|
|
49
|
+
/** One metric declaration. `perUser: true` unlocks uniques/repeat/retention/funnels for it. */
|
|
50
|
+
export type MetricSpec = {
|
|
51
|
+
kind: "counter";
|
|
52
|
+
label: string;
|
|
53
|
+
help?: string;
|
|
54
|
+
perUser?: boolean;
|
|
55
|
+
} | {
|
|
56
|
+
kind: "timing";
|
|
57
|
+
label: string;
|
|
58
|
+
help?: string;
|
|
59
|
+
unit?: string;
|
|
60
|
+
perUser?: boolean;
|
|
61
|
+
};
|
|
62
|
+
export interface MetricsOptions<Keys extends string = string> {
|
|
63
|
+
/**
|
|
64
|
+
* The atomic day-bucket upsert(s): add `addCount`/`addSum` to today's row
|
|
65
|
+
* for `key`, and — when `user` is present — to the per-user day row too
|
|
66
|
+
* (one batch). Failures are swallowed by the registry; make the op itself
|
|
67
|
+
* atomic.
|
|
68
|
+
*/
|
|
69
|
+
write: (key: string, addCount: number, addSum: number, user?: number) => Promise<unknown>;
|
|
70
|
+
/**
|
|
71
|
+
* Declared conversion pairs over perUser metrics ("of users who did FROM,
|
|
72
|
+
* how many did TO"). Panels render these generically; they are queries
|
|
73
|
+
* over the shared user space, not extra storage.
|
|
74
|
+
*/
|
|
75
|
+
funnels?: ReadonlyArray<readonly [Keys, Keys]>;
|
|
76
|
+
}
|
|
77
|
+
export interface CounterHandle {
|
|
78
|
+
/** Count `n` events (default 1); pass `user` on perUser metrics. Fire-and-forget, never throws. */
|
|
79
|
+
bump(opts?: {
|
|
80
|
+
user?: number;
|
|
81
|
+
n?: number;
|
|
82
|
+
}): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
export interface TimingHandle {
|
|
85
|
+
/** Record one sample; pass `user` on perUser metrics. Fire-and-forget, never throws. */
|
|
86
|
+
record(value: number, opts?: {
|
|
87
|
+
user?: number;
|
|
88
|
+
}): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
export interface MetricsSchema {
|
|
91
|
+
metrics: Array<{
|
|
92
|
+
key: string;
|
|
93
|
+
} & MetricSpec>;
|
|
94
|
+
funnels: Array<{
|
|
95
|
+
from: string;
|
|
96
|
+
to: string;
|
|
97
|
+
}>;
|
|
98
|
+
}
|
|
99
|
+
export type Metrics<Spec extends Record<string, MetricSpec>> = {
|
|
100
|
+
[K in keyof Spec]: Spec[K]["kind"] extends "counter" ? CounterHandle : TimingHandle;
|
|
101
|
+
} & {
|
|
102
|
+
/** The registry schema, for panels to render generically. */
|
|
103
|
+
describe(): MetricsSchema;
|
|
104
|
+
};
|
|
105
|
+
export declare function defineMetrics<const Spec extends Record<string, MetricSpec>>(spec: Spec, opts: MetricsOptions<Extract<keyof Spec, string>>): Metrics<Spec>;
|
|
106
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/bot/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,+FAA+F;AAC/F,MAAM,MAAM,UAAU,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACpE;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEL,MAAM,WAAW,cAAc,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IAC3D;;;;;OAKG;IACH,KAAK,EAAE,CACN,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,KACT,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,aAAa;IAC7B,mGAAmG;IACnG,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D;AACD,MAAM,WAAW,YAAY;IAC5B,wFAAwF;IACxF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,UAAU,CAAC,CAAC;IAC7C,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7C;AAED,MAAM,MAAM,OAAO,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI;KAC7D,CAAC,IAAI,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,SAAS,GACjD,aAAa,GACb,YAAY;CACf,GAAG;IACH,6DAA6D;IAC7D,QAAQ,IAAI,aAAa,CAAC;CAC1B,CAAC;AAEF,wBAAgB,aAAa,CAAC,KAAK,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EAC1E,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,MAAM,CAAC,CAAC,GAC/C,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metric registry — self-serve product measurement, the `bot/flags` pattern
|
|
3
|
+
* applied to counting. Declare a metric ONCE in code and it exists
|
|
4
|
+
* everywhere: `metrics.<key>.bump()` / `.record(value)` at the call site,
|
|
5
|
+
* generic day-bucketed storage underneath (no migration per metric), and
|
|
6
|
+
* `metrics.describe()` as the schema admin panels render generically — a new
|
|
7
|
+
* metric reaches every panel with zero panel edits.
|
|
8
|
+
*
|
|
9
|
+
* Built to answer PRODUCT questions, not just count:
|
|
10
|
+
* volume how many times → counter
|
|
11
|
+
* averages how long / how big → timing (count + sum → avg)
|
|
12
|
+
* uniques how many distinct users → perUser: true
|
|
13
|
+
* repeat used it again same day? → per-user day rows with count > 1
|
|
14
|
+
* retention came back another day? → same user on ≥ 2 distinct days
|
|
15
|
+
* funnels of those who did A, who did B → declared pairs over the
|
|
16
|
+
* shared user space; panels render conversion % generically
|
|
17
|
+
* depth how far do they go → per-user daily counts distribute
|
|
18
|
+
*
|
|
19
|
+
* Storage is ONE injected write op; the atomic upserts live with the
|
|
20
|
+
* consumer's database. Contract: always add (count, sum) to the day bucket
|
|
21
|
+
* for `key`; when `user` is present, ALSO to the per-user day bucket, e.g.:
|
|
22
|
+
*
|
|
23
|
+
* INSERT INTO metric (key, day, count, sum) VALUES (?1, date('now'), ?2, ?3)
|
|
24
|
+
* ON CONFLICT (key, day) DO UPDATE SET count = count + ?2, sum = sum + ?3;
|
|
25
|
+
* -- and when user is present:
|
|
26
|
+
* INSERT INTO metric_user (key, user, day, count, sum) VALUES (?1, ?4, date('now'), ?2, ?3)
|
|
27
|
+
* ON CONFLICT (key, user, day) DO UPDATE SET count = count + ?2, sum = sum + ?3;
|
|
28
|
+
*
|
|
29
|
+
* Per-user rows are personal data: wipe them in your forget-user path, and
|
|
30
|
+
* prune by age (growth is users × metrics × days). Writes are FIRE-AND-FORGET
|
|
31
|
+
* and never throw — measurement must never break the product. The registry
|
|
32
|
+
* IS the cardinality cap: keys are declared identifiers, never free-form
|
|
33
|
+
* strings. Money stays in ledgers; this is operational/product counting.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const metrics = defineMetrics({
|
|
37
|
+
* chapterOpens: { kind: "counter", label: "chapter opens", perUser: true },
|
|
38
|
+
* chapterPages: { kind: "counter", label: "chapter pages", perUser: true, help: "Back/Next taps" },
|
|
39
|
+
* chapterLatency: { kind: "timing", label: "chapter latency", unit: "ms" },
|
|
40
|
+
* }, {
|
|
41
|
+
* write: (key, addCount, addSum, user) => store.metricBump(key, addCount, addSum, user),
|
|
42
|
+
* funnels: [["chapterOpens", "chapterPages"]],
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* void metrics.chapterOpens.bump({ user: ctx.from.id })
|
|
46
|
+
* void metrics.chapterLatency.record(Date.now() - startedAt)
|
|
47
|
+
* metrics.describe() // → { metrics: [...], funnels: [...] } — panels iterate this
|
|
48
|
+
*/
|
|
49
|
+
export function defineMetrics(spec, opts) {
|
|
50
|
+
for (const [key, s] of Object.entries(spec)) {
|
|
51
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(key))
|
|
52
|
+
throw new Error(`metrics: invalid key "${key}"`);
|
|
53
|
+
if (!s.label)
|
|
54
|
+
throw new Error(`metrics: "${key}" needs a label`);
|
|
55
|
+
}
|
|
56
|
+
for (const [from, to] of opts.funnels ?? []) {
|
|
57
|
+
for (const k of [from, to]) {
|
|
58
|
+
if (!spec[k])
|
|
59
|
+
throw new Error(`metrics: funnel references unknown metric "${k}"`);
|
|
60
|
+
if (!spec[k].perUser)
|
|
61
|
+
throw new Error(`metrics: funnel metric "${k}" must be perUser (funnels join on user)`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const [key, s] of Object.entries(spec)) {
|
|
66
|
+
out[key] =
|
|
67
|
+
s.kind === "counter"
|
|
68
|
+
? {
|
|
69
|
+
async bump(o) {
|
|
70
|
+
await opts
|
|
71
|
+
.write(key, o?.n ?? 1, 0, s.perUser ? o?.user : undefined)
|
|
72
|
+
.catch(() => { });
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
: {
|
|
76
|
+
async record(value, o) {
|
|
77
|
+
await opts
|
|
78
|
+
.write(key, 1, value, s.perUser ? o?.user : undefined)
|
|
79
|
+
.catch(() => { });
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
...out,
|
|
85
|
+
describe: () => ({
|
|
86
|
+
metrics: Object.entries(spec).map(([key, s]) => ({ key, ...s })),
|
|
87
|
+
funnels: (opts.funnels ?? []).map(([from, to]) => ({ from, to })),
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/bot/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAyDH,MAAM,UAAU,aAAa,CAC5B,IAAU,EACV,IAAiD;IAEjD,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,GAAG,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,GAAG,CAAC,CAAC;QAClD,IAAI,CAAC,CAAC,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,aAAa,GAAG,iBAAiB,CAAC,CAAC;IAClE,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QAC7C,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBACX,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,GAAG,CAAC,CAAC;YACrE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO;gBACnB,MAAM,IAAI,KAAK,CACd,2BAA2B,CAAC,0CAA0C,CACtE,CAAC;QACJ,CAAC;IACF,CAAC;IAED,MAAM,GAAG,GAAiD,EAAE,CAAC;IAC7D,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,GAAG,CAAC;YACP,CAAC,CAAC,IAAI,KAAK,SAAS;gBACnB,CAAC,CAAC;oBACA,KAAK,CAAC,IAAI,CAAC,CAAiC;wBAC3C,MAAM,IAAI;6BACR,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;6BACzD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBACnB,CAAC;iBACD;gBACF,CAAC,CAAC;oBACA,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,CAAqB;wBAChD,MAAM,IAAI;6BACR,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;6BACrD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBACnB,CAAC;iBACD,CAAC;IACN,CAAC;IACD,OAAO;QACN,GAAG,GAAG;QACN,QAAQ,EAAE,GAAkB,EAAE,CAAC,CAAC;YAC/B,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YAChE,OAAO,EAAE,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;SACjE,CAAC;KACe,CAAC;AACpB,CAAC"}
|
package/dist/bot/worker.d.ts
CHANGED
|
@@ -28,10 +28,23 @@ export type WorkerBot = {
|
|
|
28
28
|
type WaitUntilCtx = {
|
|
29
29
|
waitUntil(promise: Promise<unknown>): void;
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* True when the update is the bot's own voice: a message it authored, or a forward of one of
|
|
33
|
+
* its messages (forward_origin type "user" pointing back at it). Replies TO the bot are NOT
|
|
34
|
+
* echoes — they're the conversation.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isSelfEcho(update: unknown, selfId: number): boolean;
|
|
31
37
|
export type BotWorkerRuntime = {
|
|
32
38
|
bot: WorkerBot;
|
|
33
39
|
/** Telegram webhook secret; unset → webhook/setup respond 500 (a misconfigured prod must scream). */
|
|
34
40
|
webhookSecret?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The bot's own user id (the digits before the token's colon). When set, updates that are
|
|
43
|
+
* the bot's OWN VOICE echoed back — a message it authored (channels/business echo their own
|
|
44
|
+
* posts) or a FORWARD of one of its messages (a forwarded summary still carries live links) —
|
|
45
|
+
* are acked and dropped before dispatch: a bot must never listen to itself, or it loops.
|
|
46
|
+
*/
|
|
47
|
+
selfId?: number;
|
|
35
48
|
/** Bearer for /pause, /resume, /webhook-status; unset → those 404 (surface disabled). */
|
|
36
49
|
operatorSecret?: string;
|
|
37
50
|
/** Deployment label echoed in responses/DMs (e.g. the wrangler env name). */
|
package/dist/bot/worker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/bot/worker.ts"],"names":[],"mappings":"AA2CA,OAAO,EACN,KAAK,aAAa,EAIlB,MAAM,aAAa,CAAC;AAIrB,6FAA6F;AAC7F,MAAM,MAAM,SAAS,GAAG;IACvB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7B,OAAO,EAAE;QAAE,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAC7D,GAAG,EAAE;QACJ,UAAU,CAAC,MAAM,EAAE;YAClB,GAAG,EAAE,MAAM,CAAC;YACZ,YAAY,EAAE,MAAM,CAAC;YACrB,eAAe,EAAE,MAAM,EAAE,CAAC;SAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACrB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,cAAc,IAAI,OAAO,CAAC;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAC;QAClE,+DAA+D;QAC/D,WAAW,CAAC,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACzE,CAAC;CACF,CAAC;AAEF,KAAK,YAAY,GAAG;IAAE,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/bot/worker.ts"],"names":[],"mappings":"AA2CA,OAAO,EACN,KAAK,aAAa,EAIlB,MAAM,aAAa,CAAC;AAIrB,6FAA6F;AAC7F,MAAM,MAAM,SAAS,GAAG;IACvB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7B,OAAO,EAAE;QAAE,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAC7D,GAAG,EAAE;QACJ,UAAU,CAAC,MAAM,EAAE;YAClB,GAAG,EAAE,MAAM,CAAC;YACZ,YAAY,EAAE,MAAM,CAAC;YACrB,eAAe,EAAE,MAAM,EAAE,CAAC;SAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACrB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,cAAc,IAAI,OAAO,CAAC;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAC;QAClE,+DAA+D;QAC/D,WAAW,CAAC,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACzE,CAAC;CACF,CAAC;AAEF,KAAK,YAAY,GAAG;IAAE,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC;AAgBnE;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CASnE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC9B,GAAG,EAAE,SAAS,CAAC;IACf,qGAAqG;IACrG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC5C,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,mFAAmF;IACnF,MAAM,CAAC,EAAE,CACR,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,YAAY,KACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC;IAChD,qFAAqF;IACrF,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EACjC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,GACjE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAsLtE"}
|
package/dist/bot/worker.js
CHANGED
|
@@ -43,6 +43,32 @@ import { buildAllowedUpdates } from "gramio";
|
|
|
43
43
|
import { createLogger } from "../universal/log.js";
|
|
44
44
|
import { alertAdminError, alertThrottle, notifyAdmins, } from "./notify.js";
|
|
45
45
|
const log = createLogger("bot/worker");
|
|
46
|
+
const MESSAGE_KEYS = [
|
|
47
|
+
"message",
|
|
48
|
+
"edited_message",
|
|
49
|
+
"channel_post",
|
|
50
|
+
"edited_channel_post",
|
|
51
|
+
"business_message",
|
|
52
|
+
"edited_business_message",
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* True when the update is the bot's own voice: a message it authored, or a forward of one of
|
|
56
|
+
* its messages (forward_origin type "user" pointing back at it). Replies TO the bot are NOT
|
|
57
|
+
* echoes — they're the conversation.
|
|
58
|
+
*/
|
|
59
|
+
export function isSelfEcho(update, selfId) {
|
|
60
|
+
const u = update;
|
|
61
|
+
for (const key of MESSAGE_KEYS) {
|
|
62
|
+
const m = u?.[key];
|
|
63
|
+
if (!m)
|
|
64
|
+
continue;
|
|
65
|
+
if (m.from?.id === selfId)
|
|
66
|
+
return true;
|
|
67
|
+
if (m.forward_origin?.sender_user?.id === selfId)
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
46
72
|
/**
|
|
47
73
|
* Build the Worker `fetch` handler. `resolve` runs per request — memoize your
|
|
48
74
|
* bot construction inside it (the classic `let botPromise` pattern) so the
|
|
@@ -82,6 +108,10 @@ export function botWorkerFetch(resolve) {
|
|
|
82
108
|
catch {
|
|
83
109
|
return new Response("Bad Request", { status: 400 });
|
|
84
110
|
}
|
|
111
|
+
// The bot never listens to itself (see isSelfEcho): ack and drop.
|
|
112
|
+
if (rt.selfId !== undefined && isSelfEcho(update, rt.selfId)) {
|
|
113
|
+
return new Response("OK");
|
|
114
|
+
}
|
|
85
115
|
// Ack Telegram immediately, then keep the Worker alive for the slow
|
|
86
116
|
// work (avoids GramIO's 30s webhook timeout).
|
|
87
117
|
ctx.waitUntil((async () => {
|
package/dist/bot/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/bot/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAEN,eAAe,EACf,aAAa,EACb,YAAY,GACZ,MAAM,aAAa,CAAC;AAErB,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/bot/worker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adriangalilea/utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.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",
|
|
@@ -151,6 +151,10 @@
|
|
|
151
151
|
"./bot/inline-feedback": {
|
|
152
152
|
"types": "./dist/bot/inline-feedback.d.ts",
|
|
153
153
|
"default": "./dist/bot/inline-feedback.js"
|
|
154
|
+
},
|
|
155
|
+
"./bot/metrics": {
|
|
156
|
+
"types": "./dist/bot/metrics.d.ts",
|
|
157
|
+
"default": "./dist/bot/metrics.js"
|
|
154
158
|
}
|
|
155
159
|
},
|
|
156
160
|
"files": [
|
|
@@ -180,7 +184,8 @@
|
|
|
180
184
|
"test:user": "tsx tests/user-test.ts",
|
|
181
185
|
"test:language-picker": "tsx tests/language-picker-test.ts",
|
|
182
186
|
"test:llm": "tsx tests/llm-live-test.ts",
|
|
183
|
-
"test:tg-md": "tsx tests/tg-md-test.ts"
|
|
187
|
+
"test:tg-md": "tsx tests/tg-md-test.ts",
|
|
188
|
+
"test:self-echo": "tsx tests/self-echo-test.ts"
|
|
184
189
|
},
|
|
185
190
|
"keywords": [
|
|
186
191
|
"typescript",
|