telemeister 0.1.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 (133) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +460 -0
  3. package/bin/telemeister.js +18 -0
  4. package/dist/bot/polling.d.ts +20 -0
  5. package/dist/bot/polling.d.ts.map +1 -0
  6. package/dist/bot/polling.js +115 -0
  7. package/dist/bot/polling.js.map +1 -0
  8. package/dist/bot/session.d.ts +50 -0
  9. package/dist/bot/session.d.ts.map +1 -0
  10. package/dist/bot/session.js +92 -0
  11. package/dist/bot/session.js.map +1 -0
  12. package/dist/bot/webhook.d.ts +36 -0
  13. package/dist/bot/webhook.d.ts.map +1 -0
  14. package/dist/bot/webhook.js +199 -0
  15. package/dist/bot/webhook.js.map +1 -0
  16. package/dist/bot-state-types.d.ts +10 -0
  17. package/dist/bot-state-types.d.ts.map +1 -0
  18. package/dist/bot-state-types.js +3 -0
  19. package/dist/bot-state-types.js.map +1 -0
  20. package/dist/cli/cli.d.ts +14 -0
  21. package/dist/cli/cli.d.ts.map +1 -0
  22. package/dist/cli/cli.js +57 -0
  23. package/dist/cli/cli.js.map +1 -0
  24. package/dist/cli/create-bot.d.ts +5 -0
  25. package/dist/cli/create-bot.d.ts.map +1 -0
  26. package/dist/cli/create-bot.js +275 -0
  27. package/dist/cli/create-bot.js.map +1 -0
  28. package/dist/cli/index.d.ts +4 -0
  29. package/dist/cli/index.d.ts.map +1 -0
  30. package/dist/cli/index.js +4 -0
  31. package/dist/cli/index.js.map +1 -0
  32. package/dist/cli/state-manager.d.ts +9 -0
  33. package/dist/cli/state-manager.d.ts.map +1 -0
  34. package/dist/cli/state-manager.js +381 -0
  35. package/dist/cli/state-manager.js.map +1 -0
  36. package/dist/config.d.ts +24 -0
  37. package/dist/config.d.ts.map +1 -0
  38. package/dist/config.js +52 -0
  39. package/dist/config.js.map +1 -0
  40. package/dist/core/app-states.d.ts +8 -0
  41. package/dist/core/app-states.d.ts.map +1 -0
  42. package/dist/core/app-states.js +8 -0
  43. package/dist/core/app-states.js.map +1 -0
  44. package/dist/core/builder.d.ts +138 -0
  45. package/dist/core/builder.d.ts.map +1 -0
  46. package/dist/core/builder.js +195 -0
  47. package/dist/core/builder.js.map +1 -0
  48. package/dist/core/compact-machine.d.ts +57 -0
  49. package/dist/core/compact-machine.d.ts.map +1 -0
  50. package/dist/core/compact-machine.js +113 -0
  51. package/dist/core/compact-machine.js.map +1 -0
  52. package/dist/core/index.d.ts +13 -0
  53. package/dist/core/index.d.ts.map +1 -0
  54. package/dist/core/index.js +9 -0
  55. package/dist/core/index.js.map +1 -0
  56. package/dist/core/types.d.ts +47 -0
  57. package/dist/core/types.d.ts.map +1 -0
  58. package/dist/core/types.js +3 -0
  59. package/dist/core/types.js.map +1 -0
  60. package/dist/database.d.ts +43 -0
  61. package/dist/database.d.ts.map +1 -0
  62. package/dist/database.js +127 -0
  63. package/dist/database.js.map +1 -0
  64. package/dist/generated/prisma/browser.d.ts +15 -0
  65. package/dist/generated/prisma/browser.d.ts.map +1 -0
  66. package/dist/generated/prisma/browser.js +18 -0
  67. package/dist/generated/prisma/browser.js.map +1 -0
  68. package/dist/generated/prisma/client.d.ts +32 -0
  69. package/dist/generated/prisma/client.d.ts.map +1 -0
  70. package/dist/generated/prisma/client.js +33 -0
  71. package/dist/generated/prisma/client.js.map +1 -0
  72. package/dist/generated/prisma/commonInputTypes.d.ts +166 -0
  73. package/dist/generated/prisma/commonInputTypes.d.ts.map +1 -0
  74. package/dist/generated/prisma/commonInputTypes.js +11 -0
  75. package/dist/generated/prisma/commonInputTypes.js.map +1 -0
  76. package/dist/generated/prisma/enums.d.ts +2 -0
  77. package/dist/generated/prisma/enums.d.ts.map +1 -0
  78. package/dist/generated/prisma/enums.js +11 -0
  79. package/dist/generated/prisma/enums.js.map +1 -0
  80. package/dist/generated/prisma/internal/class.d.ts +138 -0
  81. package/dist/generated/prisma/internal/class.d.ts.map +1 -0
  82. package/dist/generated/prisma/internal/class.js +50 -0
  83. package/dist/generated/prisma/internal/class.js.map +1 -0
  84. package/dist/generated/prisma/internal/prismaNamespace.d.ts +591 -0
  85. package/dist/generated/prisma/internal/prismaNamespace.d.ts.map +1 -0
  86. package/dist/generated/prisma/internal/prismaNamespace.js +96 -0
  87. package/dist/generated/prisma/internal/prismaNamespace.js.map +1 -0
  88. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +56 -0
  89. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -0
  90. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +67 -0
  91. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -0
  92. package/dist/generated/prisma/models/User.d.ts +1181 -0
  93. package/dist/generated/prisma/models/User.d.ts.map +1 -0
  94. package/dist/generated/prisma/models/User.js +2 -0
  95. package/dist/generated/prisma/models/User.js.map +1 -0
  96. package/dist/generated/prisma/models/UserInfo.d.ts +1101 -0
  97. package/dist/generated/prisma/models/UserInfo.d.ts.map +1 -0
  98. package/dist/generated/prisma/models/UserInfo.js +2 -0
  99. package/dist/generated/prisma/models/UserInfo.js.map +1 -0
  100. package/dist/generated/prisma/models.d.ts +4 -0
  101. package/dist/generated/prisma/models.d.ts.map +1 -0
  102. package/dist/generated/prisma/models.js +2 -0
  103. package/dist/generated/prisma/models.js.map +1 -0
  104. package/dist/handlers/idle/index.d.ts +2 -0
  105. package/dist/handlers/idle/index.d.ts.map +1 -0
  106. package/dist/handlers/idle/index.js +22 -0
  107. package/dist/handlers/idle/index.js.map +1 -0
  108. package/dist/handlers/index.d.ts +12 -0
  109. package/dist/handlers/index.d.ts.map +1 -0
  110. package/dist/handlers/index.js +14 -0
  111. package/dist/handlers/index.js.map +1 -0
  112. package/dist/handlers/menu/index.d.ts +2 -0
  113. package/dist/handlers/menu/index.d.ts.map +1 -0
  114. package/dist/handlers/menu/index.js +35 -0
  115. package/dist/handlers/menu/index.js.map +1 -0
  116. package/dist/handlers/menu.d.ts +2 -0
  117. package/dist/handlers/menu.d.ts.map +1 -0
  118. package/dist/handlers/menu.js +37 -0
  119. package/dist/handlers/menu.js.map +1 -0
  120. package/dist/handlers/welcome/index.d.ts +2 -0
  121. package/dist/handlers/welcome/index.d.ts.map +1 -0
  122. package/dist/handlers/welcome/index.js +22 -0
  123. package/dist/handlers/welcome/index.js.map +1 -0
  124. package/dist/handlers/welcome.d.ts +2 -0
  125. package/dist/handlers/welcome.d.ts.map +1 -0
  126. package/dist/handlers/welcome.js +30 -0
  127. package/dist/handlers/welcome.js.map +1 -0
  128. package/dist/index.d.ts +3 -0
  129. package/dist/index.d.ts.map +1 -0
  130. package/dist/index.js +39 -0
  131. package/dist/index.js.map +1 -0
  132. package/package.json +111 -0
  133. package/templates/handler.ts.ejs +36 -0
