@adriangalilea/utils 0.7.0 → 0.8.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.
Files changed (73) hide show
  1. package/README.md +1 -5
  2. package/dist/bot/access-control.d.ts +91 -58
  3. package/dist/bot/access-control.d.ts.map +1 -1
  4. package/dist/bot/access-control.js +105 -75
  5. package/dist/bot/access-control.js.map +1 -1
  6. package/dist/bot/index.d.ts +3 -0
  7. package/dist/bot/index.d.ts.map +1 -1
  8. package/dist/bot/index.js +3 -0
  9. package/dist/bot/index.js.map +1 -1
  10. package/dist/bot/kit.d.ts.map +1 -1
  11. package/dist/bot/kit.js +6 -0
  12. package/dist/bot/kit.js.map +1 -1
  13. package/dist/bot/language.d.ts +279 -0
  14. package/dist/bot/language.d.ts.map +1 -0
  15. package/dist/bot/language.js +222 -0
  16. package/dist/bot/language.js.map +1 -0
  17. package/dist/bot/menu.d.ts +175 -0
  18. package/dist/bot/menu.d.ts.map +1 -0
  19. package/dist/bot/menu.js +284 -0
  20. package/dist/bot/menu.js.map +1 -0
  21. package/dist/bot/message-history.d.ts +259 -0
  22. package/dist/bot/message-history.d.ts.map +1 -0
  23. package/dist/bot/message-history.js +111 -0
  24. package/dist/bot/message-history.js.map +1 -0
  25. package/package.json +13 -1
  26. package/dist/currency/crypto-symbols-data.d.ts +0 -10
  27. package/dist/currency/crypto-symbols-data.d.ts.map +0 -1
  28. package/dist/currency/crypto-symbols-data.js +0 -13765
  29. package/dist/currency/crypto-symbols-data.js.map +0 -1
  30. package/dist/currency/crypto-symbols.d.ts +0 -20
  31. package/dist/currency/crypto-symbols.d.ts.map +0 -1
  32. package/dist/currency/crypto-symbols.js +0 -23
  33. package/dist/currency/crypto-symbols.js.map +0 -1
  34. package/dist/currency/download-crypto-list.d.ts +0 -10
  35. package/dist/currency/download-crypto-list.d.ts.map +0 -1
  36. package/dist/currency/download-crypto-list.js +0 -69
  37. package/dist/currency/download-crypto-list.js.map +0 -1
  38. package/dist/currency/index.d.ts +0 -84
  39. package/dist/currency/index.d.ts.map +0 -1
  40. package/dist/currency/index.js +0 -230
  41. package/dist/currency/index.js.map +0 -1
  42. package/dist/dir.d.ts +0 -40
  43. package/dist/dir.d.ts.map +0 -1
  44. package/dist/dir.js +0 -108
  45. package/dist/dir.js.map +0 -1
  46. package/dist/file.d.ts +0 -53
  47. package/dist/file.d.ts.map +0 -1
  48. package/dist/file.js +0 -211
  49. package/dist/file.js.map +0 -1
  50. package/dist/format.d.ts +0 -40
  51. package/dist/format.d.ts.map +0 -1
  52. package/dist/format.js +0 -83
  53. package/dist/format.js.map +0 -1
  54. package/dist/kev.d.ts +0 -149
  55. package/dist/kev.d.ts.map +0 -1
  56. package/dist/kev.js +0 -761
  57. package/dist/kev.js.map +0 -1
  58. package/dist/log.d.ts +0 -91
  59. package/dist/log.d.ts.map +0 -1
  60. package/dist/log.js +0 -300
  61. package/dist/log.js.map +0 -1
  62. package/dist/logger.d.ts +0 -91
  63. package/dist/logger.d.ts.map +0 -1
  64. package/dist/logger.js +0 -269
  65. package/dist/logger.js.map +0 -1
  66. package/dist/path.d.ts +0 -67
  67. package/dist/path.d.ts.map +0 -1
  68. package/dist/path.js +0 -107
  69. package/dist/path.js.map +0 -1
  70. package/dist/project.d.ts +0 -35
  71. package/dist/project.d.ts.map +0 -1
  72. package/dist/project.js +0 -154
  73. package/dist/project.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../src/bot/menu.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Composable settings menu — registers a single slash command, renders
