@adriangalilea/utils 0.26.0 → 0.28.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 +5 -2
- package/dist/bot/callbacks.d.ts +11 -1
- package/dist/bot/callbacks.d.ts.map +1 -1
- package/dist/bot/callbacks.js +3 -0
- package/dist/bot/callbacks.js.map +1 -1
- 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/kit.d.ts +9 -59
- package/dist/bot/kit.d.ts.map +1 -1
- package/dist/bot/kit.js +4 -142
- package/dist/bot/kit.js.map +1 -1
- package/dist/bot/notify.d.ts +59 -0
- package/dist/bot/notify.d.ts.map +1 -0
- package/dist/bot/notify.js +62 -0
- package/dist/bot/notify.js.map +1 -0
- package/dist/bot/payments/refund.d.ts +16 -4
- package/dist/bot/payments/refund.d.ts.map +1 -1
- package/dist/bot/payments/waiver.d.ts +6 -2
- package/dist/bot/payments/waiver.d.ts.map +1 -1
- package/dist/bot/session.d.ts +63 -0
- package/dist/bot/session.d.ts.map +1 -0
- package/dist/bot/session.js +93 -0
- package/dist/bot/session.js.map +1 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -385,7 +385,9 @@ Then `pnpm install`. Every `ctx.send` / `ctx.sendDocument` / `ctx.reply` / etc.
|
|
|
385
385
|
|---|---|
|
|
386
386
|
| `@adriangalilea/utils/bot/ctx` | Structural ctx types (`BotMessageCtx`, `BotCallbackCtx`, …) and the `narrow<T>(ctx)` cast helper. Pure typing — no runtime state. |
|
|
387
387
|
| `@adriangalilea/utils/bot/keys` | The bot-id key namespace, a **persisted contract**: `botId(ctx)`, `botStorageKey(ctx, userId)` → `bot-<id>:<userId>`, `botSubKey(ctx, sub)` → `bot-<id>:<sub>`. Every storage key the library writes derives from here, and those keys live in YOUR Redis/D1 rows — the shape never changes. Pure functions of `ctx.bot.info.id` (no env/fs), which is what keeps the whole bot surface Worker-safe. |
|
|
388
|
-
| `@adriangalilea/utils/bot/kit` | `gracefulStart(bot, opts?)` — SIGINT/SIGTERM → `bot.stop()` → exit; force-kills if shutdown hangs. DMs the admin `@<bot> started.` / `@<bot> shutting down.` by default when `KEV.TELEGRAM_ADMIN_ID` is set (graceful only — crashes don't trigger `onStop`); pass `notifyAdmin: false` to disable or `notifyAdmin: 12345` for an explicit chat id.<br>`adminContext({ adminId? })` — reads `TELEGRAM_ADMIN_ID` from `kev` (with optional hardcoded fallback), decorates `ctx.adminId` + `ctx.isAdmin`.<br
|
|
388
|
+
| `@adriangalilea/utils/bot/kit` | The Node-only corner. `gracefulStart(bot, opts?)` — SIGINT/SIGTERM → `bot.stop()` → exit; force-kills if shutdown hangs. DMs the admin `@<bot> started.` / `@<bot> shutting down.` by default when `KEV.TELEGRAM_ADMIN_ID` is set (graceful only — crashes don't trigger `onStop`); pass `notifyAdmin: false` to disable or `notifyAdmin: 12345` for an explicit chat id.<br>`adminContext({ adminId? })` — reads `TELEGRAM_ADMIN_ID` from `kev` (with optional hardcoded fallback), decorates `ctx.adminId` + `ctx.isAdmin`.<br>Also re-exports `botSession` / `prefixStorage` from `bot/session` so Node consumers keep one import. |
|
|
389
|
+
| `@adriangalilea/utils/bot/session` | `botSession(opts)` — **drop-in replacement for `@gramio/session`'s `session()`** that auto-namespaces every key as `bot-<id>:<senderId>` using `ctx.bot.info.id` (populated by `getMe()` at startup). Use this instead of `session()` — full stop. Multiple bots sharing one Redis/D1 stay isolated by construction; every plugin in this package derives the same prefix internally via `botStorageKey(ctx, userId)` / `botSubKey(ctx, sub)` (from `bot/keys`). No regex, no manual prefix argument, no way to forget.<br>`prefixStorage(storage, prefix)` — escape hatch for adding a top-level prefix on top of the bot-id namespace; almost never needed. Worker-safe (used to live in `bot/kit`, which forced Worker bots to hand-roll the keying). |
|
|
390
|
+
| `@adriangalilea/utils/bot/notify` | Best-effort admin DMs, worker-safe (you pass the ids; no env, no process). `notifyAdmins(bot, adminIds, text, extra?)` — DM each admin, failures logged and swallowed (a notification must never take the bot down). `alertAdminError(bot, adminIds, label, error, throttle?)` — truncated `🚨 label\nName: message`, rate-limited through a caller-owned `alertThrottle(ms?)` so a failure storm sends one alert per window. `gracefulStart`'s start/stop DMs are built on this. |
|
|
389
391
|
| `@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. |
|
|
390
392
|
| `@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. |
|
|
391
393
|
| `@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. |
|
|
@@ -398,7 +400,8 @@ Standard wiring:
|
|
|
398
400
|
```typescript
|
|
399
401
|
import { Bot } from 'gramio'
|
|
400
402
|
import { redisStorage } from '@gramio/storage-redis'
|
|
401
|
-
import { adminContext,
|
|
403
|
+
import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
|
|
404
|
+
import { botSession } from '@adriangalilea/utils/bot/session'
|
|
402
405
|
import { accessControl } from '@adriangalilea/utils/bot/access-control'
|
|
403
406
|
import { llmStream, llmHistory } from '@adriangalilea/utils/bot/llm'
|
|
404
407
|
|
package/dist/bot/callbacks.d.ts
CHANGED
|
@@ -49,6 +49,16 @@
|
|
|
49
49
|
import { CallbackData } from "gramio";
|
|
50
50
|
type FieldType = "string" | "number" | "string?" | "number?";
|
|
51
51
|
type FieldDef = Readonly<Record<string, FieldType>>;
|
|
52
|
+
type RequiredFieldKeys<F extends FieldDef> = {
|
|
53
|
+
[K in keyof F]: F[K] extends "string" | "number" ? K : never;
|
|
54
|
+
}[keyof F];
|
|
55
|
+
type FieldBase<T extends FieldType> = T extends `string${string}` ? string : number;
|
|
56
|
+
type SchemaOf<F extends FieldDef> = {
|
|
57
|
+
[K in RequiredFieldKeys<F> & string]: FieldBase<F[K]>;
|
|
58
|
+
} & {
|
|
59
|
+
[K in Exclude<keyof F, RequiredFieldKeys<F>> & string]?: FieldBase<F[K]>;
|
|
60
|
+
};
|
|
61
|
+
type TypedCallbackData<F extends FieldDef> = CallbackData<SchemaOf<F>, SchemaOf<F>>;
|
|
52
62
|
export type CallbackNamespace = {
|
|
53
63
|
readonly prefix: string;
|
|
54
64
|
/**
|
|
@@ -61,7 +71,7 @@ export type CallbackNamespace = {
|
|
|
61
71
|
* HMR / dual-import). A second registration with different fields
|
|
62
72
|
* panics — that's a programming error, not a runtime issue.
|
|
63
73
|
*/
|
|
64
|
-
data: <F extends FieldDef>(name: string, fields: F) =>
|
|
74
|
+
data: <F extends FieldDef>(name: string, fields: F) => TypedCallbackData<F>;
|
|
65
75
|
};
|
|
66
76
|
/**
|
|
67
77
|
* Reserve a callback-data prefix for a plugin. Returns a namespace
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callbacks.d.ts","sourceRoot":"","sources":["../../src/bot/callbacks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAE7D,KAAK,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"callbacks.d.ts","sourceRoot":"","sources":["../../src/bot/callbacks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAE7D,KAAK,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAMpD,KAAK,iBAAiB,CAAC,CAAC,SAAS,QAAQ,IAAI;KAC3C,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,GAAG,CAAC,GAAG,KAAK;CAC5D,CAAC,MAAM,CAAC,CAAC,CAAC;AACX,KAAK,SAAS,CAAC,CAAC,SAAS,SAAS,IAAI,CAAC,SAAS,SAAS,MAAM,EAAE,GAC9D,MAAM,GACN,MAAM,CAAC;AACV,KAAK,QAAQ,CAAC,CAAC,SAAS,QAAQ,IAAI;KAClC,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACrD,GAAG;KACF,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACxE,CAAC;AACF,KAAK,iBAAiB,CAAC,CAAC,SAAS,QAAQ,IAAI,YAAY,CACxD,QAAQ,CAAC,CAAC,CAAC,EACX,QAAQ,CAAC,CAAC,CAAC,CACX,CAAC;AAsCF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;;;OASG;IACH,IAAI,EAAE,CAAC,CAAC,SAAS,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,iBAAiB,CAAC,CAAC,CAAC,CAAC;CAC5E,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,KAAG,iBAsC3C,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAAO,aAAa,CAAC;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,CAAC;CACjB,CAIG,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAO,IAEzC,CAAC"}
|
package/dist/bot/callbacks.js
CHANGED
|
@@ -101,10 +101,13 @@ export const callbackNs = (prefix) => {
|
|
|
101
101
|
`with different fields ${JSON.stringify(existing.fields)}; ` +
|
|
102
102
|
`re-registration tried ${JSON.stringify(fields)}.`);
|
|
103
103
|
}
|
|
104
|
+
// fieldsEqual just proved the stored schema IS F's schema.
|
|
104
105
|
return existing.cb;
|
|
105
106
|
}
|
|
106
107
|
const cb = buildCallbackData(fullName, fields);
|
|
107
108
|
registry.set(fullName, { fullName, fields, cb });
|
|
109
|
+
// buildCallbackData accumulates exactly F's fields at runtime; the
|
|
110
|
+
// erased builder type is re-asserted here, once, at the boundary.
|
|
108
111
|
return cb;
|
|
109
112
|
},
|
|
110
113
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callbacks.js","sourceRoot":"","sources":["../../src/bot/callbacks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"callbacks.js","sourceRoot":"","sources":["../../src/bot/callbacks.ts"],"names":[],"mappings":"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"}
|
package/dist/bot/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export * from "./kit.js";
|
|
|
20
20
|
export * from "./language.js";
|
|
21
21
|
export * from "./llm.js";
|
|
22
22
|
export * from "./menu.js";
|
|
23
|
+
export * from "./notify.js";
|
|
23
24
|
export * from "./payments/index.js";
|
|
24
25
|
export * from "./storage.js";
|
|
25
26
|
//# sourceMappingURL=index.d.ts.map
|
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,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,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,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,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"}
|
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,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,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,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,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"}
|
package/dist/bot/kit.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Foundational helpers
|
|
2
|
+
* Foundational helpers for NODE bots — the library's deliberately
|
|
3
|
+
* Node-only corner (process signals, KEV env resolution). Worker bots
|
|
4
|
+
* use the worker-safe subpaths instead: `bot/session` (namespaced
|
|
5
|
+
* sessions) and `bot/notify` (admin DMs) hold the pieces that used to
|
|
6
|
+
* live here but never needed an OS.
|
|
3
7
|
*
|
|
4
8
|
* `gracefulStart(bot, opts?)` — wires SIGINT/SIGTERM to bot.stop(),
|
|
5
9
|
* runs an optional shutdown hook, force-kills if it hangs. DMs the
|
|
@@ -12,15 +16,9 @@
|
|
|
12
16
|
* every context with `ctx.adminId` (number) and `ctx.isAdmin`
|
|
13
17
|
* (boolean). Throws at startup if neither source provides an id.
|
|
14
18
|
*
|
|
15
|
-
* `botSession
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* several bots share one Redis instance, each writes to a disjoint
|
|
19
|
-
* keyspace (`bot-<id>:<senderId>`) with no manual wiring. Every
|
|
20
|
-
* plugin in this package (`accessControl`, `botMenu`,
|
|
21
|
-
* `llmHistory`, …) derives the SAME prefix from `ctx.bot` for its
|
|
22
|
-
* own storage access, so the whole package stays isolated by
|
|
23
|
-
* construction. Use this instead of `session()` — full stop.
|
|
19
|
+
* `botSession` / `prefixStorage` — re-exported from `bot/session` so
|
|
20
|
+
* Node consumers keep one import; the implementation is worker-safe
|
|
21
|
+
* and lives there.
|
|
24
22
|
*
|
|
25
23
|
* Peer deps: `gramio`, `@gramio/session`, `@gramio/storage`.
|
|
26
24
|
*
|
|
@@ -39,10 +37,9 @@
|
|
|
39
37
|
*
|
|
40
38
|
* await gracefulStart(bot, { onShutdown: () => db.end() })
|
|
41
39
|
*/
|
|
42
|
-
import { type SessionOptions, session } from "@gramio/session";
|
|
43
|
-
import type { Storage } from "@gramio/storage";
|
|
44
40
|
import type { Plugin as PluginType } from "gramio";
|
|
45
41
|
import { type AnyBot } from "gramio";
|
|
42
|
+
export { botSession, prefixStorage } from "./session.js";
|
|
46
43
|
export type GracefulStartOptions = {
|
|
47
44
|
/** Runs after `bot.stop()` resolves, before `process.exit`. Close DBs, flush logs. */
|
|
48
45
|
onShutdown?: () => Promise<void> | void;
|
|
@@ -88,52 +85,5 @@ export declare const adminContext: (opts?: AdminContextOptions) => PluginType<{}
|
|
|
88
85
|
isAdmin: boolean;
|
|
89
86
|
};
|
|
90
87
|
}, {}>;
|
|
91
|
-
/**
|
|
92
|
-
* Drop-in replacement for `@gramio/session`'s `session()` that forces
|
|
93
|
-
* the storage key to include the calling bot's id. Equivalent to
|
|
94
|
-
* passing `getSessionKey: (ctx) => bot-${id}:${senderId}` explicitly,
|
|
95
|
-
* but baked in so no consumer can forget.
|
|
96
|
-
*
|
|
97
|
-
* Returns a plain `@gramio/session` plugin — same shape, same derives,
|
|
98
|
-
* fully interchangeable in `.extend(userSession)` chains and plugin
|
|
99
|
-
* options like `accessControl({ session: userSession, … })`.
|
|
100
|
-
*
|
|
101
|
-
* `getSessionKey` can still be overridden for advanced cases (e.g. a
|
|
102
|
-
* per-chat session in groups); the override is wrapped so it still
|
|
103
|
-
* receives the `bot-<id>:` prefix. Don't override unless you know what
|
|
104
|
-
* you're doing — every plugin in this package expects the
|
|
105
|
-
* `bot-<id>:<userId>` shape for cross-user storage reads.
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* import { redisStorage } from '@gramio/storage-redis'
|
|
109
|
-
* import { botSession } from '@adriangalilea/utils/bot/kit'
|
|
110
|
-
*
|
|
111
|
-
* const storage = redisStorage() // shared across N bots, safe
|
|
112
|
-
*
|
|
113
|
-
* const userSession = botSession({
|
|
114
|
-
* storage,
|
|
115
|
-
* key: 'session',
|
|
116
|
-
* initial: () => ({}),
|
|
117
|
-
* })
|
|
118
|
-
*
|
|
119
|
-
* bot.extend(userSession)
|
|
120
|
-
*/
|
|
121
|
-
export declare const botSession: <Data = unknown, Key extends string = "session", Lazy extends boolean = false>(opts: SessionOptions<Data, Key, Lazy>) => ReturnType<typeof session<Data, Key, Lazy>>;
|
|
122
|
-
/**
|
|
123
|
-
* Wraps any `@gramio/storage` adapter so every key gets a fixed
|
|
124
|
-
* `${prefix}` prepended.
|
|
125
|
-
*
|
|
126
|
-
* **You almost never need this.** The library's plugins are isolated
|
|
127
|
-
* by bot id automatically when you wire `botSession` (see above) —
|
|
128
|
-
* `prefixStorage` exists for edge cases:
|
|
129
|
-
*
|
|
130
|
-
* - Sharing a Redis instance with a NON-bot system, and you want a
|
|
131
|
-
* top-level prefix on top of the bot-id namespace.
|
|
132
|
-
* - Migrating from an older deployment that used a custom prefix.
|
|
133
|
-
*
|
|
134
|
-
* In those cases the wrapper composes cleanly with `botSession`'s
|
|
135
|
-
* internal prefix: final keys look like `${prefix}bot-<id>:<userId>`.
|
|
136
|
-
*/
|
|
137
|
-
export declare const prefixStorage: (storage: Storage, prefix: string) => Storage;
|
|
138
88
|
export type { PluginType };
|
|
139
89
|
//# sourceMappingURL=kit.d.ts.map
|
package/dist/bot/kit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kit.d.ts","sourceRoot":"","sources":["../../src/bot/kit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"kit.d.ts","sourceRoot":"","sources":["../../src/bot/kit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,KAAK,MAAM,EAAU,MAAM,QAAQ,CAAC;AAI7C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAIzD,MAAM,MAAM,oBAAoB,GAAG;IAClC,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxC,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC;IACtC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,aAAa,GACzB,KAAK,MAAM,EACX,OAAM,oBAAyB,KAC7B,OAAO,CAAC,IAAI,CAmEd,CAAC;AAIF,MAAM,MAAM,mBAAmB,GAAG;IACjC,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,OAAM,mBAAwB;;;;;;;;MAqB1D,CAAC;AAIF,YAAY,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/bot/kit.js
CHANGED
|
@@ -1,48 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Foundational helpers every bot wants.
|
|
3
|
-
*
|
|
4
|
-
* `gracefulStart(bot, opts?)` — wires SIGINT/SIGTERM to bot.stop(),
|
|
5
|
-
* runs an optional shutdown hook, force-kills if it hangs. DMs the
|
|
6
|
-
* admin on start/stop by default (`@<bot> started.` /
|
|
7
|
-
* `@<bot> shutting down.`) when `KEV.TELEGRAM_ADMIN_ID` is set —
|
|
8
|
-
* pass `notifyAdmin: false` to disable.
|
|
9
|
-
*
|
|
10
|
-
* `adminContext({ adminId? })` — reads admin Telegram id from KEV
|
|
11
|
-
* (`TELEGRAM_ADMIN_ID`) with optional hardcoded fallback. Decorates
|
|
12
|
-
* every context with `ctx.adminId` (number) and `ctx.isAdmin`
|
|
13
|
-
* (boolean). Throws at startup if neither source provides an id.
|
|
14
|
-
*
|
|
15
|
-
* `botSession(opts)` — drop-in replacement for `@gramio/session`'s
|
|
16
|
-
* `session()` that AUTOMATICALLY namespaces every key with the
|
|
17
|
-
* calling bot's id (parsed from `ctx.bot.options.token`). When
|
|
18
|
-
* several bots share one Redis instance, each writes to a disjoint
|
|
19
|
-
* keyspace (`bot-<id>:<senderId>`) with no manual wiring. Every
|
|
20
|
-
* plugin in this package (`accessControl`, `botMenu`,
|
|
21
|
-
* `llmHistory`, …) derives the SAME prefix from `ctx.bot` for its
|
|
22
|
-
* own storage access, so the whole package stays isolated by
|
|
23
|
-
* construction. Use this instead of `session()` — full stop.
|
|
24
|
-
*
|
|
25
|
-
* Peer deps: `gramio`, `@gramio/session`, `@gramio/storage`.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* import { Bot } from 'gramio'
|
|
29
|
-
* import { redisStorage } from '@gramio/storage-redis'
|
|
30
|
-
* import { adminContext, gracefulStart, botSession } from '@adriangalilea/utils/bot/kit'
|
|
31
|
-
*
|
|
32
|
-
* const storage = redisStorage() // share across bots, safe
|
|
33
|
-
* const userSession = botSession({ storage, initial: () => ({}) })
|
|
34
|
-
*
|
|
35
|
-
* const bot = new Bot(process.env.BOT_TOKEN!)
|
|
36
|
-
* .extend(adminContext({ adminId: 190202471 })) // KEV wins, 190… is fallback
|
|
37
|
-
* .extend(userSession)
|
|
38
|
-
* .command('whoami', (ctx) => ctx.send(`admin? ${ctx.isAdmin}`))
|
|
39
|
-
*
|
|
40
|
-
* await gracefulStart(bot, { onShutdown: () => db.end() })
|
|
41
|
-
*/
|
|
42
|
-
import { session } from "@gramio/session";
|
|
43
1
|
import { Plugin } from "gramio";
|
|
44
2
|
import { kev } from "../platform/kev.js";
|
|
45
|
-
import {
|
|
3
|
+
import { notifyAdmins } from "./notify.js";
|
|
4
|
+
export { botSession, prefixStorage } from "./session.js";
|
|
46
5
|
export const gracefulStart = async (bot, opts = {}) => {
|
|
47
6
|
const log = opts.log === false ? () => { } : (opts.log ?? ((m) => console.log(m)));
|
|
48
7
|
const forceMs = opts.forceExitAfterMs ?? 10_000;
|
|
@@ -93,28 +52,8 @@ export const gracefulStart = async (bot, opts = {}) => {
|
|
|
93
52
|
"Pass a number explicitly, set the env var, or remove notifyAdmin.");
|
|
94
53
|
}
|
|
95
54
|
if (adminId) {
|
|
96
|
-
bot.onStart(
|
|
97
|
-
|
|
98
|
-
await bot.api.sendMessage({
|
|
99
|
-
chat_id: adminId,
|
|
100
|
-
text: `@${info.username} started.`,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
catch (e) {
|
|
104
|
-
console.error("[bot] notifyAdmin (start) failed", e);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
bot.onStop(async ({ info }) => {
|
|
108
|
-
try {
|
|
109
|
-
await bot.api.sendMessage({
|
|
110
|
-
chat_id: adminId,
|
|
111
|
-
text: `@${info.username} shutting down.`,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
catch (e) {
|
|
115
|
-
console.error("[bot] notifyAdmin (stop) failed", e);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
55
|
+
bot.onStart(({ info }) => notifyAdmins(bot, [adminId], `@${info.username} started.`));
|
|
56
|
+
bot.onStop(({ info }) => notifyAdmins(bot, [adminId], `@${info.username} shutting down.`));
|
|
118
57
|
}
|
|
119
58
|
await bot.start();
|
|
120
59
|
};
|
|
@@ -136,81 +75,4 @@ export const adminContext = (opts = {}) => {
|
|
|
136
75
|
isAdmin: "senderId" in ctx && ctx.senderId === adminId,
|
|
137
76
|
}));
|
|
138
77
|
};
|
|
139
|
-
// ─── botSession ────────────────────────────────────────────────────
|
|
140
|
-
/**
|
|
141
|
-
* Drop-in replacement for `@gramio/session`'s `session()` that forces
|
|
142
|
-
* the storage key to include the calling bot's id. Equivalent to
|
|
143
|
-
* passing `getSessionKey: (ctx) => bot-${id}:${senderId}` explicitly,
|
|
144
|
-
* but baked in so no consumer can forget.
|
|
145
|
-
*
|
|
146
|
-
* Returns a plain `@gramio/session` plugin — same shape, same derives,
|
|
147
|
-
* fully interchangeable in `.extend(userSession)` chains and plugin
|
|
148
|
-
* options like `accessControl({ session: userSession, … })`.
|
|
149
|
-
*
|
|
150
|
-
* `getSessionKey` can still be overridden for advanced cases (e.g. a
|
|
151
|
-
* per-chat session in groups); the override is wrapped so it still
|
|
152
|
-
* receives the `bot-<id>:` prefix. Don't override unless you know what
|
|
153
|
-
* you're doing — every plugin in this package expects the
|
|
154
|
-
* `bot-<id>:<userId>` shape for cross-user storage reads.
|
|
155
|
-
*
|
|
156
|
-
* @example
|
|
157
|
-
* import { redisStorage } from '@gramio/storage-redis'
|
|
158
|
-
* import { botSession } from '@adriangalilea/utils/bot/kit'
|
|
159
|
-
*
|
|
160
|
-
* const storage = redisStorage() // shared across N bots, safe
|
|
161
|
-
*
|
|
162
|
-
* const userSession = botSession({
|
|
163
|
-
* storage,
|
|
164
|
-
* key: 'session',
|
|
165
|
-
* initial: () => ({}),
|
|
166
|
-
* })
|
|
167
|
-
*
|
|
168
|
-
* bot.extend(userSession)
|
|
169
|
-
*/
|
|
170
|
-
export const botSession = (opts) => {
|
|
171
|
-
const userKeyer = opts.getSessionKey;
|
|
172
|
-
return session({
|
|
173
|
-
...opts,
|
|
174
|
-
// The session plugin types `getSessionKey` against `ContextType<BotLike, Events>`
|
|
175
|
-
// — too narrow for our generic wrapper. We only need `bot.info.id` +
|
|
176
|
-
// `senderId`, both of which gramio guarantees on every event the session
|
|
177
|
-
// plugin processes (bot.info is populated by bot.start / bot.init before
|
|
178
|
-
// any handler fires). Structural cast at the boundary keeps the public
|
|
179
|
-
// API generic-friendly without forcing consumers to specify Event unions.
|
|
180
|
-
getSessionKey: (async (ctxRaw) => {
|
|
181
|
-
const ctx = ctxRaw;
|
|
182
|
-
const prefix = `bot-${botId(ctx)}:`;
|
|
183
|
-
if (userKeyer) {
|
|
184
|
-
const inner = await userKeyer(ctx);
|
|
185
|
-
return `${prefix}${inner}`;
|
|
186
|
-
}
|
|
187
|
-
return `${prefix}${ctx.senderId ?? ""}`;
|
|
188
|
-
}),
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
// ─── prefixStorage (escape hatch) ──────────────────────────────────
|
|
192
|
-
/**
|
|
193
|
-
* Wraps any `@gramio/storage` adapter so every key gets a fixed
|
|
194
|
-
* `${prefix}` prepended.
|
|
195
|
-
*
|
|
196
|
-
* **You almost never need this.** The library's plugins are isolated
|
|
197
|
-
* by bot id automatically when you wire `botSession` (see above) —
|
|
198
|
-
* `prefixStorage` exists for edge cases:
|
|
199
|
-
*
|
|
200
|
-
* - Sharing a Redis instance with a NON-bot system, and you want a
|
|
201
|
-
* top-level prefix on top of the bot-id namespace.
|
|
202
|
-
* - Migrating from an older deployment that used a custom prefix.
|
|
203
|
-
*
|
|
204
|
-
* In those cases the wrapper composes cleanly with `botSession`'s
|
|
205
|
-
* internal prefix: final keys look like `${prefix}bot-<id>:<userId>`.
|
|
206
|
-
*/
|
|
207
|
-
export const prefixStorage = (storage, prefix) => {
|
|
208
|
-
const k = (key) => `${prefix}${String(key)}`;
|
|
209
|
-
return {
|
|
210
|
-
get: (key) => storage.get(k(key)),
|
|
211
|
-
has: (key) => storage.has(k(key)),
|
|
212
|
-
set: (key, value) => storage.set(k(key), value),
|
|
213
|
-
delete: (key) => storage.delete(k(key)),
|
|
214
|
-
};
|
|
215
|
-
};
|
|
216
78
|
//# sourceMappingURL=kit.js.map
|
package/dist/bot/kit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kit.js","sourceRoot":"","sources":["../../src/bot/kit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"kit.js","sourceRoot":"","sources":["../../src/bot/kit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin notifications — best-effort DMs to the bot's operator(s).
|
|
3
|
+
* Worker-safe: no env, no process; the caller passes the admin ids
|
|
4
|
+
* (from wherever its runtime keeps them: KEV on Node, D1/env on a
|
|
5
|
+
* Worker).
|
|
6
|
+
*
|
|
7
|
+
* Everything here is FIRE-AND-FORGET BY CONTRACT: a notification must
|
|
8
|
+
* never take the bot down, so failures are logged (console.error —
|
|
9
|
+
* visible in a Worker tail and a Node terminal alike) and swallowed.
|
|
10
|
+
*
|
|
11
|
+
* `notifyAdmins(bot, adminIds, text, extra?)` — DM each admin.
|
|
12
|
+
* `alertAdminError(bot, adminIds, label, error, throttle?)` — DM a
|
|
13
|
+
* truncated `🚨 label\nName: message`, rate-limited through a
|
|
14
|
+
* caller-owned throttle so a failure storm can't flood anyone.
|
|
15
|
+
* `alertThrottle(ms?)` — make that throttle (per-process on Node,
|
|
16
|
+
* per-isolate on a Worker; own one per subsystem if you want
|
|
17
|
+
* independent budgets).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* import { alertAdminError, alertThrottle, notifyAdmins } from '@adriangalilea/utils/bot/notify'
|
|
21
|
+
*
|
|
22
|
+
* const errorAlerts = alertThrottle() // 60s budget, module-level
|
|
23
|
+
* bot.onError(({ kind, error }) => void alertAdminError(bot, adminIds, `error [${kind}]`, error, errorAlerts))
|
|
24
|
+
* await notifyAdmins(bot, adminIds, `@${bot.info.username} deployed.`)
|
|
25
|
+
*/
|
|
26
|
+
/** The one API surface a notification needs — satisfied by any gramio `Bot`. */
|
|
27
|
+
export type NotifyBot = {
|
|
28
|
+
api: {
|
|
29
|
+
sendMessage: (params: {
|
|
30
|
+
chat_id: number;
|
|
31
|
+
text: string;
|
|
32
|
+
}) => Promise<unknown>;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* DM `text` to every admin id, best-effort: each failure is logged and
|
|
37
|
+
* swallowed (one unreachable admin must not block the rest, and a
|
|
38
|
+
* notification must never throw into the calling flow). `extra` merges
|
|
39
|
+
* into the sendMessage params (reply_markup, link_preview_options, …).
|
|
40
|
+
*/
|
|
41
|
+
export declare function notifyAdmins(bot: NotifyBot, adminIds: readonly number[], text: string, extra?: Record<string, unknown>): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Rate-limit state for {@link alertAdminError}, owned by the CALLER so
|
|
44
|
+
* scope is explicit (per-process on Node, per-isolate on a Worker) and
|
|
45
|
+
* independent subsystems can hold independent budgets.
|
|
46
|
+
*/
|
|
47
|
+
export type AlertThrottle = {
|
|
48
|
+
ms: number;
|
|
49
|
+
lastAt: number;
|
|
50
|
+
};
|
|
51
|
+
/** A fresh throttle: at most one alert per `ms` (default 60s). */
|
|
52
|
+
export declare const alertThrottle: (ms?: number) => AlertThrottle;
|
|
53
|
+
/**
|
|
54
|
+
* DM the admins a truncated error (`🚨 label\nName: message`), gated by
|
|
55
|
+
* `throttle` so a failure storm sends one alert per window, not one per
|
|
56
|
+
* failure. No throttle = every call sends.
|
|
57
|
+
*/
|
|
58
|
+
export declare function alertAdminError(bot: NotifyBot, adminIds: readonly number[], label: string, error: unknown, throttle?: AlertThrottle): Promise<void>;
|
|
59
|
+
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/bot/notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG;IACvB,GAAG,EAAE;QACJ,WAAW,EAAE,CAAC,MAAM,EAAE;YACrB,OAAO,EAAE,MAAM,CAAC;YAChB,IAAI,EAAE,MAAM,CAAC;SACb,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KACvB,CAAC;CACF,CAAC;AAKF;;;;;GAKG;AACH,wBAAsB,YAAY,CACjC,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,kEAAkE;AAClE,eAAO,MAAM,aAAa,GAAI,WAAW,KAAG,aAG1C,CAAC;AAEH;;;;GAIG;AACH,wBAAsB,eAAe,CACpC,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CASf"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin notifications — best-effort DMs to the bot's operator(s).
|
|
3
|
+
* Worker-safe: no env, no process; the caller passes the admin ids
|
|
4
|
+
* (from wherever its runtime keeps them: KEV on Node, D1/env on a
|
|
5
|
+
* Worker).
|
|
6
|
+
*
|
|
7
|
+
* Everything here is FIRE-AND-FORGET BY CONTRACT: a notification must
|
|
8
|
+
* never take the bot down, so failures are logged (console.error —
|
|
9
|
+
* visible in a Worker tail and a Node terminal alike) and swallowed.
|
|
10
|
+
*
|
|
11
|
+
* `notifyAdmins(bot, adminIds, text, extra?)` — DM each admin.
|
|
12
|
+
* `alertAdminError(bot, adminIds, label, error, throttle?)` — DM a
|
|
13
|
+
* truncated `🚨 label\nName: message`, rate-limited through a
|
|
14
|
+
* caller-owned throttle so a failure storm can't flood anyone.
|
|
15
|
+
* `alertThrottle(ms?)` — make that throttle (per-process on Node,
|
|
16
|
+
* per-isolate on a Worker; own one per subsystem if you want
|
|
17
|
+
* independent budgets).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* import { alertAdminError, alertThrottle, notifyAdmins } from '@adriangalilea/utils/bot/notify'
|
|
21
|
+
*
|
|
22
|
+
* const errorAlerts = alertThrottle() // 60s budget, module-level
|
|
23
|
+
* bot.onError(({ kind, error }) => void alertAdminError(bot, adminIds, `error [${kind}]`, error, errorAlerts))
|
|
24
|
+
* await notifyAdmins(bot, adminIds, `@${bot.info.username} deployed.`)
|
|
25
|
+
*/
|
|
26
|
+
/** Telegram's hard per-message limit; longer texts are truncated, never split. */
|
|
27
|
+
const MAX_CHARS = 4096;
|
|
28
|
+
/**
|
|
29
|
+
* DM `text` to every admin id, best-effort: each failure is logged and
|
|
30
|
+
* swallowed (one unreachable admin must not block the rest, and a
|
|
31
|
+
* notification must never throw into the calling flow). `extra` merges
|
|
32
|
+
* into the sendMessage params (reply_markup, link_preview_options, …).
|
|
33
|
+
*/
|
|
34
|
+
export async function notifyAdmins(bot, adminIds, text, extra) {
|
|
35
|
+
const body = text.slice(0, MAX_CHARS);
|
|
36
|
+
await Promise.all(adminIds
|
|
37
|
+
.filter((id) => id > 0)
|
|
38
|
+
.map((id) => bot.api
|
|
39
|
+
.sendMessage({ ...extra, chat_id: id, text: body })
|
|
40
|
+
.catch((e) => console.error(`[bot/notify] admin ${id} failed:`, e))));
|
|
41
|
+
}
|
|
42
|
+
/** A fresh throttle: at most one alert per `ms` (default 60s). */
|
|
43
|
+
export const alertThrottle = (ms = 60_000) => ({
|
|
44
|
+
ms,
|
|
45
|
+
lastAt: 0,
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* DM the admins a truncated error (`🚨 label\nName: message`), gated by
|
|
49
|
+
* `throttle` so a failure storm sends one alert per window, not one per
|
|
50
|
+
* failure. No throttle = every call sends.
|
|
51
|
+
*/
|
|
52
|
+
export async function alertAdminError(bot, adminIds, label, error, throttle) {
|
|
53
|
+
if (throttle) {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
if (now - throttle.lastAt < throttle.ms)
|
|
56
|
+
return;
|
|
57
|
+
throttle.lastAt = now;
|
|
58
|
+
}
|
|
59
|
+
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
60
|
+
await notifyAdmins(bot, adminIds, `🚨 ${label}\n${message}`);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/bot/notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAYH,kFAAkF;AAClF,MAAM,SAAS,GAAG,IAAI,CAAC;AAEvB;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,GAAc,EACd,QAA2B,EAC3B,IAAY,EACZ,KAA+B;IAE/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACtC,MAAM,OAAO,CAAC,GAAG,CAChB,QAAQ;SACN,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;SACtB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CACX,GAAG,CAAC,GAAG;SACL,WAAW,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SAClD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CACpE,CACF,CAAC;AACH,CAAC;AASD,kEAAkE;AAClE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,GAAG,MAAM,EAAiB,EAAE,CAAC,CAAC;IAC7D,EAAE;IACF,MAAM,EAAE,CAAC;CACT,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,GAAc,EACd,QAA2B,EAC3B,KAAa,EACb,KAAc,EACd,QAAwB;IAExB,IAAI,QAAQ,EAAE,CAAC;QACd,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,EAAE;YAAE,OAAO;QAChD,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;IACvB,CAAC;IACD,MAAM,OAAO,GACZ,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5E,MAAM,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,KAAK,KAAK,OAAO,EAAE,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -29,13 +29,25 @@ import type { BotCallbackCtx } from "../ctx.js";
|
|
|
29
29
|
import type { PaymentsStores } from "./stores.js";
|
|
30
30
|
import type { BotPaymentsConfig, PaymentsSession } from "./types.js";
|
|
31
31
|
/** User tap on a charge entry — opens confirmation overlay then admin DM. */
|
|
32
|
-
export declare const refundRequestCb: import("gramio").CallbackData<
|
|
32
|
+
export declare const refundRequestCb: import("gramio").CallbackData<{
|
|
33
|
+
cid: string;
|
|
34
|
+
} & {}, {
|
|
35
|
+
cid: string;
|
|
36
|
+
} & {}>;
|
|
33
37
|
/** Admin tap on the DM notification — approve. */
|
|
34
|
-
export declare const refundApproveCb: import("gramio").CallbackData<
|
|
38
|
+
export declare const refundApproveCb: import("gramio").CallbackData<{
|
|
39
|
+
cid: string;
|
|
40
|
+
} & {}, {
|
|
41
|
+
cid: string;
|
|
42
|
+
} & {}>;
|
|
35
43
|
/** Admin tap on the DM notification — deny. */
|
|
36
|
-
export declare const refundDenyCb: import("gramio").CallbackData<
|
|
44
|
+
export declare const refundDenyCb: import("gramio").CallbackData<{
|
|
45
|
+
cid: string;
|
|
46
|
+
} & {}, {
|
|
47
|
+
cid: string;
|
|
48
|
+
} & {}>;
|
|
37
49
|
/** Admin tap on the DM notification — close (no action). */
|
|
38
|
-
export declare const refundCloseCb: import("gramio").CallbackData<
|
|
50
|
+
export declare const refundCloseCb: import("gramio").CallbackData<{} & {}, {} & {}>;
|
|
39
51
|
/** Strict bot.api surface this module exercises. */
|
|
40
52
|
type RefundBotApi = {
|
|
41
53
|
refundStarPayment: (params: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refund.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/refund.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAM/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACX,iBAAiB,EAEjB,eAAe,EACf,MAAM,YAAY,CAAC;AASpB,6EAA6E;AAC7E,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"refund.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/refund.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAM/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACX,iBAAiB,EAEjB,eAAe,EACf,MAAM,YAAY,CAAC;AASpB,6EAA6E;AAC7E,eAAO,MAAM,eAAe;;;;OAA+C,CAAC;AAC5E,kDAAkD;AAClD,eAAO,MAAM,eAAe;;;;OAA+C,CAAC;AAC5E,+CAA+C;AAC/C,eAAO,MAAM,YAAY;;;;OAA4C,CAAC;AACtE,4DAA4D;AAC5D,eAAO,MAAM,aAAa,iDAA8B,CAAC;AAIzD,oDAAoD;AACpD,KAAK,YAAY,GAAG;IACnB,iBAAiB,EAAE,CAAC,MAAM,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,0BAA0B,EAAE,MAAM,CAAC;KACnC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,wBAAwB,EAAE,CAAC,MAAM,EAAE;QAClC,OAAO,EAAE,MAAM,CAAC;QAChB,0BAA0B,EAAE,MAAM,CAAC;QACnC,WAAW,EAAE,OAAO,CAAC;KACrB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,WAAW,EAAE,CAAC,MAAM,EAAE;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,CAAC,EAAE,OAAO,CAAC;KACvB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB,CAAC;AAEF,KAAK,WAAW,GAAG;IAAE,GAAG,CAAC,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE;;;;;GAKG;AACH,KAAK,SAAS,GAAG,cAAc,CAAC,WAAW,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,YAAY,CAAC,GAAG;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CACjB,CAAC;AAqIF,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,GACpC,MAAM,qBAAqB,MAE3B,KAAK,SAAS,GAAG;IAAE,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAC7C,OAAO,CAAC,MAAM,GAAG,SAAS,CA8D5B,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACpC,MAAM,qBAAqB,MACrB,KAAK,SAAS,GAAG;IAAE,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAG,OAAO,CAAC,IAAI,CAiGpE,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,qBAAqB,MACrB,KAAK,SAAS,GAAG;IAAE,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAG,OAAO,CAAC,IAAI,CAyCpE,CAAC"}
|
|
@@ -29,9 +29,13 @@ import { type BotPaymentsConfig, type PaymentsSession, type WaiverRecord } from
|
|
|
29
29
|
* `pk` (productKey) follows the shape validated by `payload.ts` —
|
|
30
30
|
* `vip.N` / `credits.N` / `perks.X` — all ASCII-safe.
|
|
31
31
|
*/
|
|
32
|
-
export declare const waiverConsentCb: import("gramio").CallbackData<
|
|
32
|
+
export declare const waiverConsentCb: import("gramio").CallbackData<{
|
|
33
|
+
pk: string;
|
|
34
|
+
} & {}, {
|
|
35
|
+
pk: string;
|
|
36
|
+
} & {}>;
|
|
33
37
|
/** Fired when the user taps "❌ Cancelar" in the consent prompt. */
|
|
34
|
-
export declare const waiverCancelCb: import("gramio").CallbackData<
|
|
38
|
+
export declare const waiverCancelCb: import("gramio").CallbackData<{} & {}, {} & {}>;
|
|
35
39
|
/**
|
|
36
40
|
* Returns true when the stored consent matches the current waiver
|
|
37
41
|
* version — i.e. no re-consent needed before a purchase.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"waiver.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/waiver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAMxC,OAAO,EACN,KAAK,iBAAiB,EAGtB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,MAAM,YAAY,CAAC;AAMpB;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"waiver.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/waiver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAMxC,OAAO,EACN,KAAK,iBAAiB,EAGtB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,MAAM,YAAY,CAAC;AAMpB;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe;;;;OAA8C,CAAC;AAE3E,mEAAmE;AACnE,eAAO,MAAM,cAAc,iDAA+B,CAAC;AAI3D;;;GAGG;AACH,eAAO,MAAM,aAAa,GACzB,QAAQ,YAAY,GAAG,SAAS,EAChC,gBAAgB,MAAM,KACpB,OAGF,CAAC;AAiBF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAC1B,KAAK,iBAAiB,CAAC,MAAM,CAAC,EAC9B,YAAY,MAAM,EAClB,MAAM,MAAM,KACV,cA8BF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC7B,KAAK,iBAAiB,CAAC,MAAM,CAAC,EAC9B,MAAM,MAAM,KACV,MAWU,CAAC;AAId;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK;IAAE,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,eAAe,CAAA;KAAE,CAAA;CAAE,EAC3C,SAAS,MAAM,EACf,QAAQ,MAAM,KACZ,YAUF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,OAAO,EAChB,KAAK;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,EACrB,QAAQ,MAAM,EACd,SAAS,MAAM,EACf,QAAQ,MAAM,KACZ,OAAO,CAAC,YAAY,CAatB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bot-namespaced session storage — worker-safe (no env, no process, no
|
|
3
|
+
* signals; runs identically on Node and Cloudflare Workers).
|
|
4
|
+
*
|
|
5
|
+
* `botSession(opts)` — drop-in replacement for `@gramio/session`'s
|
|
6
|
+
* `session()` that AUTOMATICALLY namespaces every key with the
|
|
7
|
+
* calling bot's id (parsed from `ctx.bot.options.token`). When
|
|
8
|
+
* several bots share one storage instance, each writes to a
|
|
9
|
+
* disjoint keyspace (`bot-<id>:<senderId>`) with no manual wiring.
|
|
10
|
+
* Every plugin in this package (`accessControl`, `botMenu`,
|
|
11
|
+
* `llmHistory`, …) derives the SAME prefix from `ctx.bot` for its
|
|
12
|
+
* own storage access, so the whole package stays isolated by
|
|
13
|
+
* construction. Use this instead of `session()` — full stop.
|
|
14
|
+
*
|
|
15
|
+
* `prefixStorage(storage, prefix)` — escape hatch that prepends a
|
|
16
|
+
* fixed prefix to every key of any `@gramio/storage` adapter.
|
|
17
|
+
*
|
|
18
|
+
* Peer deps: `gramio`, `@gramio/session`, `@gramio/storage`.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { redisStorage } from '@gramio/storage-redis'
|
|
22
|
+
* import { botSession } from '@adriangalilea/utils/bot/session'
|
|
23
|
+
*
|
|
24
|
+
* const storage = redisStorage() // share across bots, safe
|
|
25
|
+
* const userSession = botSession({ storage, initial: () => ({}) })
|
|
26
|
+
* bot.extend(userSession)
|
|
27
|
+
*/
|
|
28
|
+
import { type SessionOptions, session } from "@gramio/session";
|
|
29
|
+
import type { Storage } from "@gramio/storage";
|
|
30
|
+
/**
|
|
31
|
+
* Drop-in replacement for `@gramio/session`'s `session()` that forces
|
|
32
|
+
* the storage key to include the calling bot's id. Equivalent to
|
|
33
|
+
* passing `getSessionKey: (ctx) => bot-${id}:${senderId}` explicitly,
|
|
34
|
+
* but baked in so no consumer can forget.
|
|
35
|
+
*
|
|
36
|
+
* Returns a plain `@gramio/session` plugin — same shape, same derives,
|
|
37
|
+
* fully interchangeable in `.extend(userSession)` chains and plugin
|
|
38
|
+
* options like `accessControl({ session: userSession, … })`.
|
|
39
|
+
*
|
|
40
|
+
* `getSessionKey` can still be overridden for advanced cases (e.g. a
|
|
41
|
+
* per-chat session in groups); the override is wrapped so it still
|
|
42
|
+
* receives the `bot-<id>:` prefix. Don't override unless you know what
|
|
43
|
+
* you're doing — every plugin in this package expects the
|
|
44
|
+
* `bot-<id>:<userId>` shape for cross-user storage reads.
|
|
45
|
+
*/
|
|
46
|
+
export declare const botSession: <Data = unknown, Key extends string = "session", Lazy extends boolean = false>(opts: SessionOptions<Data, Key, Lazy>) => ReturnType<typeof session<Data, Key, Lazy>>;
|
|
47
|
+
/**
|
|
48
|
+
* Wraps any `@gramio/storage` adapter so every key gets a fixed
|
|
49
|
+
* `${prefix}` prepended.
|
|
50
|
+
*
|
|
51
|
+
* **You almost never need this.** The library's plugins are isolated
|
|
52
|
+
* by bot id automatically when you wire `botSession` (see above) —
|
|
53
|
+
* `prefixStorage` exists for edge cases:
|
|
54
|
+
*
|
|
55
|
+
* - Sharing a Redis instance with a NON-bot system, and you want a
|
|
56
|
+
* top-level prefix on top of the bot-id namespace.
|
|
57
|
+
* - Migrating from an older deployment that used a custom prefix.
|
|
58
|
+
*
|
|
59
|
+
* In those cases the wrapper composes cleanly with `botSession`'s
|
|
60
|
+
* internal prefix: final keys look like `${prefix}bot-<id>:<userId>`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const prefixStorage: (storage: Storage, prefix: string) => Storage;
|
|
63
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/bot/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,KAAK,cAAc,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAK/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,GACtB,IAAI,GAAG,OAAO,EACd,GAAG,SAAS,MAAM,GAAG,SAAS,EAC9B,IAAI,SAAS,OAAO,GAAG,KAAK,EAE5B,MAAM,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,KACnC,UAAU,CAAC,OAAO,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CA0B5C,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO,EAAE,QAAQ,MAAM,KAAG,OAShE,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bot-namespaced session storage — worker-safe (no env, no process, no
|
|
3
|
+
* signals; runs identically on Node and Cloudflare Workers).
|
|
4
|
+
*
|
|
5
|
+
* `botSession(opts)` — drop-in replacement for `@gramio/session`'s
|
|
6
|
+
* `session()` that AUTOMATICALLY namespaces every key with the
|
|
7
|
+
* calling bot's id (parsed from `ctx.bot.options.token`). When
|
|
8
|
+
* several bots share one storage instance, each writes to a
|
|
9
|
+
* disjoint keyspace (`bot-<id>:<senderId>`) with no manual wiring.
|
|
10
|
+
* Every plugin in this package (`accessControl`, `botMenu`,
|
|
11
|
+
* `llmHistory`, …) derives the SAME prefix from `ctx.bot` for its
|
|
12
|
+
* own storage access, so the whole package stays isolated by
|
|
13
|
+
* construction. Use this instead of `session()` — full stop.
|
|
14
|
+
*
|
|
15
|
+
* `prefixStorage(storage, prefix)` — escape hatch that prepends a
|
|
16
|
+
* fixed prefix to every key of any `@gramio/storage` adapter.
|
|
17
|
+
*
|
|
18
|
+
* Peer deps: `gramio`, `@gramio/session`, `@gramio/storage`.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { redisStorage } from '@gramio/storage-redis'
|
|
22
|
+
* import { botSession } from '@adriangalilea/utils/bot/session'
|
|
23
|
+
*
|
|
24
|
+
* const storage = redisStorage() // share across bots, safe
|
|
25
|
+
* const userSession = botSession({ storage, initial: () => ({}) })
|
|
26
|
+
* bot.extend(userSession)
|
|
27
|
+
*/
|
|
28
|
+
import { session } from "@gramio/session";
|
|
29
|
+
import { botId } from "./keys.js";
|
|
30
|
+
// ─── botSession ────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Drop-in replacement for `@gramio/session`'s `session()` that forces
|
|
33
|
+
* the storage key to include the calling bot's id. Equivalent to
|
|
34
|
+
* passing `getSessionKey: (ctx) => bot-${id}:${senderId}` explicitly,
|
|
35
|
+
* but baked in so no consumer can forget.
|
|
36
|
+
*
|
|
37
|
+
* Returns a plain `@gramio/session` plugin — same shape, same derives,
|
|
38
|
+
* fully interchangeable in `.extend(userSession)` chains and plugin
|
|
39
|
+
* options like `accessControl({ session: userSession, … })`.
|
|
40
|
+
*
|
|
41
|
+
* `getSessionKey` can still be overridden for advanced cases (e.g. a
|
|
42
|
+
* per-chat session in groups); the override is wrapped so it still
|
|
43
|
+
* receives the `bot-<id>:` prefix. Don't override unless you know what
|
|
44
|
+
* you're doing — every plugin in this package expects the
|
|
45
|
+
* `bot-<id>:<userId>` shape for cross-user storage reads.
|
|
46
|
+
*/
|
|
47
|
+
export const botSession = (opts) => {
|
|
48
|
+
const userKeyer = opts.getSessionKey;
|
|
49
|
+
return session({
|
|
50
|
+
...opts,
|
|
51
|
+
// The session plugin types `getSessionKey` against `ContextType<BotLike, Events>`
|
|
52
|
+
// — too narrow for our generic wrapper. We only need `bot.info.id` +
|
|
53
|
+
// `senderId`, both of which gramio guarantees on every event the session
|
|
54
|
+
// plugin processes (bot.info is populated by bot.start / bot.init before
|
|
55
|
+
// any handler fires). Structural cast at the boundary keeps the public
|
|
56
|
+
// API generic-friendly without forcing consumers to specify Event unions.
|
|
57
|
+
getSessionKey: (async (ctxRaw) => {
|
|
58
|
+
const ctx = ctxRaw;
|
|
59
|
+
const prefix = `bot-${botId(ctx)}:`;
|
|
60
|
+
if (userKeyer) {
|
|
61
|
+
const inner = await userKeyer(ctx);
|
|
62
|
+
return `${prefix}${inner}`;
|
|
63
|
+
}
|
|
64
|
+
return `${prefix}${ctx.senderId ?? ""}`;
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
// ─── prefixStorage (escape hatch) ──────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Wraps any `@gramio/storage` adapter so every key gets a fixed
|
|
71
|
+
* `${prefix}` prepended.
|
|
72
|
+
*
|
|
73
|
+
* **You almost never need this.** The library's plugins are isolated
|
|
74
|
+
* by bot id automatically when you wire `botSession` (see above) —
|
|
75
|
+
* `prefixStorage` exists for edge cases:
|
|
76
|
+
*
|
|
77
|
+
* - Sharing a Redis instance with a NON-bot system, and you want a
|
|
78
|
+
* top-level prefix on top of the bot-id namespace.
|
|
79
|
+
* - Migrating from an older deployment that used a custom prefix.
|
|
80
|
+
*
|
|
81
|
+
* In those cases the wrapper composes cleanly with `botSession`'s
|
|
82
|
+
* internal prefix: final keys look like `${prefix}bot-<id>:<userId>`.
|
|
83
|
+
*/
|
|
84
|
+
export const prefixStorage = (storage, prefix) => {
|
|
85
|
+
const k = (key) => `${prefix}${String(key)}`;
|
|
86
|
+
return {
|
|
87
|
+
get: (key) => storage.get(k(key)),
|
|
88
|
+
has: (key) => storage.has(k(key)),
|
|
89
|
+
set: (key, value) => storage.set(k(key), value),
|
|
90
|
+
delete: (key) => storage.delete(k(key)),
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/bot/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAuB,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAElC,sEAAsE;AAEtE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAKzB,IAAqC,EACS,EAAE;IAChD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC;IAKrC,OAAO,OAAO,CAAkB;QAC/B,GAAG,IAAI;QACP,kFAAkF;QAClF,qEAAqE;QACrE,yEAAyE;QACzE,yEAAyE;QACzE,uEAAuE;QACvE,0EAA0E;QAC1E,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAChC,MAAM,GAAG,GAAG,MAAgB,CAAC;YAC7B,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YACpC,IAAI,SAAS,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,MACb,SACA,CAAC,GAAG,CAAC,CAAC;gBACP,OAAO,GAAG,MAAM,GAAG,KAAK,EAAE,CAAC;YAC5B,CAAC;YACD,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACzC,CAAC,CAAqD;KACtD,CAAgD,CAAC;AACnD,CAAC,CAAC;AAEF,sEAAsE;AAEtE;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAgB,EAAE,MAAc,EAAW,EAAE;IAC1E,MAAM,CAAC,GAAG,CAAC,GAA6B,EAAU,EAAE,CACnD,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAC3B,OAAO;QACN,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACjC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACjC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC;QAC/C,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACvC,CAAC;AACH,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adriangalilea/utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.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",
|
|
@@ -87,6 +87,14 @@
|
|
|
87
87
|
"./bot/callbacks": {
|
|
88
88
|
"types": "./dist/bot/callbacks.d.ts",
|
|
89
89
|
"default": "./dist/bot/callbacks.js"
|
|
90
|
+
},
|
|
91
|
+
"./bot/session": {
|
|
92
|
+
"types": "./dist/bot/session.d.ts",
|
|
93
|
+
"default": "./dist/bot/session.js"
|
|
94
|
+
},
|
|
95
|
+
"./bot/notify": {
|
|
96
|
+
"types": "./dist/bot/notify.d.ts",
|
|
97
|
+
"default": "./dist/bot/notify.js"
|
|
90
98
|
}
|
|
91
99
|
},
|
|
92
100
|
"files": [
|