package/README.md ADDED
@@ -0,0 +1,460 @@
1
+ # Telemeister
2
+
3
+ A TypeScript Telegram Bot Framework with [Grammy](https://grammy.dev), XState-powered Finite State Machines (FSM), Prisma ORM for persistence, and a type-safe builder pattern for defining conversation flows.
4
+
5
+ **Goal**: Build bot infrastructure with explicit structure that allows an LLM to build and verify bots from text descriptions, and detect inconsistencies in those descriptions.
6
+
7
+ ## Features
8
+
9
+ - **NPM Package**: Install as a dependency to any bot project
10
+ - **Project Scaffolding**: `npx telemeister create-bot` creates new projects
11
+ - **Grammy Bot Framework**: Modern, TypeScript-first Telegram Bot API library
12
+ - **XState FSM**: Compact, maintainable state machines using XState's "states as data" pattern
13
+ - **Type-Safe State Transitions**: Full TypeScript support with strict transition types
14
+ - **State Machine Configuration**: JSON-based state machine definition (`bot.json`)
15
+ - **Auto-Generated Types**: TypeScript types generated from state machine config
16
+ - **State Diagram Visualization**: Mermaid diagrams (MD + PNG) auto-generated
17
+ - **Prisma ORM 7.x**: Modern database toolkit with driver adapters for SQLite and MySQL
18
+ - **Single Schema**: One Prisma schema works for both SQLite (dev) and MySQL (production)
19
+ - **Builder Pattern**: Fluent API for defining state handlers
20
+ - **Dual Mode**: Supports both Polling and Webhook modes
21
+ - **CLI Tools**: Built-in commands for managing states, transitions, and webhooks
22
+
23
+ ## Quick Start
24
+
25
+ ### Create a New Bot
26
+
27
+ ```bash
28
+ npx telemeister create-bot my-bot
29
+ cd my-bot
30
+ npm install
31
+ ```
32
+
33
+ ### Environment Setup
34
+
35
+ ```bash
36
+ cp .env.example .env
37
+ # Edit .env with your credentials
38
+ ```
39
+
40
+ Required environment variables:
41
+ ```env
42
+ BOT_TOKEN=your_bot_token # From @BotFather (https://t.me/BotFather)
43
+
44
+ # Database Configuration
45
+ # For SQLite (development):
46
+ DATABASE_URL="file:./dev.db"
47
+
48
+ # For MySQL (production):
49
+ # DATABASE_URL="mysql://user:password@localhost:3306/dbname"
50
+ ```
51
+
52
+ ### Database Setup
53
+
54
+ **Generate Prisma Client:**
55
+ ```bash
56
+ npm run db:generate
57
+ ```
58
+
59
+ **Run Migrations:**
60
+ ```bash
61
+ # Development (SQLite)
62
+ npm run db:migrate
63
+
64
+ # Production (MySQL) - after updating DATABASE_URL
65
+ npm run db:deploy
66
+ ```
67
+
68
+ ### Run the Bot
69
+
70
+ **Polling mode (development):**
71
+ ```bash
72
+ npm run dev
73
+ ```
74
+
75
+ **Webhook mode (production):**
76
+ ```bash
77
+ # Set webhook URL first
78
+ npm run webhook:set -- https://your-domain.com/webhook
79
+
80
+ # Start in webhook mode
81
+ BOT_MODE=webhook npm run dev
82
+ ```
83
+
84
+ ## Project Structure
85
+
86
+ ```
87
+ my-bot/
88
+ ├── bot.json # State machine configuration (source of truth, gitignored)
89
+ ├── src/
90
+ │ ├── bot-state-types.ts # Auto-generated types (DO NOT EDIT)
91
+ │ ├── bot-diagram.md # Auto-generated Mermaid diagram
92
+ │ ├── bot-diagram.png # Auto-generated diagram image
93
+ │ ├── handlers/ # Your state handlers
94
+ │ │ ├── index.ts # Handler imports
95
+ │ │ ├── idle/ # Idle state handler
96
+ │ │ ├── welcome/ # Welcome state handler
97
+ │ │ └── menu/ # Menu state handler
98
+ │ └── index.ts # Bot entry point
99
+ ├── prisma/
100
+ │ └── schema.prisma # Database schema
101
+ ├── .env # Environment variables (gitignored)
102
+ └── package.json
103
+ ```
104
+
105
+ ## State Management
106
+
107
+ ### State Machine Configuration
108
+
109
+ The `bot.json` file is the source of truth for your state machine:
110
+
111
+ ```json
112
+ {
113
+ "idle": ["welcome"],
114
+ "welcome": ["menu"],
115
+ "menu": ["welcome", "idle"]
116
+ }
117
+ ```
118
+
119
+ Each key is a state, and the array contains valid transition targets.
120
+
121
+ ### CLI Commands
122
+
123
+ | Command | Description |
124
+ |---------|-------------|
125
+ | `telemeister state:add <name>` | Add a new state + create handler |
126
+ | `telemeister state:delete <name>` | Delete a state (with safety checks) |
127
+ | `telemeister state:sync` | Sync types + create missing handlers |
128
+ | `telemeister state:transition:add <from> <to>` | Add a transition |
129
+ | `telemeister state:transition:delete <from> <to>` | Delete a transition |
130
+
131
+ Or use npm scripts:
132
+ ```bash
133
+ npm run state:add -- settings
134
+ npm run state:sync
135
+ ```
136
+
137
+ ### Adding a New State
138
+
139
+ ```bash
140
+ telemeister state:add collectEmail
141
+ ```
142
+
143
+ This command:
144
+ - Adds `"collectEmail": []` to `bot.json`
145
+ - Creates `src/handlers/collectEmail/index.ts` with a template
146
+ - Updates `src/handlers/index.ts` with the import
147
+ - Regenerates `src/bot-state-types.ts`
148
+ - Regenerates `src/bot-diagram.md` and `src/bot-diagram.png`
149
+
150
+ ### Adding Transitions
151
+
152
+ ```bash
153
+ telemeister state:transition:add collectEmail completed
154
+ ```
155
+
156
+ This updates `bot.json`, regenerates types and diagrams.
157
+
158
+ ### Deleting States
159
+
160
+ Safety checks prevent accidental deletion:
161
+ - Cannot delete if handler folder is non-empty
162
+ - Cannot delete if state has outgoing transitions
163
+ - Cannot delete if state has incoming transitions
164
+
165
+ ```bash
166
+ # Remove transitions first
167
+ telemeister state:transition:delete collectEmail completed
168
+
169
+ # Then empty the handler folder or move files
170
+ rm -rf src/handlers/collectEmail
171
+
172
+ # Now delete the state
173
+ telemeister state:delete collectEmail
174
+ ```
175
+
176
+ ### Syncing
177
+
178
+ ```bash
179
+ telemeister state:sync
180
+ ```
181
+
182
+ This regenerates:
183
+ - `src/bot-state-types.ts` - TypeScript types from `bot.json`
184
+ - `src/bot-diagram.md` - Mermaid diagram
185
+ - `src/bot-diagram.png` - PNG image (requires mermaid-cli)
186
+ - Creates missing handler folders (never overwrites existing)
187
+
188
+ ## Auto-Generated Types
189
+
190
+ The `src/bot-state-types.ts` file is auto-generated:
191
+
192
+ ```typescript
193
+ // Auto-generated by state:sync - DO NOT EDIT
194
+
195
+ export type AppStates = 'idle' | 'menu' | 'welcome';
196
+
197
+ export type StateTransitions = {
198
+ idle: 'welcome' | void;
199
+ menu: 'idle' | 'welcome' | void;
200
+ welcome: 'menu' | void;
201
+ };
202
+
203
+ export type IdleTransitions = Promise<StateTransitions['idle']>;
204
+ export type MenuTransitions = Promise<StateTransitions['menu']>;
205
+ export type WelcomeTransitions = Promise<StateTransitions['welcome']>;
206
+ ```
207
+
208
+ ## Handler API
209
+
210
+ ### Strict Transition Types
211
+
212
+ Handlers use generated types for strict return type checking:
213
+
214
+ ```typescript
215
+ import { appBuilder, type AppContext } from 'telemeister/core';
216
+ import type { MenuTransitions } from './bot-state-types.js';
217
+
218
+ appBuilder
219
+ .forState('menu')
220
+ .onEnter(async (context: AppContext): MenuTransitions => {
221
+ await context.send('Welcome to menu!');
222
+ // Can only return 'idle', 'welcome', or void
223
+ })
224
+ .onResponse(async (context: AppContext, response): MenuTransitions => {
225
+ if (response === 'back') return 'welcome'; // ✅ Valid
226
+ if (response === 'exit') return 'idle'; // ✅ Valid
227
+ return 'invalid'; // ❌ Type error - not in transitions
228
+ });
229
+ ```
230
+
231
+ ### Context Methods
232
+
233
+ ```typescript
234
+ interface BotHandlerContext<TState> {
235
+ // User info
236
+ userId: number;
237
+ telegramId: number;
238
+ chatId: number;
239
+ currentState: TState;
240
+
241
+ // Messaging
242
+ send: (text: string) => Promise<unknown>;
243
+
244
+ // Data persistence (per-user)
245
+ setData: <T>(key: string, value: T) => void;
246
+ getData: <T>(key: string) => T | undefined;
247
+
248
+ // State transition
249
+ transition: (toState: TState) => Promise<void>;
250
+ }
251
+ ```
252
+
253
+ ### Handler Types
254
+
255
+ ```typescript
256
+ // Called when entering a state
257
+ .onEnter(async (context) => {
258
+ await context.send("Welcome!");
259
+ // Optionally return a state for immediate transition
260
+ return "anotherState";
261
+ })
262
+
263
+ // Called when user sends a message
264
+ .onResponse(async (context, response) => {
265
+ // Return state name to transition, or void/undefined to stay
266
+ if (response === "yes") return "confirmed";
267
+ return "cancelled";
268
+ })
269
+ ```
270
+
271
+ ## State Diagram
272
+
273
+ Auto-generated visualizations are updated on every state/transition change:
274
+
275
+ **`src/bot-diagram.md`:**
276
+ ```markdown
277
+ # Bot State Diagram
278
+
279
+ ```mermaid
280
+ stateDiagram-v2
281
+ idle --> welcome
282
+ welcome --> menu
283
+ menu --> welcome
284
+ menu --> idle
285
+ ```
286
+ ```
287
+
288
+ **`src/bot-diagram.png`:** PNG image rendered by mermaid-cli.
289
+
290
+ ## Database Configuration
291
+
292
+ ### Switching Between SQLite and MySQL
293
+
294
+ **1. Update `prisma/schema.prisma`:**
295
+ ```prisma
296
+ datasource db {
297
+ provider = "sqlite" // Change to "mysql" for production
298
+ }
299
+ ```
300
+
301
+ **2. Update `.env`:**
302
+ ```bash
303
+ # SQLite (development)
304
+ DATABASE_URL="file:./dev.db"
305
+
306
+ # MySQL (production)
307
+ DATABASE_URL="mysql://user:password@localhost:3306/dbname"
308
+ ```
309
+
310
+ **3. Regenerate and migrate:**
311
+ ```bash
312
+ npm run db:generate
313
+ npm run db:migrate
314
+ ```
315
+
316
+ ### Database Commands
317
+
318
+ ```bash
319
+ npm run db:generate # Generate Prisma Client after schema changes
320
+ npm run db:migrate # Create and apply migrations (development)
321
+ npm run db:deploy # Apply migrations in production
322
+ npm run db:push # Push schema changes without migration files
323
+ npm run db:studio # Open Prisma Studio (database GUI)
324
+ ```
325
+
326
+ ## Webhook Commands
327
+
328
+ ```bash
329
+ # Set webhook URL
330
+ npm run webhook:set -- https://your-domain.com/webhook
331
+
332
+ # Check webhook info
333
+ npm run webhook:info
334
+
335
+ # Delete webhook (switch back to polling)
336
+ npm run webhook:delete
337
+ ```
338
+
339
+ ## Database Schema
340
+
341
+ Users are persisted with:
342
+ - `telegramId` - Telegram user ID
343
+ - `chatId` - Telegram chat ID
344
+ - `currentState` - Current FSM state
345
+ - `stateData` - JSON data storage for user context (in separate `userInfo` relation)
346
+
347
+ ### Prisma Schema
348
+
349
+ ```prisma
350
+ model User {
351
+ id Int @id @default(autoincrement())
352
+ telegramId Int @unique
353
+ chatId Int
354
+ currentState String @default("idle")
355
+ updatedAt DateTime @updatedAt
356
+ info UserInfo?
357
+
358
+ @@index([currentState])
359
+ }
360
+
361
+ model UserInfo {
362
+ id Int @id @default(autoincrement())
363
+ userId Int @unique
364
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
365
+ stateData String @default("{}")
366
+ }
367
+ ```
368
+
369
+ ## Architecture
370
+
371
+ ### State Persistence Flow
372
+
373
+ ```
374
+ User sends message
375
+
376
+ Load user from DB (by telegramId)
377
+
378
+ Execute onResponse for current state
379
+
380
+ Handler returns nextState (or void)
381
+
382
+ Update DB with new state
383
+
384
+ Execute onEnter for new state
385
+
386
+ Send prompt to user
387
+ ```
388
+
389
+ ### Compact FSM Pattern
390
+
391
+ Instead of defining every state in XState:
392
+
393
+ ```typescript
394
+ // Traditional - verbose
395
+ states: {
396
+ idle: { on: { START: 'welcome' } },
397
+ welcome: { on: { NEXT: 'menu' } },
398
+ // ... every state
399
+ }
400
+
401
+ // Telemeister - compact
402
+ states: {
403
+ active: {
404
+ on: {
405
+ TRANSITION: {
406
+ actions: assign({ currentState: ({ event }) => event.toState }),
407
+ target: 'active',
408
+ reenter: true, // Triggers onEnter
409
+ }
410
+ }
411
+ }
412
+ }
413
+ ```
414
+
415
+ The actual state value is stored in `context.currentState`. The `bot.json` file is the source of truth for valid states and transitions.
416
+
417
+ ## Development
418
+
419
+ ### Developing the Telemeister Framework
420
+
421
+ This repository contains the Telemeister framework source code.
422
+
423
+ ```bash
424
+ # Clone and install
425
+ git clone <repo>
426
+ cd telemeister
427
+ npm install
428
+
429
+ # Build
430
+ npm run build
431
+
432
+ # Run CLI locally
433
+ npm run telemeister:state:add -- settings
434
+
435
+ # Or use tsx directly
436
+ npx tsx src/cli/cli.ts state:add settings
437
+ ```
438
+
439
+ ### Publishing
440
+
441
+ ```bash
442
+ npm run build
443
+ npm version patch
444
+ npm publish
445
+ ```
446
+
447
+ ## Technology Stack
448
+
449
+ - **[Grammy](https://grammy.dev)**: Modern Telegram Bot API framework with excellent TypeScript support
450
+ - **[XState](https://stately.ai/docs/xstate)**: State machines for complex conversation flows
451
+ - **[Prisma ORM 7.x](https://prisma.io)**: Database toolkit with driver adapters
452
+ - **Driver Adapters**: Required adapters for database connections (`@prisma/adapter-better-sqlite3`, `@prisma/adapter-mariadb`)
453
+ - **ESM-Only**: Native ES module support
454
+ - **Generated Client in Source**: Better IDE support and file watching
455
+ - **[EJS](https://ejs.co)**: Template engine for handler generation
456
+ - **[Mermaid CLI](https://github.com/mermaid-js/mermaid-cli)**: Diagram generation
457
+
458
+ ## License
459
+
460
+ MIT
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Telemeister CLI Entry Point
5
+ */
6
+
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname, join } from 'path';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ const cliPath = join(__dirname, '..', 'dist', 'cli', 'index.js');
14
+
15
+ import(cliPath).catch((err) => {
16
+ console.error('Failed to load CLI:', err.message);
17
+ process.exit(1);
18
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Grammy-based Polling Mode Implementation
3
+ *
4
+ * Uses Grammy Bot API library with database-backed sessions.
5
+ */
6
+ import { Bot, type Context } from 'grammy';
7
+ import { type SessionData } from './session.js';
8
+ interface BotContext extends Context {
9
+ session: SessionData;
10
+ }
11
+ /**
12
+ * Create and configure the Grammy bot
13
+ */
14
+ export declare function createBot(botToken: string): Bot<BotContext>;
15
+ /**
16
+ * Start the bot in polling mode
17
+ */
18
+ export declare function startPollingMode(botToken: string): Promise<void>;
19
+ export {};
20
+ //# sourceMappingURL=polling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling.d.ts","sourceRoot":"","sources":["../../src/bot/polling.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,GAAG,EAAW,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGpD,OAAO,EAA4C,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAI1F,UAAU,UAAW,SAAQ,OAAO;IAClC,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,CAyD3D;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtE"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Grammy-based Polling Mode Implementation
3
+ *
4
+ * Uses Grammy Bot API library with database-backed sessions.
5
+ */
6
+ import { Bot, session } from 'grammy';
7
+ import { appBuilder } from '../core/index.js';
8
+ import { PrismaSessionAdapter, getOrCreateSession } from './session.js';
9
+ /**
10
+ * Create and configure the Grammy bot
11
+ */
12
+ export function createBot(botToken) {
13
+ const bot = new Bot(botToken);
14
+ // Install session middleware with Prisma adapter
15
+ bot.use(session({
16
+ initial: () => ({
17
+ currentState: 'idle',
18
+ stateData: {},
19
+ }),
20
+ storage: new PrismaSessionAdapter(),
21
+ getSessionKey: (ctx) => ctx.from?.id.toString(),
22
+ }));
23
+ // Ensure user exists in database on each update
24
+ bot.use(async (ctx, next) => {
25
+ if (!ctx.from || !ctx.chat) {
26
+ return next();
27
+ }
28
+ const telegramId = ctx.from.id;
29
+ const chatId = ctx.chat.id;
30
+ // Get or create user session
31
+ const userSession = await getOrCreateSession(telegramId, chatId);
32
+ ctx.session = userSession;
33
+ return next();
34
+ });
35
+ // Handle text messages
36
+ bot.on('message:text', async (ctx) => {
37
+ const text = ctx.message.text;
38
+ const session = ctx.session;
39
+ // Create handler context compatible with existing handlers
40
+ const handlerContext = createHandlerContext(ctx, session);
41
+ // Execute onResponse handler for current state
42
+ const nextState = await appBuilder.executeOnResponse(session.currentState, handlerContext, text);
43
+ // Handle state transition
44
+ if (nextState && nextState !== session.currentState) {
45
+ await transitionToState(ctx, session, nextState, handlerContext);
46
+ }
47
+ else {
48
+ // Save any state data changes
49
+ session.stateData =
50
+ handlerContext.getData('__all') || session.stateData;
51
+ }
52
+ });
53
+ return bot;
54
+ }
55
+ /**
56
+ * Start the bot in polling mode
57
+ */
58
+ export async function startPollingMode(botToken) {
59
+ const bot = createBot(botToken);
60
+ console.log('🤖 Bot started in polling mode');
61
+ // Start polling
62
+ await bot.start({
63
+ onStart: () => {
64
+ console.log('✅ Bot is running and polling for updates...');
65
+ },
66
+ });
67
+ }
68
+ /**
69
+ * Create a handler context compatible with existing handlers
70
+ */
71
+ function createHandlerContext(ctx, session) {
72
+ // Local state data copy for modifications
73
+ const localStateData = { ...session.stateData };
74
+ return {
75
+ userId: session.userId || 0,
76
+ telegramId: ctx.from?.id || 0,
77
+ chatId: ctx.chat?.id || 0,
78
+ currentState: session.currentState,
79
+ send: async (text) => {
80
+ await ctx.reply(text, { parse_mode: 'Markdown' });
81
+ },
82
+ setData: (key, value) => {
83
+ localStateData[key] = value;
84
+ },
85
+ getData: (key) => {
86
+ if (key === '__all') {
87
+ return localStateData;
88
+ }
89
+ return localStateData[key];
90
+ },
91
+ transition: async (toState) => {
92
+ await transitionToState(ctx, session, toState, createHandlerContext(ctx, session));
93
+ },
94
+ };
95
+ }
96
+ /**
97
+ * Transition to a new state and execute onEnter handler
98
+ */
99
+ async function transitionToState(ctx, session, toState, handlerContext) {
100
+ // Update session state
101
+ session.currentState = toState;
102
+ // Execute onEnter handler for new state
103
+ const enterNextState = await appBuilder.executeOnEnter(toState, handlerContext);
104
+ // Save state data changes
105
+ session.stateData = handlerContext.getData('__all') || session.stateData;
106
+ // Handle chained transition from onEnter
107
+ if (enterNextState && enterNextState !== toState) {
108
+ // Create fresh context for the next state
109
+ const nextContext = createHandlerContext(ctx, session);
110
+ const nextState = enterNextState;
111
+ nextContext.currentState = nextState;
112
+ await transitionToState(ctx, session, nextState, nextContext);
113
+ }
114
+ }
115
+ //# sourceMappingURL=polling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling.js","sourceRoot":"","sources":["../../src/bot/polling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Grammy Session Adapter for Prisma Database
3
+ *
4
+ * This adapter integrates Grammy's session system with the Prisma database,
5
+ * allowing user state to persist across restarts.
6
+ */
7
+ import type { StorageAdapter } from 'grammy';
8
+ /**
9
+ * Session data stored per user
10
+ */
11
+ export interface SessionData {
12
+ /** Current FSM state */
13
+ currentState: string;
14
+ /** User-specific data storage */
15
+ stateData: Record<string, unknown>;
16
+ /** Internal user ID from database */
17
+ userId?: number;
18
+ /** Telegram chat ID */
19
+ chatId?: number;
20
+ }
21
+ /**
22
+ * Prisma-backed session storage adapter for Grammy
23
+ *
24
+ * This adapter loads/saves session data from/to the database,
25
+ * keyed by Telegram user ID.
26
+ */
27
+ export declare class PrismaSessionAdapter implements StorageAdapter<SessionData> {
28
+ /**
29
+ * Read session data from database
30
+ * @param key - Telegram user ID (as string)
31
+ */
32
+ read(key: string): Promise<SessionData | undefined>;
33
+ /**
34
+ * Write session data to database
35
+ * @param key - Telegram user ID (as string)
36
+ * @param value - Session data to save
37
+ */
38
+ write(key: string, value: SessionData): Promise<void>;
39
+ /**
40
+ * Delete session data (not typically used in bots)
41
+ * @param key - Telegram user ID (as string)
42
+ */
43
+ delete(key: string): Promise<void>;
44
+ }
45
+ /**
46
+ * Get or create user session
47
+ * This helper ensures a user exists in the database before processing
48
+ */
49
+ export declare function getOrCreateSession(telegramId: number, chatId: number): Promise<SessionData>;
50
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/bot/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAG7C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,wBAAwB;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,YAAW,cAAc,CAAC,WAAW,CAAC;IACtE;;;OAGG;IACG,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAmBzD;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3D;;;OAGG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CASzC;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA8BjG"}