3
+ * an `InlineKeyboard`, routes callbacks. Features (language, history,
4
+ * etc.) contribute items; the bot builder adds custom items inline.
5
+ *
6
+ * ## Why menu is a separate primitive
7
+ *
8
+ * The user-facing menu is just UI — composing items. Features (per-user
9
+ * state, recording, gates) have their OWN runtime behaviour independent
10
+ * of any menu.
11
+ *
12
+ * ## GDPR rights (Forget / Export)
13
+ *
14
+ * If you pass `personalData: { storage }`, the menu adds two buttons:
15
+ *
16
+ * - 🗑 Forget my data — `storage.delete(sessionKey(userId))`
17
+ * - 📥 Export my data — `storage.get(sessionKey(userId))` → JSON file
18
+ *
19
+ * Because all per-user state across our plugins lives in ONE shared
20
+ * session record (see `bot/language`, `bot/message-history`), wiping
21
+ * or exporting that single key covers everything in one shot. No
22
+ * registry, no cascade, no per-plugin coordination.
23
+ *
24
+ * The `sessionKey` option defaults to `String(userId)` — matching
25
+ * `@gramio/session`'s default `getSessionKey: (ctx) => `${ctx.senderId}`.
26
+ * If you customize the session's `getSessionKey`, pass a matching
27
+ * function here.
28
+ *
29
+ * ## Privacy URL
30
+ *
31
+ * `privacy` defaults to [Telegram's Standard Bot Privacy Policy](https://telegram.org/privacy-tpa).
32
+ * Override when you retain content or process data beyond what the
33
+ * standard covers.
34
+ *
35
+ * Peer deps: `gramio`, `@gramio/storage`.
36
+ *
37
+ * @example Personal LLM bot — language only, no retention
38
+ *
39
+ * import { language } from '@adriangalilea/utils/bot/language'
40
+ * import { botMenu } from '@adriangalilea/utils/bot/menu'
41
+ *
42
+ * const lang = language({ session: userSession, supported: ['en','es'] as const, default: 'en' })
43
+ *
44
+ * const menu = botMenu({
45
+ * command: 'settings',
46
+ * description: 'Open settings',
47
+ * adminContact: '@adriangalilea',
48
+ * items: [lang.menuItem],
49
+ * })
50
+ *
51
+ * bot.extend(userSession).extend(lang.plugin).extend(menu.plugin)
52
+ *
53
+ * @example Bot with retention — adds Forget/Export
54
+ *
55
+ * const menu = botMenu({
56
+ * command: 'settings',
57
+ * description: 'Open settings',
58
+ * adminContact: '@adriangalilea',
59
+ * privacy: 'https://yourbot.com/privacy',
60
+ * personalData: { storage }, // ← enables Forget/Export
61
+ * items: [lang.menuItem],
62
+ * })
63
+ *
64
+ * bot
65
+ * .extend(userSession)
66
+ * .extend(history.plugin)
67
+ * .extend(menu.plugin)
68
+ */
69
+ import { CallbackData, InlineKeyboard, Plugin, } from 'gramio';
70
+ const DEFAULT_COMMAND = 'settings';
71
+ const DEFAULT_DESCRIPTION = 'Open settings menu';
72
+ const DEFAULT_PRIVACY_URL = 'https://telegram.org/privacy-tpa';
73
+ const DEFAULT_HEADER = '⚙️ Settings';
74
+ const DEFAULT_SESSION_KEY = (userId) => String(userId);
75
+ // ─── callback data schemas ─────────────────────────────────────────
76
+ const navCb = new CallbackData('mNav').string('path');
77
+ const actCb = new CallbackData('mAct').string('path');
78
+ const forgetConfirmCb = new CallbackData('mFcfm');
79
+ const forgetCancelCb = new CallbackData('mFcnl');
80
+ const exportCb = new CallbackData('mExp');
81
+ export class BotMenu {
82
+ /** @internal */
83
+ _items;
84
+ /** @internal */
85
+ _opts;
86
+ constructor(opts) {
87
+ this._items = [...(opts.items ?? [])];
88
+ this._opts = {
89
+ command: opts.command ?? DEFAULT_COMMAND,
90
+ description: opts.description ?? DEFAULT_DESCRIPTION,
91
+ privacy: opts.privacy ?? DEFAULT_PRIVACY_URL,
92
+ header: opts.header ?? DEFAULT_HEADER,
93
+ adminContact: opts.adminContact,
94
+ personalData: opts.personalData
95
+ ? {
96
+ storage: opts.personalData.storage,
97
+ sessionKey: opts.personalData.sessionKey ?? DEFAULT_SESSION_KEY,
98
+ }
99
+ : null,
100
+ };
101
+ }
102
+ /** Append a custom item. Mutates the menu. */
103
+ add(item) {
104
+ this._items.push(item);
105
+ return this;
106
+ }
107
+ /** The gramio plugin: registers the slash command + all callback handlers. */
108
+ get plugin() {
109
+ return buildMenuPlugin(this);
110
+ }
111
+ }
112
+ export const botMenu = (opts) => new BotMenu(opts);
113
+ // ─── internal: rendering + plugin ──────────────────────────────────
114
+ const labelOf = (l, ctx) => typeof l === 'function' ? l(ctx) : l;
115
+ const itemsForPath = (root, path) => {
116
+ if (path.length === 0)
117
+ return root;
118
+ const [head, ...rest] = path;
119
+ const found = root.find((i) => i.id === head);
120
+ if (!found || !('submenu' in found))
121
+ return null;
122
+ return itemsForPath(found.submenu, rest);
123
+ };
124
+ const itemForPath = (root, path) => {
125
+ if (path.length === 0)
126
+ return null;
127
+ let current = root;
128
+ let last;
129
+ for (const segment of path) {
130
+ if (!current)
131
+ return null;
132
+ last = current.find((i) => i.id === segment);
133
+ if (!last)
134
+ return null;
135
+ current = 'submenu' in last ? last.submenu : undefined;
136
+ }
137
+ return last ?? null;
138
+ };
139
+ const renderKeyboard = (menu, items, ctx, parentPath) => {
140
+ const kb = new InlineKeyboard();
141
+ const sorted = [...items]
142
+ .filter((i) => (i.visible ? i.visible(ctx) : true))
143
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
144
+ for (const item of sorted) {
145
+ const path = [...parentPath, item.id].join('.');
146
+ const label = labelOf(item.label, ctx);
147
+ if ('action' in item) {
148
+ kb.text(label, actCb.pack({ path }));
149
+ }
150
+ else if ('url' in item) {
151
+ kb.url(label, item.url);
152
+ }
153
+ else {
154
+ kb.text(label, navCb.pack({ path }));
155
+ }
156
+ kb.row();
157
+ }
158
+ if (parentPath.length === 0) {
159
+ // GDPR rights buttons at the root view.
160
+ if (menu._opts.personalData) {
161
+ kb.text('🗑 Forget my data', actCb.pack({ path: '_forget' }));
162
+ kb.row();
163
+ kb.text('📥 Export my data', exportCb.pack({}));
164
+ kb.row();
165
+ }
166
+ // Privacy link.
167
+ kb.url('📖 Privacy', menu._opts.privacy);
168
+ kb.row();
169
+ }
170
+ else {
171
+ const backPath = parentPath.slice(0, -1).join('.');
172
+ kb.text('⬅️ Back', navCb.pack({ path: backPath || '_root' }));
173
+ }
174
+ return kb;
175
+ };
176
+ const renderConfirmForget = () => new InlineKeyboard()
177
+ .text('✅ Confirm delete', forgetConfirmCb.pack({}))
178
+ .row()
179
+ .text('⬅️ Cancel', forgetCancelCb.pack({}));
180
+ const buildMenuPlugin = (menu) => {
181
+ const { command, description, header, personalData, adminContact } = menu._opts;
182
+ return new Plugin('@adriangalilea/utils/bot/menu')
183
+ .command(command, { description }, async (ctx) => {
184
+ const kb = renderKeyboard(menu, menu._items, ctx, []);
185
+ await ctx.send(labelOf(header, ctx), { reply_markup: kb });
186
+ })
187
+ // Navigate (root / submenu)
188
+ .callbackQuery(navCb, async (ctx) => {
189
+ const raw = ctx.queryData.path;
190
+ const segments = raw === '_root' ? [] : raw.split('.');
191
+ const items = itemsForPath(menu._items, segments);
192
+ if (!items) {
193
+ await ctx.answer({ text: 'Menu out of date.' });
194
+ return;
195
+ }
196
+ await ctx.answer({});
197
+ const kb = renderKeyboard(menu, items, ctx, segments);
198
+ try {
199
+ await ctx.editText(labelOf(header, ctx), { reply_markup: kb });
200
+ }
201
+ catch {
202
+ // message too old to edit
203
+ }
204
+ })
205
+ // Action items + the forget pre-confirmation
206
+ .callbackQuery(actCb, async (ctx) => {
207
+ const raw = ctx.queryData.path;
208
+ if (raw === '_forget') {
209
+ await ctx.answer({});
210
+ try {
211
+ await ctx.editText('⚠️ Delete all your data?\n\n' +
212
+ 'This removes the session record we keep about you ' +
213
+ '(preferences, history, access state). Not reversible.', { reply_markup: renderConfirmForget() });
214
+ }
215
+ catch {
216
+ /* ignore */
217
+ }
218
+ return;
219
+ }
220
+ const item = itemForPath(menu._items, raw.split('.'));
221
+ if (!item || !('action' in item)) {
222
+ await ctx.answer({ text: 'Item not found.' });
223
+ return;
224
+ }
225
+ await ctx.answer({});
226
+ await item.action(ctx);
227
+ })
228
+ // Forget — confirm path
229
+ .callbackQuery(forgetConfirmCb, async (ctx) => {
230
+ if (!personalData) {
231
+ await ctx.answer({ text: 'Not configured.', show_alert: true });
232
+ return;
233
+ }
234
+ const userId = ctx.from?.id;
235
+ if (userId === undefined)
236
+ return ctx.answer({ text: 'No user.' });
237
+ try {
238
+ await personalData.storage.delete(personalData.sessionKey(userId));
239
+ await ctx.answer({ text: 'Deleted.' });
240
+ try {
241
+ await ctx.editText('✅ Your data has been deleted.');
242
+ }
243
+ catch {
244
+ /* ignore */
245
+ }
246
+ }
247
+ catch (e) {
248
+ console.error('[menu] /forget failed', e);
249
+ await ctx.answer({ text: 'Failed.' });
250
+ await ctx.send(`❌ Could not delete your data.\n\nPlease contact ${adminContact}.`);
251
+ }
252
+ })
253
+ .callbackQuery(forgetCancelCb, async (ctx) => {
254
+ await ctx.answer({});
255
+ const kb = renderKeyboard(menu, menu._items, ctx, []);
256
+ try {
257
+ await ctx.editText(labelOf(header, ctx), { reply_markup: kb });
258
+ }
259
+ catch {
260
+ /* ignore */
261
+ }
262
+ })
263
+ // Export — JSON file with the user's whole session record
264
+ .callbackQuery(exportCb, async (ctx) => {
265
+ if (!personalData) {
266
+ await ctx.answer({ text: 'Not configured.', show_alert: true });
267
+ return;
268
+ }
269
+ const userId = ctx.from?.id;
270
+ if (userId === undefined)
271
+ return ctx.answer({ text: 'No user.' });
272
+ const record = (await personalData.storage.get(personalData.sessionKey(userId))) ?? {};
273
+ const file = new File([JSON.stringify({ userId, exportedAt: Date.now(), data: record }, null, 2)], `my-data-${userId}-${Date.now()}.json`, { type: 'application/json' });
274
+ await ctx.answer({});
275
+ try {
276
+ await ctx.sendDocument(file, { caption: '📥 Your data export' });
277
+ }
278
+ catch (e) {
279
+ console.error('[menu] /export sendDocument failed', e);
280
+ await ctx.send(`❌ Could not send your data export.\n\nPlease contact ${adminContact}.`);
281
+ }
282
+ });
283
+ };
284
+ //# sourceMappingURL=menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.js","sourceRoot":"","sources":["../../src/bot/menu.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Per-user rolling message history for GramIO bots — opt-in, with
3
+ * retention.
4
+ *
5
+ * Follows the same shared-session pattern as `bot/language`: the
6
+ * user creates a `session()` once at bot level, and each feature
7
+ * plugin (including this one) declares it as a dependency. gramio
8
+ * dedupes the runtime extension; the types flow.
9
+ *
10
+ * ## What this plugin owns
11
+ *
12
+ * - Appends each incoming user message to `ctx.session.history.items`
13
+ * - Prunes entries older than `retentionDays` or beyond `maxMessages`
14
+ * - Exposes a read-only pruned snapshot at `ctx.history` for handlers
15
+ *
16
+ * ## GDPR caveat — retention IS personal data
17
+ *
18
+ * Unlike `bot/language` (preference data, covered by Telegram's standard
19
+ * privacy policy), retaining user **message content** is the one thing
20
+ * the [standard policy](https://telegram.org/privacy-tpa) does NOT cover
21
+ * by default. If you extend `messageHistory`, you should:
22
+ *
23
+ * 1. Set a custom `privacy` URL on your `botMenu` describing what you
24
+ * retain and for how long.
25
+ * 2. Pass `personalData: { storage }` to your `botMenu` so 🗑 Forget
26
+ * and 📥 Export buttons appear — letting users see and delete
27
+ * the data you keep about them.
28
+ *
29
+ * These are not enforced by this plugin (would couple it to menu); they
30
+ * are documented as the bot author's legal responsibility.
31
+ *
32
+ * ## Storage layout
33
+ *
34
+ * Lives entirely inside the shared session record:
35
+ *
36
+ * storage[String(senderId)] = {
37
+ * ...other plugins' fields,
38
+ * history: { items: [HistoryEntry, ...] }
39
+ * }
40
+ *
41
+ * Peer deps: `gramio`, `@gramio/session`.
42
+ *
43
+ * @example
44
+ * import { Bot } from 'gramio'
45
+ * import { session } from '@gramio/session'
46
+ * import { redisStorage } from '@gramio/storage-redis'
47
+ * import { messageHistory } from '@adriangalilea/utils/bot/message-history'
48
+ *
49
+ * const userSession = session({ storage: redisStorage(), key: 'session', initial: () => ({}) })
50
+ *
51
+ * const history = messageHistory({
52
+ * session: userSession,
53
+ * maxMessages: 100,
54
+ * retentionDays: 7,
55
+ * })
56
+ *
57
+ * const bot = new Bot(process.env.BOT_TOKEN!)
58
+ * .extend(userSession)
59
+ * .extend(history.plugin)
60
+ * .command('replay', (ctx) => {
61
+ * const last = ctx.history.slice(-3).map((e) => e.text).join('\n---\n')
62
+ * return ctx.send(last || '(no history)')
63
+ * })
64
+ */
65
+ import { type DeriveDefinitions, Plugin } from 'gramio';
66
+ import { session } from '@gramio/session';
67
+ export type HistoryEntry = {
68
+ /** Telegram message id. */
69
+ messageId: number;
70
+ /** Unix seconds (Telegram's `message.date`). */
71
+ date: number;
72
+ /** Message text, or empty string if non-text. */
73
+ text: string;
74
+ };
75
+ export type HistoryRecord = {
76
+ items: HistoryEntry[];
77
+ };
78
+ /** Loose session shape — this plugin only touches the `history` field. */
79
+ type SessionLike = {
80
+ history?: HistoryRecord;
81
+ };
82
+ /** @internal — kept unexported so it doesn't clash with peers' refs. */
83
+ type HistorySessionPluginRef = ReturnType<typeof session<SessionLike, 'session'>>;
84
+ export type MessageHistoryOptions = {
85
+ /**
86
+ * Shared session plugin. This plugin extends it for the type flow;
87
+ * gramio's runtime dedup ensures it only runs once per update.
88
+ */
89
+ session: HistorySessionPluginRef;
90
+ /** Ring buffer cap. Oldest entries dropped when exceeded. */
91
+ maxMessages: number;
92
+ /** Entries older than this (in days) are dropped on read. */
93
+ retentionDays: number;
94
+ };
95
+ export type MessageHistoryFeature = {
96
+ plugin: ReturnType<typeof buildHistoryPlugin>;
97
+ };
98
+ type HistoryDerives = {
99
+ history: ReadonlyArray<HistoryEntry>;
100
+ };
101
+ export declare const messageHistory: (opts: MessageHistoryOptions) => MessageHistoryFeature;
102
+ declare const buildHistoryPlugin: (args: {
103
+ sessionPlugin: HistorySessionPluginRef;
104
+ maxMessages: number;
105
+ retentionDays: number;
106
+ }) => Plugin<{}, DeriveDefinitions & {
107
+ global: HistoryDerives;
108
+ } & {
109
+ message: {
110
+ session: SessionLike & {
111
+ $clear: () => Promise<void>;
112
+ };
113
+ };
114
+ channel_post: {
115
+ session: SessionLike & {
116
+ $clear: () => Promise<void>;
117
+ };
118
+ };
119
+ inline_query: {
120
+ session: SessionLike & {
121
+ $clear: () => Promise<void>;
122
+ };
123
+ };
124
+ chosen_inline_result: {
125
+ session: SessionLike & {
126
+ $clear: () => Promise<void>;
127
+ };
128
+ };
129
+ callback_query: {
130
+ session: SessionLike & {
131
+ $clear: () => Promise<void>;
132
+ };
133
+ };
134
+ shipping_query: {
135
+ session: SessionLike & {
136
+ $clear: () => Promise<void>;
137
+ };
138
+ };
139
+ pre_checkout_query: {
140
+ session: SessionLike & {
141
+ $clear: () => Promise<void>;
142
+ };
143
+ };
144
+ poll_answer: {
145
+ session: SessionLike & {
146
+ $clear: () => Promise<void>;
147
+ };
148
+ };
149
+ chat_join_request: {
150
+ session: SessionLike & {
151
+ $clear: () => Promise<void>;
152
+ };
153
+ };
154
+ new_chat_members: {
155
+ session: SessionLike & {
156
+ $clear: () => Promise<void>;
157
+ };
158
+ };
159
+ new_chat_title: {
160
+ session: SessionLike & {
161
+ $clear: () => Promise<void>;
162
+ };
163
+ };
164
+ new_chat_photo: {
165
+ session: SessionLike & {
166
+ $clear: () => Promise<void>;
167
+ };
168
+ };
169
+ delete_chat_photo: {
170
+ session: SessionLike & {
171
+ $clear: () => Promise<void>;
172
+ };
173
+ };
174
+ group_chat_created: {
175
+ session: SessionLike & {
176
+ $clear: () => Promise<void>;
177
+ };
178
+ };
179
+ message_auto_delete_timer_changed: {
180
+ session: SessionLike & {
181
+ $clear: () => Promise<void>;
182
+ };
183
+ };
184
+ migrate_to_chat_id: {
185
+ session: SessionLike & {
186
+ $clear: () => Promise<void>;
187
+ };
188
+ };
189
+ migrate_from_chat_id: {
190
+ session: SessionLike & {
191
+ $clear: () => Promise<void>;
192
+ };
193
+ };
194
+ pinned_message: {
195
+ session: SessionLike & {
196
+ $clear: () => Promise<void>;
197
+ };
198
+ };
199
+ invoice: {
200
+ session: SessionLike & {
201
+ $clear: () => Promise<void>;
202
+ };
203
+ };
204
+ successful_payment: {
205
+ session: SessionLike & {
206
+ $clear: () => Promise<void>;
207
+ };
208
+ };
209
+ chat_shared: {
210
+ session: SessionLike & {
211
+ $clear: () => Promise<void>;
212
+ };
213
+ };
214
+ proximity_alert_triggered: {
215
+ session: SessionLike & {
216
+ $clear: () => Promise<void>;
217
+ };
218
+ };
219
+ video_chat_scheduled: {
220
+ session: SessionLike & {
221
+ $clear: () => Promise<void>;
222
+ };
223
+ };
224
+ video_chat_started: {
225
+ session: SessionLike & {
226
+ $clear: () => Promise<void>;
227
+ };
228
+ };
229
+ video_chat_ended: {
230
+ session: SessionLike & {
231
+ $clear: () => Promise<void>;
232
+ };
233
+ };
234
+ video_chat_participants_invited: {
235
+ session: SessionLike & {
236
+ $clear: () => Promise<void>;
237
+ };
238
+ };
239
+ web_app_data: {
240
+ session: SessionLike & {
241
+ $clear: () => Promise<void>;
242
+ };
243
+ };
244
+ location: {
245
+ session: SessionLike & {
246
+ $clear: () => Promise<void>;
247
+ };
248
+ };
249
+ passport_data: {
250
+ session: SessionLike & {
251
+ $clear: () => Promise<void>;
252
+ };
253
+ };
254
+ } & {
255
+ message: HistoryDerives;
256
+ callback_query: HistoryDerives;
257
+ }, {}>;
258
+ export {};
259
+ //# sourceMappingURL=message-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-history.d.ts","sourceRoot":"","sources":["../../src/bot/message-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAIzC,MAAM,MAAM,YAAY,GAAG;IACzB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,YAAY,EAAE,CAAA;CACtB,CAAA;AAED,0EAA0E;AAC1E,KAAK,WAAW,GAAG;IAAE,OAAO,CAAC,EAAE,aAAa,CAAA;CAAE,CAAA;AAE9C,wEAAwE;AACxE,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAA;AAEjF,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,OAAO,EAAE,uBAAuB,CAAA;IAChC,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;CAC9C,CAAA;AAID,KAAK,cAAc,GAAG;IACpB,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAA;CACrC,CAAA;AAgBD,eAAO,MAAM,cAAc,GAAI,MAAM,qBAAqB,KAAG,qBAW5D,CAAA;AAID,QAAA,MAAM,kBAAkB,GAAI,MAAM;IAChC,aAAa,EAAE,uBAAuB,CAAA;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;CACtB;YAGqD,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA8BnE,CAAA"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Per-user rolling message history for GramIO bots — opt-in, with
3
+ * retention.
4
+ *
5
+ * Follows the same shared-session pattern as `bot/language`: the
6
+ * user creates a `session()` once at bot level, and each feature
7
+ * plugin (including this one) declares it as a dependency. gramio
8
+ * dedupes the runtime extension; the types flow.
9
+ *
10
+ * ## What this plugin owns
11
+ *
12
+ * - Appends each incoming user message to `ctx.session.history.items`
13
+ * - Prunes entries older than `retentionDays` or beyond `maxMessages`
14
+ * - Exposes a read-only pruned snapshot at `ctx.history` for handlers
15
+ *
16
+ * ## GDPR caveat — retention IS personal data
17
+ *
18
+ * Unlike `bot/language` (preference data, covered by Telegram's standard
19
+ * privacy policy), retaining user **message content** is the one thing
20
+ * the [standard policy](https://telegram.org/privacy-tpa) does NOT cover
21
+ * by default. If you extend `messageHistory`, you should:
22
+ *
23
+ * 1. Set a custom `privacy` URL on your `botMenu` describing what you
24
+ * retain and for how long.
25
+ * 2. Pass `personalData: { storage }` to your `botMenu` so 🗑 Forget
26
+ * and 📥 Export buttons appear — letting users see and delete
27
+ * the data you keep about them.
28
+ *
29
+ * These are not enforced by this plugin (would couple it to menu); they
30
+ * are documented as the bot author's legal responsibility.
31
+ *
32
+ * ## Storage layout
33
+ *
34
+ * Lives entirely inside the shared session record:
35
+ *
36
+ * storage[String(senderId)] = {
37
+ * ...other plugins' fields,
38
+ * history: { items: [HistoryEntry, ...] }
39
+ * }
40
+ *
41
+ * Peer deps: `gramio`, `@gramio/session`.
42
+ *
43
+ * @example
44
+ * import { Bot } from 'gramio'
45
+ * import { session } from '@gramio/session'
46
+ * import { redisStorage } from '@gramio/storage-redis'
47
+ * import { messageHistory } from '@adriangalilea/utils/bot/message-history'
48
+ *
49
+ * const userSession = session({ storage: redisStorage(), key: 'session', initial: () => ({}) })
50
+ *
51
+ * const history = messageHistory({
52
+ * session: userSession,
53
+ * maxMessages: 100,
54
+ * retentionDays: 7,
55
+ * })
56
+ *
57
+ * const bot = new Bot(process.env.BOT_TOKEN!)
58
+ * .extend(userSession)
59
+ * .extend(history.plugin)
60
+ * .command('replay', (ctx) => {
61
+ * const last = ctx.history.slice(-3).map((e) => e.text).join('\n---\n')
62
+ * return ctx.send(last || '(no history)')
63
+ * })
64
+ */
65
+ import { Plugin } from 'gramio';
66
+ // ─── helpers ───────────────────────────────────────────────────────
67
+ const prune = (items, maxMessages, retentionDays) => {
68
+ const cutoffSec = Math.floor(Date.now() / 1000) - retentionDays * 86400;
69
+ const fresh = items.filter((e) => e.date >= cutoffSec);
70
+ return fresh.slice(-maxMessages);
71
+ };
72
+ // ─── feature factory ───────────────────────────────────────────────
73
+ export const messageHistory = (opts) => {
74
+ if (opts.maxMessages <= 0)
75
+ throw new Error('messageHistory: maxMessages must be > 0');
76
+ if (opts.retentionDays <= 0)
77
+ throw new Error('messageHistory: retentionDays must be > 0');
78
+ const plugin = buildHistoryPlugin({
79
+ sessionPlugin: opts.session,
80
+ maxMessages: opts.maxMessages,
81
+ retentionDays: opts.retentionDays,
82
+ });
83
+ return { plugin };
84
+ };
85
+ // ─── plugin builder ────────────────────────────────────────────────
86
+ const buildHistoryPlugin = (args) => {
87
+ const { sessionPlugin, maxMessages, retentionDays } = args;
88
+ return new Plugin('@adriangalilea/utils/bot/message-history')
89
+ .extend(sessionPlugin)
90
+ // Record incoming messages with text. Service messages, edits,
91
+ // and callback queries don't append — only direct user input.
92
+ .on('message', async (ctx, next) => {
93
+ if (ctx.text !== undefined) {
94
+ const entry = {
95
+ messageId: ctx.id,
96
+ date: ctx.payload.date,
97
+ text: ctx.text,
98
+ };
99
+ const cur = ctx.session.history?.items ?? [];
100
+ ctx.session.history = {
101
+ items: [...prune(cur, maxMessages, retentionDays), entry].slice(-maxMessages),
102
+ };
103
+ }
104
+ return next();
105
+ })
106
+ // Pruned read-only view for handlers downstream.
107
+ .derive(['message', 'callback_query'], (ctx) => ({
108
+ history: prune(ctx.session.history?.items ?? [], maxMessages, retentionDays),
109
+ }));
110
+ };
111
+ //# sourceMappingURL=message-history.js.map