@adriangalilea/utils 0.4.1 → 0.6.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 +99 -19
- package/dist/bot/access-control.d.ts +339 -0
- package/dist/bot/access-control.d.ts.map +1 -0
- package/dist/bot/access-control.js +516 -0
- package/dist/bot/access-control.js.map +1 -0
- package/dist/bot/index.d.ts +17 -0
- package/dist/bot/index.d.ts.map +1 -0
- package/dist/bot/index.js +17 -0
- package/dist/bot/index.js.map +1 -0
- package/dist/bot/kit.d.ts +50 -0
- package/dist/bot/kit.d.ts.map +1 -0
- package/dist/bot/kit.js +52 -0
- package/dist/bot/kit.js.map +1 -0
- package/dist/bot/llm-stream.d.ts +84 -0
- package/dist/bot/llm-stream.d.ts.map +1 -0
- package/dist/bot/llm-stream.js +201 -0
- package/dist/bot/llm-stream.js.map +1 -0
- package/dist/offensive.d.ts +89 -17
- package/dist/offensive.d.ts.map +1 -1
- package/dist/offensive.js +103 -17
- package/dist/offensive.js.map +1 -1
- package/package.json +47 -2
package/README.md
CHANGED
|
@@ -107,12 +107,14 @@ format.percentage(123.456) // "123%"
|
|
|
107
107
|
|
|
108
108
|
### Offensive Programming
|
|
109
109
|
|
|
110
|
-
Fail loud, fail fast.
|
|
110
|
+
Fail loud, fail fast. Zero dependencies, works in Node, Deno, Bun, and browsers.
|
|
111
|
+
|
|
112
|
+
Two kinds of errors, kept separate: **`Panic`** (bugs in us — crash the process) and **`SourcedError`** (boundary failures — handle per-source).
|
|
111
113
|
|
|
112
114
|
```typescript
|
|
113
|
-
import { assert, panic, must, unwrap, Panic } from '@adriangalilea/utils'
|
|
115
|
+
import { assert, panic, assertNever, must, unwrap, Panic, SourcedError, isSourcedError } from '@adriangalilea/utils'
|
|
114
116
|
|
|
115
|
-
// Assert invariants — narrows types
|
|
117
|
+
// Assert invariants — narrows types via `asserts condition`
|
|
116
118
|
assert(port > 0 && port < 65536, 'invalid port:', port)
|
|
117
119
|
|
|
118
120
|
// Impossible state
|
|
@@ -121,33 +123,66 @@ switch (state) {
|
|
|
121
123
|
default: panic('impossible state:', state)
|
|
122
124
|
}
|
|
123
125
|
|
|
126
|
+
// Exhaustiveness check — TS compile error if you miss a case
|
|
127
|
+
type Event = { kind: 'click' } | { kind: 'hover' } | { kind: 'scroll' }
|
|
128
|
+
function handle(e: Event) {
|
|
129
|
+
switch (e.kind) {
|
|
130
|
+
case 'click': return handleClick()
|
|
131
|
+
case 'hover': return handleHover()
|
|
132
|
+
// forgot 'scroll' → TS error: Argument of type '{ kind: "scroll" }' not assignable to 'never'
|
|
133
|
+
default: return assertNever(e)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Add a new variant to Event → every assertNever site lights up at compile time.
|
|
137
|
+
|
|
124
138
|
// Unwrap operations that shouldn't fail (sync + async)
|
|
125
139
|
const data = must(() => JSON.parse(staticJsonString))
|
|
126
140
|
const file = must(() => readFileSync(path))
|
|
127
141
|
const resp = await must(() => fetch(url))
|
|
128
142
|
|
|
129
|
-
// Unwrap nullable values —
|
|
130
|
-
// (assert needs two statements, unwrap does it inline)
|
|
143
|
+
// Unwrap nullable values — T | null | undefined → T in one expression
|
|
131
144
|
const user = unwrap(db.findUser(id), 'user not found:', id)
|
|
132
145
|
const el = unwrap(document.getElementById('app'))
|
|
146
|
+
```
|
|
133
147
|
|
|
134
|
-
|
|
135
|
-
// try { return readFileSync(path, 'utf-8') }
|
|
136
|
-
// catch (err) { check(err) }
|
|
137
|
-
// becomes:
|
|
138
|
-
return must(() => readFileSync(path, 'utf-8'))
|
|
148
|
+
#### Typed boundary errors — `SourcedError`
|
|
139
149
|
|
|
140
|
-
|
|
141
|
-
// In a server: let Panics crash, handle everything else
|
|
142
|
-
app.use((err, req, res, next) => {
|
|
143
|
-
if (err instanceof Panic) throw err // bug, re-throw, let it crash
|
|
144
|
-
res.status(500).json({ error: 'internal error' })
|
|
145
|
-
})
|
|
150
|
+
Every external system call should wear its source. When it fails, carry forensics:
|
|
146
151
|
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
```typescript
|
|
153
|
+
import { SourcedError, isSourcedError, Panic } from '@adriangalilea/utils'
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
return await stripe.charges.create({ customer, amount })
|
|
157
|
+
} catch (e) {
|
|
158
|
+
throw new SourcedError({
|
|
159
|
+
source: 'stripe',
|
|
160
|
+
operation: 'charge_customer',
|
|
161
|
+
message: e instanceof Error ? e.message : String(e),
|
|
162
|
+
status: (e as any)?.statusCode,
|
|
163
|
+
cause: e,
|
|
164
|
+
context: { customer, amount },
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// At catch boundaries — keep Panics and SourcedErrors separate:
|
|
169
|
+
try { await doWork() }
|
|
170
|
+
catch (e) {
|
|
171
|
+
if (e instanceof Panic) throw e // bug in us — crash
|
|
172
|
+
if (isSourcedError(e, 'stripe') && e.status === 402) {
|
|
173
|
+
// TS knows e.source === 'stripe' here (generic narrows)
|
|
174
|
+
return { error: 'card declined' }
|
|
175
|
+
}
|
|
176
|
+
if (isSourcedError(e)) {
|
|
177
|
+
logger.error(`[${e.source}:${e.operation}]`, e.toJSON()) // structured forensics
|
|
178
|
+
throw e
|
|
179
|
+
}
|
|
180
|
+
throw e // unknown — re-throw
|
|
181
|
+
}
|
|
149
182
|
```
|
|
150
183
|
|
|
184
|
+
Every `SourcedError` carries `source`, `operation`, `status`, `context`, and the original exception via `cause`. Call `.toJSON()` for serialization across process boundaries.
|
|
185
|
+
|
|
151
186
|
## Features
|
|
152
187
|
|
|
153
188
|
- **Logger**: Next.js-style colored console output with symbols
|
|
@@ -158,13 +193,14 @@ expect(() => assert(false, 'boom')).toThrow(Panic)
|
|
|
158
193
|
- Percentage and basis point utilities
|
|
159
194
|
- Fiat and stablecoin detection
|
|
160
195
|
- **Format**: Number and currency formatting with compact notation
|
|
161
|
-
- **Offensive Programming**: assert, panic, must, unwrap
|
|
196
|
+
- **Offensive Programming**: assert, panic, assertNever, must, unwrap (throw `Panic`) + SourcedError for typed boundary failures
|
|
162
197
|
- **File Operations**: Read, write with automatic path resolution
|
|
163
198
|
- **Directory Operations**: Create, list, walk directories
|
|
164
199
|
- **KEV**: Redis-style environment variable management with monorepo support
|
|
165
200
|
- **XDG**: XDG Base Directory paths — reads env vars set by [xdg-dirs](https://github.com/adriangalilea/xdg-dirs), falls back to spec defaults
|
|
166
201
|
- **Unseen**: Persistent dedup filter — "what's new since last time?" for cron/monitoring workflows
|
|
167
202
|
- **Project Discovery**: Find project/monorepo roots, detect JS/TS projects
|
|
203
|
+
- **Bot plugins (GramIO)**: `kit` (graceful shutdown + admin context), `access-control` (gate + approve/deny menu, backed by sessions), `llm-stream` (streaming LLM markdown to Telegram with graceful degradation)
|
|
168
204
|
|
|
169
205
|
### XDG Base Directories
|
|
170
206
|
|
|
@@ -214,6 +250,50 @@ newMessages = [{ id: '2', from: 'bob', text: 'hey' }]
|
|
|
214
250
|
|
|
215
251
|
Saves state to: `$XDG_STATE_HOME/unseen/{name}.json`
|
|
216
252
|
|
|
253
|
+
### Telegram bot plugins (GramIO)
|
|
254
|
+
|
|
255
|
+
Plugins for personal Telegram bots built on [GramIO](https://gramio.dev). Each plugin lives at its own subpath; peer deps (`gramio`, `@gramio/storage`, `@gramio/session`, `@gramio/format`, `marked`) are **all optional** — install only what you import.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
pnpm add @adriangalilea/utils gramio @gramio/storage @gramio/session
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
| Subpath | What it does |
|
|
262
|
+
|---|---|
|
|
263
|
+
| `@adriangalilea/utils/bot/kit` | `gracefulStart(bot)` — SIGINT/SIGTERM → `bot.stop()` → exit; force-kills if shutdown hangs.<br>`adminContext({ adminId? })` — reads `TELEGRAM_ADMIN_ID` from `kev` (with optional hardcoded fallback), decorates `ctx.adminId` + `ctx.isAdmin`. |
|
|
264
|
+
| `@adriangalilea/utils/bot/access-control` | Personal-bot ACL — gates non-admin/non-default users; admin gets DM with `[✅ Aprobar][❌ Denegar]` on first attempt; `/access` opens a persistent menu (revoke / reapprove / list pending). Backed by `@gramio/session` per-user + a small index. |
|
|
265
|
+
| `@adriangalilea/utils/bot/llm-stream` | `ctx.startStream()` for LLM token streams. Debounced `editMessageText`, splits at 4000 chars on paragraph/line/word boundary, parses Markdown locally so malformed mid-stream markup degrades to plain text instead of failing. |
|
|
266
|
+
|
|
267
|
+
Standard wiring:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { Bot } from 'gramio'
|
|
271
|
+
import { redisStorage } from '@gramio/storage-redis'
|
|
272
|
+
import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
|
|
273
|
+
import { accessControl } from '@adriangalilea/utils/bot/access-control'
|
|
274
|
+
import { llmStream } from '@adriangalilea/utils/bot/llm-stream'
|
|
275
|
+
|
|
276
|
+
const storage = redisStorage() // ONE instance, shared
|
|
277
|
+
|
|
278
|
+
const bot = new Bot(process.env.BOT_TOKEN!)
|
|
279
|
+
.extend(adminContext({ adminId: 190202471 })) // KEV.TELEGRAM_ADMIN_ID overrides
|
|
280
|
+
.extend(accessControl({ storage, defaults: [] })) // gate; depends on adminContext
|
|
281
|
+
.extend(llmStream())
|
|
282
|
+
.command('chat', async (ctx) => {
|
|
283
|
+
const stream = ctx.startStream()
|
|
284
|
+
for await (const chunk of yourLLM()) await stream.append(chunk.text)
|
|
285
|
+
await stream.end()
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
await gracefulStart(bot)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Inside handlers, `ctx.access` is a typed discriminated union — `{ allowed: true, source: 'admin' | 'default' | 'store', record? }` or `{ allowed: false, reason }`. `ctx.adminId` and `ctx.isAdmin` are available on every event from `adminContext`.
|
|
292
|
+
|
|
293
|
+
For tests/demos without a second Telegram account, `simulateAccessRequest(bot, storage, adminId, fakeUser, msg)` injects a synthetic pending request so admin can exercise the approve/deny flow.
|
|
294
|
+
|
|
295
|
+
See `src/bot/CLAUDE.md` for storage layout, design decisions, and gotchas.
|
|
296
|
+
|
|
217
297
|
## Release
|
|
218
298
|
|
|
219
299
|
Bump version in `package.json` (and `jsr.json`), push to `main`. CI handles everything:
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access control for personal GramIO bots — a one-stop guard +
|
|
3
|
+
* approve/deny + revocable allow-list with an inline admin menu.
|
|
4
|
+
*
|
|
5
|
+
* stranger DMs your bot
|
|
6
|
+
* │
|
|
7
|
+
* ▼
|
|
8
|
+
* ┌──── plugin gate (this file) ────────────────────┐
|
|
9
|
+
* │ ctx.from.id ∈ admin / defaults / approved? │
|
|
10
|
+
* │ yes → next() │
|
|
11
|
+
* │ no → drop + notify admin (rate-limited) │
|
|
12
|
+
* └─────────────────────────────────────────────────┘
|
|
13
|
+
* │
|
|
14
|
+
* admin gets DM with [✅ Aprobar] [❌ Denegar]
|
|
15
|
+
* │
|
|
16
|
+
* admin taps
|
|
17
|
+
* │
|
|
18
|
+
* stranger's session updated · stranger gets DM
|
|
19
|
+
*
|
|
20
|
+
* **Storage layout.** Per-user state lives in its own key, written
|
|
21
|
+
* through `@gramio/session` so the gate read on the hot path costs
|
|
22
|
+
* nothing extra (session is loaded for the user already). A single
|
|
23
|
+
* tiny index key keeps track of who's pending / approved / denied so
|
|
24
|
+
* the `/access` admin menu can list without scanning the whole DB.
|
|
25
|
+
*
|
|
26
|
+
* storage:
|
|
27
|
+
* access:<userId> → AccessRecord (the user's session)
|
|
28
|
+
* ac:index → { pending, approved, denied }
|
|
29
|
+
*
|
|
30
|
+
* **Cross-user mutations.** When you tap `[✅ Aprobar]` on Pepe's
|
|
31
|
+
* notification, ctx is *yours* (the admin), so `ctx.access` is your
|
|
32
|
+
* own record. To mutate Pepe's record we hit the storage at the same
|
|
33
|
+
* key format we registered the session with (`access:<id>`) and
|
|
34
|
+
* update the index. This isn't a hack — it's our own module
|
|
35
|
+
* coordinating with itself.
|
|
36
|
+
*
|
|
37
|
+
* **Composes with `adminContext`** (kit.ts) — that plugin must be
|
|
38
|
+
* extended first or `bot.start()` throws. Inside this plugin,
|
|
39
|
+
* `ctx.adminId` and `ctx.isAdmin` are typed.
|
|
40
|
+
*
|
|
41
|
+
* Peer deps: `gramio`, `@gramio/storage`, `@gramio/session`.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* import { Bot } from 'gramio'
|
|
45
|
+
* import { redisStorage } from '@gramio/storage-redis'
|
|
46
|
+
* import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
|
|
47
|
+
* import { accessControl } from '@adriangalilea/utils/bot/access-control'
|
|
48
|
+
*
|
|
49
|
+
* const storage = redisStorage()
|
|
50
|
+
*
|
|
51
|
+
* const bot = new Bot(process.env.BOT_TOKEN!)
|
|
52
|
+
* .extend(adminContext({ adminId: 190202471 }))
|
|
53
|
+
* .extend(accessControl({ storage, defaults: [1158734055] }))
|
|
54
|
+
* .command('start', (ctx) => ctx.send(`hola, source=${ctx.access.source}`))
|
|
55
|
+
*
|
|
56
|
+
* await gracefulStart(bot)
|
|
57
|
+
*/
|
|
58
|
+
import { type AnyBot, type DeriveDefinitions, Plugin } from 'gramio';
|
|
59
|
+
import { type Storage } from '@gramio/storage';
|
|
60
|
+
export type AccessStatus = 'unknown' | 'pending' | 'approved' | 'denied';
|
|
61
|
+
export type AccessUser = {
|
|
62
|
+
id: number;
|
|
63
|
+
firstName?: string;
|
|
64
|
+
lastName?: string;
|
|
65
|
+
username?: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The shape persisted per-user via session at `access:<userId>`.
|
|
69
|
+
* `unknown` is the initial state (session never seen this user).
|
|
70
|
+
*/
|
|
71
|
+
export type AccessRecord = {
|
|
72
|
+
status: AccessStatus;
|
|
73
|
+
user?: AccessUser;
|
|
74
|
+
/** Chat to DM the user back. For private chats this equals user.id. */
|
|
75
|
+
chatId?: number;
|
|
76
|
+
requestedAt?: number;
|
|
77
|
+
approvedAt?: number;
|
|
78
|
+
approvedBy?: number;
|
|
79
|
+
deniedAt?: number;
|
|
80
|
+
deniedBy?: number;
|
|
81
|
+
/** First message text from the request (truncated). */
|
|
82
|
+
firstMessage?: string;
|
|
83
|
+
lastActivityAt?: number;
|
|
84
|
+
messageCount?: number;
|
|
85
|
+
/** Counts attempts after the initial request — used by the throttle. */
|
|
86
|
+
rejectedAttempts?: number;
|
|
87
|
+
lastNotifiedAt?: number;
|
|
88
|
+
};
|
|
89
|
+
export type AccessIndex = {
|
|
90
|
+
pending: number[];
|
|
91
|
+
approved: number[];
|
|
92
|
+
denied: number[];
|
|
93
|
+
};
|
|
94
|
+
export type AccessSource = 'admin' | 'default' | 'store';
|
|
95
|
+
/**
|
|
96
|
+
* What handlers downstream see on `ctx.access`. A discriminated union —
|
|
97
|
+
* use the `allowed` field to narrow.
|
|
98
|
+
*/
|
|
99
|
+
export type AccessInfo = {
|
|
100
|
+
allowed: true;
|
|
101
|
+
source: AccessSource;
|
|
102
|
+
/** The persisted record, when source is 'store'. */
|
|
103
|
+
record?: AccessRecord;
|
|
104
|
+
} | {
|
|
105
|
+
allowed: false;
|
|
106
|
+
reason: 'denied' | 'pending' | 'unknown' | 'no-sender';
|
|
107
|
+
};
|
|
108
|
+
export type AccessControlOptions = {
|
|
109
|
+
/** Persistence. Default `inMemoryStorage()` (data lost on restart — warns once). */
|
|
110
|
+
storage?: Storage;
|
|
111
|
+
/** Always-allowed user ids, hardcoded. Bypass the entire flow. */
|
|
112
|
+
defaults?: ReadonlyArray<number>;
|
|
113
|
+
/** Reply sent to denied users on first attempt. `false` to silence. */
|
|
114
|
+
denyMessage?: string | false;
|
|
115
|
+
/** Min ms between repeat admin notifications for the same user. Default 6h. */
|
|
116
|
+
notifyThrottleMs?: number;
|
|
117
|
+
/** Callbacks for your own logging / metrics. */
|
|
118
|
+
onAccessRequest?: (info: {
|
|
119
|
+
user: AccessUser;
|
|
120
|
+
firstMessage?: string;
|
|
121
|
+
}) => void;
|
|
122
|
+
onApprove?: (info: {
|
|
123
|
+
userId: number;
|
|
124
|
+
approvedBy: number;
|
|
125
|
+
}) => void;
|
|
126
|
+
onDeny?: (info: {
|
|
127
|
+
userId: number;
|
|
128
|
+
deniedBy: number;
|
|
129
|
+
}) => void;
|
|
130
|
+
};
|
|
131
|
+
type AdminDerives = {
|
|
132
|
+
adminId: number;
|
|
133
|
+
isAdmin: boolean;
|
|
134
|
+
};
|
|
135
|
+
type AccessSessionDerives = {
|
|
136
|
+
_accessSession: AccessRecord;
|
|
137
|
+
};
|
|
138
|
+
type AccessDerives = {
|
|
139
|
+
access: AccessInfo;
|
|
140
|
+
};
|
|
141
|
+
export declare const accessControl: (opts?: AccessControlOptions) => Plugin<{}, DeriveDefinitions & {
|
|
142
|
+
global: AdminDerives & AccessSessionDerives & AccessDerives;
|
|
143
|
+
} & {
|
|
144
|
+
message: {
|
|
145
|
+
_accessSession: AccessRecord & {
|
|
146
|
+
$clear: () => Promise<void>;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
channel_post: {
|
|
150
|
+
_accessSession: AccessRecord & {
|
|
151
|
+
$clear: () => Promise<void>;
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
inline_query: {
|
|
155
|
+
_accessSession: AccessRecord & {
|
|
156
|
+
$clear: () => Promise<void>;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
chosen_inline_result: {
|
|
160
|
+
_accessSession: AccessRecord & {
|
|
161
|
+
$clear: () => Promise<void>;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
callback_query: {
|
|
165
|
+
_accessSession: AccessRecord & {
|
|
166
|
+
$clear: () => Promise<void>;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
shipping_query: {
|
|
170
|
+
_accessSession: AccessRecord & {
|
|
171
|
+
$clear: () => Promise<void>;
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
pre_checkout_query: {
|
|
175
|
+
_accessSession: AccessRecord & {
|
|
176
|
+
$clear: () => Promise<void>;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
poll_answer: {
|
|
180
|
+
_accessSession: AccessRecord & {
|
|
181
|
+
$clear: () => Promise<void>;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
chat_join_request: {
|
|
185
|
+
_accessSession: AccessRecord & {
|
|
186
|
+
$clear: () => Promise<void>;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
new_chat_members: {
|
|
190
|
+
_accessSession: AccessRecord & {
|
|
191
|
+
$clear: () => Promise<void>;
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
new_chat_title: {
|
|
195
|
+
_accessSession: AccessRecord & {
|
|
196
|
+
$clear: () => Promise<void>;
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
new_chat_photo: {
|
|
200
|
+
_accessSession: AccessRecord & {
|
|
201
|
+
$clear: () => Promise<void>;
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
delete_chat_photo: {
|
|
205
|
+
_accessSession: AccessRecord & {
|
|
206
|
+
$clear: () => Promise<void>;
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
group_chat_created: {
|
|
210
|
+
_accessSession: AccessRecord & {
|
|
211
|
+
$clear: () => Promise<void>;
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
message_auto_delete_timer_changed: {
|
|
215
|
+
_accessSession: AccessRecord & {
|
|
216
|
+
$clear: () => Promise<void>;
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
migrate_to_chat_id: {
|
|
220
|
+
_accessSession: AccessRecord & {
|
|
221
|
+
$clear: () => Promise<void>;
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
migrate_from_chat_id: {
|
|
225
|
+
_accessSession: AccessRecord & {
|
|
226
|
+
$clear: () => Promise<void>;
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
pinned_message: {
|
|
230
|
+
_accessSession: AccessRecord & {
|
|
231
|
+
$clear: () => Promise<void>;
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
invoice: {
|
|
235
|
+
_accessSession: AccessRecord & {
|
|
236
|
+
$clear: () => Promise<void>;
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
successful_payment: {
|
|
240
|
+
_accessSession: AccessRecord & {
|
|
241
|
+
$clear: () => Promise<void>;
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
chat_shared: {
|
|
245
|
+
_accessSession: AccessRecord & {
|
|
246
|
+
$clear: () => Promise<void>;
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
proximity_alert_triggered: {
|
|
250
|
+
_accessSession: AccessRecord & {
|
|
251
|
+
$clear: () => Promise<void>;
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
video_chat_scheduled: {
|
|
255
|
+
_accessSession: AccessRecord & {
|
|
256
|
+
$clear: () => Promise<void>;
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
video_chat_started: {
|
|
260
|
+
_accessSession: AccessRecord & {
|
|
261
|
+
$clear: () => Promise<void>;
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
video_chat_ended: {
|
|
265
|
+
_accessSession: AccessRecord & {
|
|
266
|
+
$clear: () => Promise<void>;
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
video_chat_participants_invited: {
|
|
270
|
+
_accessSession: AccessRecord & {
|
|
271
|
+
$clear: () => Promise<void>;
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
web_app_data: {
|
|
275
|
+
_accessSession: AccessRecord & {
|
|
276
|
+
$clear: () => Promise<void>;
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
location: {
|
|
280
|
+
_accessSession: AccessRecord & {
|
|
281
|
+
$clear: () => Promise<void>;
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
passport_data: {
|
|
285
|
+
_accessSession: AccessRecord & {
|
|
286
|
+
$clear: () => Promise<void>;
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
} & {
|
|
290
|
+
global: {
|
|
291
|
+
access: {
|
|
292
|
+
allowed: false;
|
|
293
|
+
reason: "no-sender";
|
|
294
|
+
source?: undefined;
|
|
295
|
+
record?: undefined;
|
|
296
|
+
};
|
|
297
|
+
} | {
|
|
298
|
+
access: {
|
|
299
|
+
allowed: true;
|
|
300
|
+
source: "admin";
|
|
301
|
+
reason?: undefined;
|
|
302
|
+
record?: undefined;
|
|
303
|
+
};
|
|
304
|
+
} | {
|
|
305
|
+
access: {
|
|
306
|
+
allowed: true;
|
|
307
|
+
source: "default";
|
|
308
|
+
reason?: undefined;
|
|
309
|
+
record?: undefined;
|
|
310
|
+
};
|
|
311
|
+
} | {
|
|
312
|
+
access: {
|
|
313
|
+
allowed: true;
|
|
314
|
+
source: "store";
|
|
315
|
+
record: AccessRecord;
|
|
316
|
+
reason?: undefined;
|
|
317
|
+
};
|
|
318
|
+
} | {
|
|
319
|
+
access: {
|
|
320
|
+
allowed: false;
|
|
321
|
+
reason: "unknown" | "pending" | "denied";
|
|
322
|
+
source?: undefined;
|
|
323
|
+
record?: undefined;
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
}, {}>;
|
|
327
|
+
/**
|
|
328
|
+
* Inject a synthetic access request — for tests/demos when you can't
|
|
329
|
+
* easily spin up a second Telegram account. Writes a `pending` record
|
|
330
|
+
* to storage at the same key the plugin's session would, updates the
|
|
331
|
+
* index, then DMs the admin with the real
|
|
332
|
+
* `[✅ Aprobar][❌ Denegar]` keyboard. Tapping those buttons exercises
|
|
333
|
+
* the real callback handlers end-to-end.
|
|
334
|
+
*
|
|
335
|
+
* Pass the SAME `storage` instance you passed to `accessControl({ storage })`.
|
|
336
|
+
*/
|
|
337
|
+
export declare const simulateAccessRequest: (bot: AnyBot, storage: Storage, adminId: number, fakeUser: AccessUser, message?: string) => Promise<void>;
|
|
338
|
+
export {};
|
|
339
|
+
//# sourceMappingURL=access-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../../src/bot/access-control.ts"],"names":[],"mappings":"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"}
|