@adriangalilea/utils 0.5.0 → 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 +45 -0
- 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/package.json +47 -2
|
@@ -0,0 +1,516 @@
|
|
|
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 { CallbackData, InlineKeyboard, Plugin, } from 'gramio';
|
|
59
|
+
import { session } from '@gramio/session';
|
|
60
|
+
import { inMemoryStorage } from '@gramio/storage';
|
|
61
|
+
const SESSION_KEY_PREFIX = 'access:';
|
|
62
|
+
const INDEX_KEY = 'ac:index';
|
|
63
|
+
const FIRST_MSG_LIMIT = 200;
|
|
64
|
+
const DEFAULT_DENY_MSG = 'Este bot es privado. Tu solicitud se ha enviado al admin.';
|
|
65
|
+
const DEFAULT_THROTTLE_MS = 6 * 60 * 60 * 1000;
|
|
66
|
+
const userSessionKey = (userId) => `${SESSION_KEY_PREFIX}${userId}`;
|
|
67
|
+
// ─── callback schemas ──────────────────────────────────────────────
|
|
68
|
+
//
|
|
69
|
+
// Short `nameId`s keep callback_data under Telegram's 64-byte cap.
|
|
70
|
+
// `v` (optional) carries the originating list view ('pending' | 'denied'
|
|
71
|
+
// | 'approved'). When present, the handler refreshes that list after
|
|
72
|
+
// the action; absent = original notification, edits the message inline.
|
|
73
|
+
const acApprove = new CallbackData('acA').number('uid').string('v', { optional: true });
|
|
74
|
+
const acDeny = new CallbackData('acD').number('uid').string('v', { optional: true });
|
|
75
|
+
const acRevoke = new CallbackData('acR').number('uid');
|
|
76
|
+
const acView = new CallbackData('acV').string('v'); // main | approved | pending | denied
|
|
77
|
+
const acClose = new CallbackData('acC');
|
|
78
|
+
// ─── small helpers ─────────────────────────────────────────────────
|
|
79
|
+
const formatUser = (u, fallbackId) => {
|
|
80
|
+
if (!u)
|
|
81
|
+
return `id ${fallbackId}`;
|
|
82
|
+
const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || `id ${u.id}`;
|
|
83
|
+
const handle = u.username ? `@${u.username}` : `id ${u.id}`;
|
|
84
|
+
return `${name} (${handle})`;
|
|
85
|
+
};
|
|
86
|
+
const fmtAge = (ms) => {
|
|
87
|
+
const s = Math.floor(ms / 1000);
|
|
88
|
+
if (s < 60)
|
|
89
|
+
return `${s}s`;
|
|
90
|
+
const m = Math.floor(s / 60);
|
|
91
|
+
if (m < 60)
|
|
92
|
+
return `${m}min`;
|
|
93
|
+
const h = Math.floor(m / 60);
|
|
94
|
+
if (h < 24)
|
|
95
|
+
return `${h}h`;
|
|
96
|
+
return `${Math.floor(h / 24)}d`;
|
|
97
|
+
};
|
|
98
|
+
const requestNotificationText = (uid, r, repeat) => {
|
|
99
|
+
const parts = [
|
|
100
|
+
repeat ? '🔁 Acceso re-solicitado' : '🔔 Acceso solicitado',
|
|
101
|
+
'',
|
|
102
|
+
`👤 ${formatUser(r.user, uid)}`,
|
|
103
|
+
`🆔 ${uid}`,
|
|
104
|
+
`⏰ hace ${fmtAge(Date.now() - (r.requestedAt ?? Date.now()))}`,
|
|
105
|
+
];
|
|
106
|
+
if (repeat)
|
|
107
|
+
parts.push(`🔁 intentos: ${(r.rejectedAttempts ?? 0) + 1}`);
|
|
108
|
+
if (r.firstMessage)
|
|
109
|
+
parts.push('', `💬 "${r.firstMessage}"`);
|
|
110
|
+
return parts.join('\n');
|
|
111
|
+
};
|
|
112
|
+
const requestKeyboard = (uid) => new InlineKeyboard()
|
|
113
|
+
.text('✅ Aprobar', acApprove.pack({ uid }))
|
|
114
|
+
.text('❌ Denegar', acDeny.pack({ uid }));
|
|
115
|
+
let warnedMemory = false;
|
|
116
|
+
const warnedMemoryStorage = () => {
|
|
117
|
+
if (!warnedMemory) {
|
|
118
|
+
warnedMemory = true;
|
|
119
|
+
console.warn('[access-control] using inMemoryStorage — approvals will not survive restarts. ' +
|
|
120
|
+
'Pass `storage: redisStorage()` (or sqliteStorage / cloudflareStorage) for persistence.');
|
|
121
|
+
}
|
|
122
|
+
return inMemoryStorage();
|
|
123
|
+
};
|
|
124
|
+
// ─── index helpers ─────────────────────────────────────────────────
|
|
125
|
+
const emptyIndex = () => ({ pending: [], approved: [], denied: [] });
|
|
126
|
+
const loadIndex = async (storage) => {
|
|
127
|
+
const raw = (await storage.get(INDEX_KEY));
|
|
128
|
+
return {
|
|
129
|
+
pending: raw?.pending ?? [],
|
|
130
|
+
approved: raw?.approved ?? [],
|
|
131
|
+
denied: raw?.denied ?? [],
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
const saveIndex = (storage, idx) => storage.set(INDEX_KEY, idx);
|
|
135
|
+
const indexAdd = async (storage, bucket, uid) => {
|
|
136
|
+
const idx = await loadIndex(storage);
|
|
137
|
+
if (!idx[bucket].includes(uid))
|
|
138
|
+
idx[bucket].push(uid);
|
|
139
|
+
await saveIndex(storage, idx);
|
|
140
|
+
};
|
|
141
|
+
const indexMove = async (storage, uid, from, to) => {
|
|
142
|
+
const idx = await loadIndex(storage);
|
|
143
|
+
const remove = (list) => {
|
|
144
|
+
const i = list.indexOf(uid);
|
|
145
|
+
if (i >= 0)
|
|
146
|
+
list.splice(i, 1);
|
|
147
|
+
};
|
|
148
|
+
if (from === 'any') {
|
|
149
|
+
remove(idx.pending);
|
|
150
|
+
remove(idx.approved);
|
|
151
|
+
remove(idx.denied);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
remove(idx[from]);
|
|
155
|
+
}
|
|
156
|
+
if (!idx[to].includes(uid))
|
|
157
|
+
idx[to].push(uid);
|
|
158
|
+
await saveIndex(storage, idx);
|
|
159
|
+
};
|
|
160
|
+
// ─── per-user record helpers (cross-user storage access) ───────────
|
|
161
|
+
const loadRecord = async (storage, userId) => (await storage.get(userSessionKey(userId)));
|
|
162
|
+
const saveRecord = (storage, userId, rec) => storage.set(userSessionKey(userId), rec);
|
|
163
|
+
// ─── plugin ────────────────────────────────────────────────────────
|
|
164
|
+
export const accessControl = (opts = {}) => {
|
|
165
|
+
const storage = opts.storage ?? warnedMemoryStorage();
|
|
166
|
+
const defaults = new Set(opts.defaults ?? []);
|
|
167
|
+
const denyMessage = opts.denyMessage === false ? null : (opts.denyMessage ?? DEFAULT_DENY_MSG);
|
|
168
|
+
const throttleMs = opts.notifyThrottleMs ?? DEFAULT_THROTTLE_MS;
|
|
169
|
+
return (new Plugin('@adriangalilea/utils/bot/access-control', {
|
|
170
|
+
dependencies: ['@adriangalilea/utils/bot/admin'],
|
|
171
|
+
})
|
|
172
|
+
// Per-user record lives in this session (storage key `access:<senderId>`).
|
|
173
|
+
// The internal name `_accessSession` is plumbing — consumers read
|
|
174
|
+
// `ctx.access` (computed below) instead.
|
|
175
|
+
.extend(session({
|
|
176
|
+
storage,
|
|
177
|
+
key: '_accessSession',
|
|
178
|
+
getSessionKey: (ctx) => userSessionKey(ctx.senderId ?? 0),
|
|
179
|
+
initial: () => ({ status: 'unknown' }),
|
|
180
|
+
}))
|
|
181
|
+
// Compute the gate decision so handlers can read `ctx.access` ergonomically.
|
|
182
|
+
.derive((ctx) => {
|
|
183
|
+
// Only message + callback_query carry a senderId we can gate on.
|
|
184
|
+
if (!ctx.is('message') && !ctx.is('callback_query')) {
|
|
185
|
+
return { access: { allowed: false, reason: 'no-sender' } };
|
|
186
|
+
}
|
|
187
|
+
const senderId = ctx.from.id;
|
|
188
|
+
if (senderId === ctx.adminId) {
|
|
189
|
+
return { access: { allowed: true, source: 'admin' } };
|
|
190
|
+
}
|
|
191
|
+
if (defaults.has(senderId)) {
|
|
192
|
+
return { access: { allowed: true, source: 'default' } };
|
|
193
|
+
}
|
|
194
|
+
const rec = ctx._accessSession;
|
|
195
|
+
if (rec.status === 'approved') {
|
|
196
|
+
return { access: { allowed: true, source: 'store', record: rec } };
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
access: { allowed: false, reason: rec.status },
|
|
200
|
+
};
|
|
201
|
+
})
|
|
202
|
+
// Gate. Authorized passes through; unauthorized triggers admin notify
|
|
203
|
+
// and silent stranger reply, then drops.
|
|
204
|
+
.use(async (ctx, next) => {
|
|
205
|
+
if (ctx.access.allowed) {
|
|
206
|
+
// Activity bump (only for store-approved users — admins/defaults
|
|
207
|
+
// don't have a session record we want to clutter).
|
|
208
|
+
if (ctx.access.source === 'store' && ctx.is('message')) {
|
|
209
|
+
ctx._accessSession.lastActivityAt = Date.now();
|
|
210
|
+
ctx._accessSession.messageCount = (ctx._accessSession.messageCount ?? 0) + 1;
|
|
211
|
+
}
|
|
212
|
+
return next();
|
|
213
|
+
}
|
|
214
|
+
// Acknowledge unauthorized callback queries so the spinner clears.
|
|
215
|
+
if (ctx.is('callback_query')) {
|
|
216
|
+
await ctx.answer({ text: 'Sin acceso.', show_alert: false });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Only message-shaped events have .text/.chat for our notification.
|
|
220
|
+
if (!ctx.is('message'))
|
|
221
|
+
return;
|
|
222
|
+
const userId = ctx.from.id;
|
|
223
|
+
const rec = ctx._accessSession;
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
const isFirstRequest = rec.status === 'unknown';
|
|
226
|
+
if (isFirstRequest) {
|
|
227
|
+
rec.status = 'pending';
|
|
228
|
+
rec.user = {
|
|
229
|
+
id: userId,
|
|
230
|
+
firstName: ctx.from.firstName,
|
|
231
|
+
lastName: ctx.from.lastName,
|
|
232
|
+
username: ctx.from.username,
|
|
233
|
+
};
|
|
234
|
+
rec.chatId = ctx.chat.id;
|
|
235
|
+
rec.requestedAt = now;
|
|
236
|
+
rec.firstMessage = ctx.text?.slice(0, FIRST_MSG_LIMIT);
|
|
237
|
+
rec.messageCount = 0;
|
|
238
|
+
rec.rejectedAttempts = 0;
|
|
239
|
+
await indexAdd(storage, 'pending', userId);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
rec.rejectedAttempts = (rec.rejectedAttempts ?? 0) + 1;
|
|
243
|
+
}
|
|
244
|
+
const shouldNotify = isFirstRequest || now - (rec.lastNotifiedAt ?? 0) > throttleMs;
|
|
245
|
+
if (shouldNotify) {
|
|
246
|
+
rec.lastNotifiedAt = now;
|
|
247
|
+
try {
|
|
248
|
+
await ctx.bot.api.sendMessage({
|
|
249
|
+
chat_id: ctx.adminId,
|
|
250
|
+
text: requestNotificationText(userId, rec, !isFirstRequest),
|
|
251
|
+
reply_markup: requestKeyboard(userId),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
console.error('[access-control] failed to notify admin (have you /started the bot from your account?)', e);
|
|
256
|
+
}
|
|
257
|
+
opts.onAccessRequest?.({ user: rec.user, firstMessage: rec.firstMessage });
|
|
258
|
+
}
|
|
259
|
+
if (denyMessage && isFirstRequest) {
|
|
260
|
+
try {
|
|
261
|
+
await ctx.send(denyMessage);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// user blocked the bot — irrelevant
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// do NOT call next — drop
|
|
268
|
+
})
|
|
269
|
+
// ─── admin actions ────────────────────────────────────────
|
|
270
|
+
.callbackQuery(acApprove, async (ctx) => {
|
|
271
|
+
if (!ctx.isAdmin)
|
|
272
|
+
return ctx.answer({ text: 'Solo admin.', show_alert: true });
|
|
273
|
+
const uid = ctx.queryData.uid;
|
|
274
|
+
const rec = await loadRecord(storage, uid);
|
|
275
|
+
if (!rec)
|
|
276
|
+
return ctx.answer({ text: 'No encontrado.' });
|
|
277
|
+
const wasDenied = rec.status === 'denied';
|
|
278
|
+
const wasPending = rec.status === 'pending';
|
|
279
|
+
rec.status = 'approved';
|
|
280
|
+
rec.approvedAt = Date.now();
|
|
281
|
+
rec.approvedBy = ctx.adminId;
|
|
282
|
+
rec.deniedAt = undefined;
|
|
283
|
+
rec.deniedBy = undefined;
|
|
284
|
+
await saveRecord(storage, uid, rec);
|
|
285
|
+
await indexMove(storage, uid, wasPending ? 'pending' : wasDenied ? 'denied' : 'any', 'approved');
|
|
286
|
+
if (rec.chatId !== undefined) {
|
|
287
|
+
try {
|
|
288
|
+
await ctx.bot.api.sendMessage({
|
|
289
|
+
chat_id: rec.chatId,
|
|
290
|
+
text: wasDenied
|
|
291
|
+
? '✅ El admin reconsideró: ya tienes acceso.'
|
|
292
|
+
: '✅ Acceso concedido. Ya puedes usar el bot.',
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// user blocked / chat gone
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
await ctx.answer({ text: '✅ Aprobado' });
|
|
300
|
+
if (ctx.queryData.v) {
|
|
301
|
+
await renderView(ctx, storage, defaults, ctx.queryData.v);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
try {
|
|
305
|
+
await ctx.editText(`✅ Aprobado · ${formatUser(rec.user, uid)}`);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// not always editable
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
opts.onApprove?.({ userId: uid, approvedBy: ctx.adminId });
|
|
312
|
+
})
|
|
313
|
+
.callbackQuery(acDeny, async (ctx) => {
|
|
314
|
+
if (!ctx.isAdmin)
|
|
315
|
+
return ctx.answer({ text: 'Solo admin.', show_alert: true });
|
|
316
|
+
const uid = ctx.queryData.uid;
|
|
317
|
+
const rec = await loadRecord(storage, uid);
|
|
318
|
+
if (!rec)
|
|
319
|
+
return ctx.answer({ text: 'No encontrado.' });
|
|
320
|
+
const wasPending = rec.status === 'pending';
|
|
321
|
+
rec.status = 'denied';
|
|
322
|
+
rec.deniedAt = Date.now();
|
|
323
|
+
rec.deniedBy = ctx.adminId;
|
|
324
|
+
await saveRecord(storage, uid, rec);
|
|
325
|
+
await indexMove(storage, uid, wasPending ? 'pending' : 'any', 'denied');
|
|
326
|
+
if (rec.chatId !== undefined) {
|
|
327
|
+
try {
|
|
328
|
+
await ctx.bot.api.sendMessage({
|
|
329
|
+
chat_id: rec.chatId,
|
|
330
|
+
text: '❌ Acceso denegado.',
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
// ignore
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
await ctx.answer({ text: '❌ Denegado' });
|
|
338
|
+
if (ctx.queryData.v) {
|
|
339
|
+
await renderView(ctx, storage, defaults, ctx.queryData.v);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
try {
|
|
343
|
+
await ctx.editText(`❌ Denegado · ${formatUser(rec.user, uid)}`);
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// ignore
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
opts.onDeny?.({ userId: uid, deniedBy: ctx.adminId });
|
|
350
|
+
})
|
|
351
|
+
.callbackQuery(acRevoke, async (ctx) => {
|
|
352
|
+
if (!ctx.isAdmin)
|
|
353
|
+
return ctx.answer({ text: 'Solo admin.', show_alert: true });
|
|
354
|
+
const uid = ctx.queryData.uid;
|
|
355
|
+
const rec = await loadRecord(storage, uid);
|
|
356
|
+
if (!rec)
|
|
357
|
+
return ctx.answer({ text: 'No encontrado.' });
|
|
358
|
+
rec.status = 'denied';
|
|
359
|
+
rec.deniedAt = Date.now();
|
|
360
|
+
rec.deniedBy = ctx.adminId;
|
|
361
|
+
await saveRecord(storage, uid, rec);
|
|
362
|
+
await indexMove(storage, uid, 'approved', 'denied');
|
|
363
|
+
if (rec.chatId !== undefined) {
|
|
364
|
+
try {
|
|
365
|
+
await ctx.bot.api.sendMessage({
|
|
366
|
+
chat_id: rec.chatId,
|
|
367
|
+
text: '↩️ Tu acceso al bot ha sido revocado.',
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// ignore
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
await ctx.answer({ text: '↩️ Revocado' });
|
|
375
|
+
await renderView(ctx, storage, defaults, 'approved');
|
|
376
|
+
})
|
|
377
|
+
.callbackQuery(acView, async (ctx) => {
|
|
378
|
+
if (!ctx.isAdmin)
|
|
379
|
+
return ctx.answer({ text: 'Solo admin.', show_alert: true });
|
|
380
|
+
await ctx.answer({});
|
|
381
|
+
await renderView(ctx, storage, defaults, ctx.queryData.v);
|
|
382
|
+
})
|
|
383
|
+
.callbackQuery(acClose, async (ctx) => {
|
|
384
|
+
if (!ctx.isAdmin)
|
|
385
|
+
return ctx.answer({ text: 'Solo admin.', show_alert: true });
|
|
386
|
+
await ctx.answer({});
|
|
387
|
+
try {
|
|
388
|
+
await ctx.message?.delete();
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// ignore
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
.command('access', async (ctx) => {
|
|
395
|
+
if (!ctx.isAdmin)
|
|
396
|
+
return;
|
|
397
|
+
const v = await mainView(storage, defaults);
|
|
398
|
+
await ctx.send(v.text, { reply_markup: v.keyboard });
|
|
399
|
+
}));
|
|
400
|
+
};
|
|
401
|
+
const renderView = async (ctx, storage, defaults, view) => {
|
|
402
|
+
const v = view === 'approved'
|
|
403
|
+
? await listView(storage, 'approved', defaults)
|
|
404
|
+
: view === 'pending'
|
|
405
|
+
? await listView(storage, 'pending', defaults)
|
|
406
|
+
: view === 'denied'
|
|
407
|
+
? await listView(storage, 'denied', defaults)
|
|
408
|
+
: await mainView(storage, defaults);
|
|
409
|
+
try {
|
|
410
|
+
await ctx.editText(v.text, { reply_markup: v.keyboard });
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// editText only works while message is recent enough; ignore
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const mainView = async (storage, defaults) => {
|
|
417
|
+
const idx = await loadIndex(storage);
|
|
418
|
+
const text = [
|
|
419
|
+
'🔐 Access Control',
|
|
420
|
+
'',
|
|
421
|
+
`✅ Aprobados: ${idx.approved.length}`,
|
|
422
|
+
`⏳ Pendientes: ${idx.pending.length}`,
|
|
423
|
+
`❌ Denegados: ${idx.denied.length}`,
|
|
424
|
+
`👑 Defaults: ${defaults.size} (hardcoded)`,
|
|
425
|
+
].join('\n');
|
|
426
|
+
const keyboard = new InlineKeyboard()
|
|
427
|
+
.text(`✅ Aprobados (${idx.approved.length})`, acView.pack({ v: 'approved' }))
|
|
428
|
+
.text(`⏳ Pendientes (${idx.pending.length})`, acView.pack({ v: 'pending' }))
|
|
429
|
+
.row()
|
|
430
|
+
.text(`❌ Denegados (${idx.denied.length})`, acView.pack({ v: 'denied' }))
|
|
431
|
+
.text('🔄 Refresh', acView.pack({ v: 'main' }))
|
|
432
|
+
.row()
|
|
433
|
+
.text('✖️ Cerrar', acClose.pack({}));
|
|
434
|
+
return { text, keyboard };
|
|
435
|
+
};
|
|
436
|
+
const listView = async (storage, filter, defaults) => {
|
|
437
|
+
const idx = await loadIndex(storage);
|
|
438
|
+
const ids = idx[filter];
|
|
439
|
+
// Cap at 20 to keep callback_data + rendering sane.
|
|
440
|
+
const shownIds = ids.slice(0, 20);
|
|
441
|
+
const records = await Promise.all(shownIds.map(async (id) => ({ id, rec: await loadRecord(storage, id) })));
|
|
442
|
+
const headerEmoji = filter === 'approved' ? '✅' : filter === 'pending' ? '⏳' : '❌';
|
|
443
|
+
const headerLabel = filter === 'approved' ? 'Aprobados' : filter === 'pending' ? 'Pendientes' : 'Denegados';
|
|
444
|
+
if (ids.length === 0) {
|
|
445
|
+
const text = `${headerEmoji} ${headerLabel} (0)\n\n(vacío)`;
|
|
446
|
+
const keyboard = new InlineKeyboard().text('⬅️ Volver', acView.pack({ v: 'main' }));
|
|
447
|
+
return { text, keyboard };
|
|
448
|
+
}
|
|
449
|
+
const lines = [`${headerEmoji} ${headerLabel} (${ids.length})`, ''];
|
|
450
|
+
const keyboard = new InlineKeyboard();
|
|
451
|
+
for (let i = 0; i < records.length; i++) {
|
|
452
|
+
const { id, rec } = records[i];
|
|
453
|
+
if (!rec) {
|
|
454
|
+
// index referenced a missing record — show as placeholder
|
|
455
|
+
lines.push(`${i + 1}. id ${id} (datos perdidos)`);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const ageRef = rec.approvedAt ?? rec.deniedAt ?? rec.requestedAt ?? Date.now();
|
|
459
|
+
lines.push(`${i + 1}. ${formatUser(rec.user, id)} · hace ${fmtAge(Date.now() - ageRef)}` +
|
|
460
|
+
(rec.messageCount ? ` · ${rec.messageCount} msgs` : ''));
|
|
461
|
+
if (filter === 'pending') {
|
|
462
|
+
keyboard
|
|
463
|
+
.text(`✅ ${i + 1}`, acApprove.pack({ uid: id, v: 'pending' }))
|
|
464
|
+
.text(`❌ ${i + 1}`, acDeny.pack({ uid: id, v: 'pending' }))
|
|
465
|
+
.row();
|
|
466
|
+
}
|
|
467
|
+
else if (filter === 'approved') {
|
|
468
|
+
keyboard.text(`↩️ Revocar #${i + 1}`, acRevoke.pack({ uid: id })).row();
|
|
469
|
+
}
|
|
470
|
+
else if (filter === 'denied') {
|
|
471
|
+
keyboard
|
|
472
|
+
.text(`✅ Reaprobar #${i + 1}`, acApprove.pack({ uid: id, v: 'denied' }))
|
|
473
|
+
.row();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (ids.length > shownIds.length) {
|
|
477
|
+
lines.push('', `(+${ids.length - shownIds.length} más, no mostrados)`);
|
|
478
|
+
}
|
|
479
|
+
if (filter === 'approved' && defaults.size > 0) {
|
|
480
|
+
lines.push('', `+ ${defaults.size} hardcoded defaults`);
|
|
481
|
+
}
|
|
482
|
+
keyboard.text('⬅️ Volver', acView.pack({ v: 'main' }));
|
|
483
|
+
return { text: lines.join('\n'), keyboard };
|
|
484
|
+
};
|
|
485
|
+
// ─── test helper ───────────────────────────────────────────────────
|
|
486
|
+
/**
|
|
487
|
+
* Inject a synthetic access request — for tests/demos when you can't
|
|
488
|
+
* easily spin up a second Telegram account. Writes a `pending` record
|
|
489
|
+
* to storage at the same key the plugin's session would, updates the
|
|
490
|
+
* index, then DMs the admin with the real
|
|
491
|
+
* `[✅ Aprobar][❌ Denegar]` keyboard. Tapping those buttons exercises
|
|
492
|
+
* the real callback handlers end-to-end.
|
|
493
|
+
*
|
|
494
|
+
* Pass the SAME `storage` instance you passed to `accessControl({ storage })`.
|
|
495
|
+
*/
|
|
496
|
+
export const simulateAccessRequest = async (bot, storage, adminId, fakeUser, message) => {
|
|
497
|
+
const now = Date.now();
|
|
498
|
+
const rec = {
|
|
499
|
+
status: 'pending',
|
|
500
|
+
user: fakeUser,
|
|
501
|
+
chatId: fakeUser.id,
|
|
502
|
+
requestedAt: now,
|
|
503
|
+
firstMessage: message?.slice(0, FIRST_MSG_LIMIT),
|
|
504
|
+
messageCount: 0,
|
|
505
|
+
rejectedAttempts: 0,
|
|
506
|
+
lastNotifiedAt: now,
|
|
507
|
+
};
|
|
508
|
+
await saveRecord(storage, fakeUser.id, rec);
|
|
509
|
+
await indexAdd(storage, 'pending', fakeUser.id);
|
|
510
|
+
await bot.api.sendMessage({
|
|
511
|
+
chat_id: adminId,
|
|
512
|
+
text: requestNotificationText(fakeUser.id, rec, false),
|
|
513
|
+
reply_markup: requestKeyboard(fakeUser.id),
|
|
514
|
+
});
|
|
515
|
+
};
|
|
516
|
+
//# sourceMappingURL=access-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-control.js","sourceRoot":"","sources":["../../src/bot/access-control.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram bot plugins for GramIO. Imported as subpaths so this module
|
|
3
|
+
* has zero footprint for consumers that don't use them.
|
|
4
|
+
*
|
|
5
|
+
* Peer deps (all optional): `gramio`, `@gramio/format`, `@gramio/storage`, `marked`.
|
|
6
|
+
*
|
|
7
|
+
* import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
|
|
8
|
+
* import { accessControl } from '@adriangalilea/utils/bot/access-control'
|
|
9
|
+
* import { llmStream } from '@adriangalilea/utils/bot/llm-stream'
|
|
10
|
+
*
|
|
11
|
+
* Or all-in-one (pulls every subpath):
|
|
12
|
+
* import { ... } from '@adriangalilea/utils/bot'
|
|
13
|
+
*/
|
|
14
|
+
export * from './kit.js';
|
|
15
|
+
export * from './access-control.js';
|
|
16
|
+
export * from './llm-stream.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,UAAU,CAAA;AACxB,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram bot plugins for GramIO. Imported as subpaths so this module
|
|
3
|
+
* has zero footprint for consumers that don't use them.
|
|
4
|
+
*
|
|
5
|
+
* Peer deps (all optional): `gramio`, `@gramio/format`, `@gramio/storage`, `marked`.
|
|
6
|
+
*
|
|
7
|
+
* import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
|
|
8
|
+
* import { accessControl } from '@adriangalilea/utils/bot/access-control'
|
|
9
|
+
* import { llmStream } from '@adriangalilea/utils/bot/llm-stream'
|
|
10
|
+
*
|
|
11
|
+
* Or all-in-one (pulls every subpath):
|
|
12
|
+
* import { ... } from '@adriangalilea/utils/bot'
|
|
13
|
+
*/
|
|
14
|
+
export * from './kit.js';
|
|
15
|
+
export * from './access-control.js';
|
|
16
|
+
export * from './llm-stream.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,UAAU,CAAA;AACxB,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foundational helpers every bot wants. Two things:
|
|
3
|
+
*
|
|
4
|
+
* `gracefulStart(bot, opts?)` — wires SIGINT/SIGTERM to bot.stop(),
|
|
5
|
+
* runs an optional shutdown hook, force-kills if it hangs.
|
|
6
|
+
*
|
|
7
|
+
* `adminContext({ adminId? })` — reads admin Telegram id from KEV
|
|
8
|
+
* (`TELEGRAM_ADMIN_ID`) with optional hardcoded fallback. Decorates
|
|
9
|
+
* every context with `ctx.adminId` (number) and `ctx.isAdmin`
|
|
10
|
+
* (boolean). Throws at startup if neither source provides an id.
|
|
11
|
+
*
|
|
12
|
+
* Peer deps: `gramio`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* import { Bot } from 'gramio'
|
|
16
|
+
* import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
|
|
17
|
+
*
|
|
18
|
+
* const bot = new Bot(process.env.BOT_TOKEN!)
|
|
19
|
+
* .extend(adminContext({ adminId: 190202471 })) // KEV wins, 190… is fallback
|
|
20
|
+
* .command('whoami', (ctx) => ctx.send(`admin? ${ctx.isAdmin}`))
|
|
21
|
+
*
|
|
22
|
+
* await gracefulStart(bot, { onShutdown: () => db.end() })
|
|
23
|
+
*/
|
|
24
|
+
import type { AnyBot } from 'gramio';
|
|
25
|
+
import { Plugin } from 'gramio';
|
|
26
|
+
export type GracefulStartOptions = {
|
|
27
|
+
/** Runs after `bot.stop()` resolves, before `process.exit`. Close DBs, flush logs. */
|
|
28
|
+
onShutdown?: () => Promise<void> | void;
|
|
29
|
+
/** Process exit code on graceful shutdown. Default 0. */
|
|
30
|
+
exitCode?: number;
|
|
31
|
+
/** Hard-kill after this many ms if shutdown hangs. Default 10000. */
|
|
32
|
+
forceExitAfterMs?: number;
|
|
33
|
+
/** Logger. Default `console.log`. Set `false` to silence. */
|
|
34
|
+
log?: ((msg: string) => void) | false;
|
|
35
|
+
};
|
|
36
|
+
export declare const gracefulStart: (bot: AnyBot, opts?: GracefulStartOptions) => Promise<void>;
|
|
37
|
+
export type AdminContextOptions = {
|
|
38
|
+
/** Hardcoded fallback used when `KEV.TELEGRAM_ADMIN_ID` is unset. */
|
|
39
|
+
adminId?: number;
|
|
40
|
+
};
|
|
41
|
+
export declare const adminContext: (opts?: AdminContextOptions) => Plugin<{}, import("gramio").DeriveDefinitions & {
|
|
42
|
+
global: {
|
|
43
|
+
adminId: number;
|
|
44
|
+
};
|
|
45
|
+
} & {
|
|
46
|
+
global: {
|
|
47
|
+
isAdmin: boolean;
|
|
48
|
+
};
|
|
49
|
+
}, {}>;
|
|
50
|
+
//# sourceMappingURL=kit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kit.d.ts","sourceRoot":"","sources":["../../src/bot/kit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAK/B,MAAM,MAAM,oBAAoB,GAAG;IACjC,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACvC,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,KAAK,CAAA;CACtC,CAAA;AAED,eAAO,MAAM,aAAa,GACxB,KAAK,MAAM,EACX,OAAM,oBAAyB,KAC9B,OAAO,CAAC,IAAI,CAiCd,CAAA;AAID,MAAM,MAAM,mBAAmB,GAAG;IAChC,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,OAAM,mBAAwB;;;;;;;;MAqB1D,CAAA"}
|