telemeister 0.2.8 → 0.2.10
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/bin/telemeister-cli.js +7 -1
- package/dist/cli/create-bot.d.ts.map +1 -1
- package/dist/cli/create-bot.js +7 -5
- package/dist/cli/create-bot.js.map +1 -1
- package/dist/core/builder.d.ts +8 -8
- package/dist/core/builder.js +8 -8
- package/dist/templates/README.md.ejs +6 -6
- package/dist/templates/handler.ts.ejs +19 -20
- package/dist/templates/package.json.ejs +1 -1
- package/package.json +2 -2
- package/src/templates/package.json.ejs +1 -1
- package/dist/templates/templates/README.md.ejs +0 -286
- package/dist/templates/templates/bot.json.ejs +0 -5
- package/dist/templates/templates/database-example.ts.ejs +0 -138
- package/dist/templates/templates/database.ts.ejs +0 -206
- package/dist/templates/templates/env.example.ejs +0 -5
- package/dist/templates/templates/gitignore.ejs +0 -21
- package/dist/templates/templates/handler.ts.ejs +0 -114
- package/dist/templates/templates/index.ts.ejs +0 -53
- package/dist/templates/templates/package.json.ejs +0 -41
- package/dist/templates/templates/prisma-schema.prisma.ejs +0 -29
- package/dist/templates/templates/prisma.config.ts.ejs +0 -9
- package/dist/templates/templates/tsconfig.json.ejs +0 -19
package/bin/telemeister-cli.js
CHANGED
|
@@ -1069,6 +1069,12 @@ function getPackageRoot2() {
|
|
|
1069
1069
|
}
|
|
1070
1070
|
return path3.join(currentDir, "..");
|
|
1071
1071
|
}
|
|
1072
|
+
function getPackageVersion() {
|
|
1073
|
+
const packageRoot = getPackageRoot2();
|
|
1074
|
+
const packageJsonPath = path3.join(packageRoot, "package.json");
|
|
1075
|
+
const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
|
|
1076
|
+
return packageJson.version;
|
|
1077
|
+
}
|
|
1072
1078
|
function loadTemplate(templateName) {
|
|
1073
1079
|
const packageRoot = getPackageRoot2();
|
|
1074
1080
|
const templatePath = path3.join(packageRoot, "dist", "templates", templateName);
|
|
@@ -1119,7 +1125,7 @@ async function createBot(botName) {
|
|
|
1119
1125
|
fs3.mkdirSync(path3.join(targetDir, "src", "lib"), { recursive: true });
|
|
1120
1126
|
fs3.writeFileSync(path3.join(targetDir, "src", "lib", "database.ts"), loadTemplate("database.ts.ejs"));
|
|
1121
1127
|
fs3.writeFileSync(path3.join(targetDir, "README.md"), renderTemplate("README.md.ejs", { botName }));
|
|
1122
|
-
fs3.writeFileSync(path3.join(targetDir, "package.json"), renderTemplate("package.json.ejs", { botName }));
|
|
1128
|
+
fs3.writeFileSync(path3.join(targetDir, "package.json"), renderTemplate("package.json.ejs", { botName, telemeisterVersion: getPackageVersion() }));
|
|
1123
1129
|
process.chdir(targetDir);
|
|
1124
1130
|
await stateSync();
|
|
1125
1131
|
console.log("\n\u{1F4E6} Installing dependencies...");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-bot.d.ts","sourceRoot":"","sources":["../../src/cli/create-bot.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"create-bot.d.ts","sourceRoot":"","sources":["../../src/cli/create-bot.ts"],"names":[],"mappings":"AAAA;;GAEG;AA+CH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA6G1E"}
|
package/dist/cli/create-bot.js
CHANGED
|
@@ -8,18 +8,20 @@ import { execSync } from 'child_process';
|
|
|
8
8
|
import ejs from 'ejs';
|
|
9
9
|
import { stateSync } from './state-manager.js';
|
|
10
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
// Get the package root directory (works for both dist/cli/ and bin/ locations)
|
|
12
11
|
function getPackageRoot() {
|
|
13
|
-
// __dirname is either dist/cli/ (from tsc) or bin/ (from bundle)
|
|
14
12
|
const currentDir = __dirname;
|
|
15
|
-
// If we're in dist/cli/, go up 2 levels to get package root
|
|
16
|
-
// If we're in bin/, go up 1 level to get package root
|
|
17
13
|
const baseName = path.basename(currentDir);
|
|
18
14
|
if (baseName === 'cli' || baseName === 'dist') {
|
|
19
15
|
return path.join(currentDir, '..', '..');
|
|
20
16
|
}
|
|
21
17
|
return path.join(currentDir, '..');
|
|
22
18
|
}
|
|
19
|
+
function getPackageVersion() {
|
|
20
|
+
const packageRoot = getPackageRoot();
|
|
21
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
22
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
23
|
+
return packageJson.version;
|
|
24
|
+
}
|
|
23
25
|
function loadTemplate(templateName) {
|
|
24
26
|
const packageRoot = getPackageRoot();
|
|
25
27
|
const templatePath = path.join(packageRoot, 'dist', 'templates', templateName);
|
|
@@ -75,7 +77,7 @@ export async function createBot(botName) {
|
|
|
75
77
|
// Note: Bot runtime files (session.ts, polling.ts, webhook.ts) are now provided by the framework
|
|
76
78
|
// in 'telemeister/core/bot' and don't need to be generated
|
|
77
79
|
fs.writeFileSync(path.join(targetDir, 'README.md'), renderTemplate('README.md.ejs', { botName }));
|
|
78
|
-
fs.writeFileSync(path.join(targetDir, 'package.json'), renderTemplate('package.json.ejs', { botName }));
|
|
80
|
+
fs.writeFileSync(path.join(targetDir, 'package.json'), renderTemplate('package.json.ejs', { botName, telemeisterVersion: getPackageVersion() }));
|
|
79
81
|
// Sync handlers and types from bot.json
|
|
80
82
|
process.chdir(targetDir);
|
|
81
83
|
await stateSync();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-bot.js","sourceRoot":"","sources":["../../src/cli/create-bot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D
|
|
1
|
+
{"version":3,"file":"create-bot.js","sourceRoot":"","sources":["../../src/cli/create-bot.ts"],"names":[],"mappings":"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"}
|
package/dist/core/builder.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ declare class StateBuilder<TState extends BotState = BotState> {
|
|
|
14
14
|
* Can optionally return a state name to immediately transition to
|
|
15
15
|
*
|
|
16
16
|
* Example:
|
|
17
|
-
* .onEnter(async (
|
|
18
|
-
* await ctx.
|
|
17
|
+
* .onEnter(async (context) => {
|
|
18
|
+
* await context.ctx.reply('Welcome!');
|
|
19
19
|
* // Optionally transition immediately:
|
|
20
20
|
* return 'anotherState';
|
|
21
21
|
* })
|
|
@@ -23,11 +23,11 @@ declare class StateBuilder<TState extends BotState = BotState> {
|
|
|
23
23
|
onEnter(handler: EnterHandler<TState>): this;
|
|
24
24
|
/**
|
|
25
25
|
* Set the onResponse handler for this state
|
|
26
|
-
* Called when the user sends
|
|
26
|
+
* Called when the user sends any update while in this state
|
|
27
27
|
*
|
|
28
28
|
* Example:
|
|
29
|
-
* .onResponse(async (
|
|
30
|
-
* if (
|
|
29
|
+
* .onResponse(async (context) => {
|
|
30
|
+
* if (context.ctx.message?.text === 'yes') {
|
|
31
31
|
* return 'confirmed';
|
|
32
32
|
* }
|
|
33
33
|
* return 'cancelled';
|
|
@@ -62,11 +62,11 @@ declare class MultiStateBuilder<TState extends BotState = BotState> {
|
|
|
62
62
|
*
|
|
63
63
|
* typedBuilder
|
|
64
64
|
* .forState('welcome')
|
|
65
|
-
* .onEnter(async (
|
|
66
|
-
* await ctx.
|
|
65
|
+
* .onEnter(async (context) => {
|
|
66
|
+
* await context.ctx.reply('Welcome!');
|
|
67
67
|
* return 'menu'; // ✅ Type-safe: only 'idle' | 'welcome' | 'menu' | 'collectName' allowed
|
|
68
68
|
* })
|
|
69
|
-
* .onResponse(async (
|
|
69
|
+
* .onResponse(async (context) => {
|
|
70
70
|
* return 'collectName'; // ✅ Also type-safe
|
|
71
71
|
* });
|
|
72
72
|
* ```
|
package/dist/core/builder.js
CHANGED
|
@@ -16,8 +16,8 @@ class StateBuilder {
|
|
|
16
16
|
* Can optionally return a state name to immediately transition to
|
|
17
17
|
*
|
|
18
18
|
* Example:
|
|
19
|
-
* .onEnter(async (
|
|
20
|
-
* await ctx.
|
|
19
|
+
* .onEnter(async (context) => {
|
|
20
|
+
* await context.ctx.reply('Welcome!');
|
|
21
21
|
* // Optionally transition immediately:
|
|
22
22
|
* return 'anotherState';
|
|
23
23
|
* })
|
|
@@ -30,11 +30,11 @@ class StateBuilder {
|
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Set the onResponse handler for this state
|
|
33
|
-
* Called when the user sends
|
|
33
|
+
* Called when the user sends any update while in this state
|
|
34
34
|
*
|
|
35
35
|
* Example:
|
|
36
|
-
* .onResponse(async (
|
|
37
|
-
* if (
|
|
36
|
+
* .onResponse(async (context) => {
|
|
37
|
+
* if (context.ctx.message?.text === 'yes') {
|
|
38
38
|
* return 'confirmed';
|
|
39
39
|
* }
|
|
40
40
|
* return 'cancelled';
|
|
@@ -91,11 +91,11 @@ class MultiStateBuilder {
|
|
|
91
91
|
*
|
|
92
92
|
* typedBuilder
|
|
93
93
|
* .forState('welcome')
|
|
94
|
-
* .onEnter(async (
|
|
95
|
-
* await ctx.
|
|
94
|
+
* .onEnter(async (context) => {
|
|
95
|
+
* await context.ctx.reply('Welcome!');
|
|
96
96
|
* return 'menu'; // ✅ Type-safe: only 'idle' | 'welcome' | 'menu' | 'collectName' allowed
|
|
97
97
|
* })
|
|
98
|
-
* .onResponse(async (
|
|
98
|
+
* .onResponse(async (context) => {
|
|
99
99
|
* return 'collectName'; // ✅ Also type-safe
|
|
100
100
|
* });
|
|
101
101
|
* ```
|
|
@@ -143,9 +143,9 @@ export async function createOrder(
|
|
|
143
143
|
const user = await prisma.user.findUnique({
|
|
144
144
|
where: { telegramId },
|
|
145
145
|
});
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
if (!user) throw new Error('User not found');
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
return await prisma.order.create({
|
|
150
150
|
data: {
|
|
151
151
|
userId: user.id,
|
|
@@ -163,11 +163,11 @@ export async function getUserOrderStats(telegramId: string) {
|
|
|
163
163
|
orders: true,
|
|
164
164
|
},
|
|
165
165
|
});
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
if (!user) return { count: 0, total: 0 };
|
|
168
|
-
|
|
168
|
+
|
|
169
169
|
const total = user.orders.reduce((sum, order) => sum + order.amount, 0);
|
|
170
|
-
|
|
170
|
+
|
|
171
171
|
return {
|
|
172
172
|
count: user.orders.length,
|
|
173
173
|
total,
|
|
@@ -189,7 +189,7 @@ appBuilder
|
|
|
189
189
|
.forState('menu')
|
|
190
190
|
.onEnter(async (context: AppContext): MenuTransitions => {
|
|
191
191
|
const user = await getUserWithOrders(String(context.telegramId));
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
if (user?.orders.length) {
|
|
194
194
|
await context.ctx.reply(`You have ${user.orders.length} orders!`);
|
|
195
195
|
} else {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { appBuilder, type AppContext } from 'telemeister/core';
|
|
2
|
-
import type { Context } from 'grammy';
|
|
3
2
|
<% if (transitionStates.length > 0) { %>import type { <%= pascalCase(stateName) %>Transitions } from '../../bot-state-types.js';<% } %>
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -47,27 +46,27 @@ appBuilder
|
|
|
47
46
|
<% } %>
|
|
48
47
|
})
|
|
49
48
|
<% if (transitionStates.length > 0) { %>
|
|
50
|
-
.onResponse(async (context: AppContext
|
|
49
|
+
.onResponse(async (context: AppContext): <%= pascalCase(stateName) %>Transitions => {
|
|
51
50
|
<% } else { %>
|
|
52
|
-
.onResponse(async (context: AppContext
|
|
51
|
+
.onResponse(async (context: AppContext) => {
|
|
53
52
|
<% } %>
|
|
54
53
|
// Called when user sends any update in this state (message, callback, poll, etc.)
|
|
55
54
|
// Return a state name to transition, or nothing to stay
|
|
56
55
|
|
|
57
|
-
// Handle different update types via Grammy context:
|
|
58
|
-
// - ctx.message?.text - text messages
|
|
59
|
-
// - ctx.callbackQuery?.data - inline button callbacks
|
|
60
|
-
// - ctx.message?.photo - photo messages
|
|
61
|
-
// - ctx.pollAnswer - poll responses
|
|
56
|
+
// Handle different update types via Grammy context (context.ctx):
|
|
57
|
+
// - context.ctx.message?.text - text messages
|
|
58
|
+
// - context.ctx.callbackQuery?.data - inline button callbacks
|
|
59
|
+
// - context.ctx.message?.photo - photo messages
|
|
60
|
+
// - context.ctx.pollAnswer - poll responses
|
|
62
61
|
// See Grammy docs: https://grammy.dev/guide/context
|
|
63
62
|
|
|
64
63
|
// === INLINE KEYBOARD CALLBACK EXAMPLE ===
|
|
65
|
-
// if (ctx.callbackQuery?.data) {
|
|
66
|
-
// await ctx.answerCallbackQuery();
|
|
67
|
-
// const data = ctx.callbackQuery.data;
|
|
64
|
+
// if (context.ctx.callbackQuery?.data) {
|
|
65
|
+
// await context.ctx.answerCallbackQuery();
|
|
66
|
+
// const data = context.ctx.callbackQuery.data;
|
|
68
67
|
// switch (data) {
|
|
69
68
|
// case 'btn1':
|
|
70
|
-
// await ctx.reply('You clicked Button 1!');
|
|
69
|
+
// await context.ctx.reply('You clicked Button 1!');
|
|
71
70
|
// break;
|
|
72
71
|
// case 'btn2':
|
|
73
72
|
// return 'otherState'; // Transition to another state
|
|
@@ -76,33 +75,33 @@ appBuilder
|
|
|
76
75
|
// }
|
|
77
76
|
|
|
78
77
|
// === POLL ANSWER EXAMPLE ===
|
|
79
|
-
// if (ctx.pollAnswer) {
|
|
80
|
-
// const optionIds = ctx.pollAnswer.option_ids;
|
|
78
|
+
// if (context.ctx.pollAnswer) {
|
|
79
|
+
// const optionIds = context.ctx.pollAnswer.option_ids;
|
|
81
80
|
// const options = ['Option A', 'Option B', 'Option C'];
|
|
82
81
|
// const selected = optionIds.map(id => options[id]).join(', ');
|
|
83
|
-
// await ctx.reply(`You voted for: ${selected}`);
|
|
82
|
+
// await context.ctx.reply(`You voted for: ${selected}`);
|
|
84
83
|
// return;
|
|
85
84
|
// }
|
|
86
85
|
|
|
87
86
|
// === COMMAND HANDLING EXAMPLE ===
|
|
88
|
-
// const text = ctx.message?.text?.trim();
|
|
87
|
+
// const text = context.ctx.message?.text?.trim();
|
|
89
88
|
// if (text?.startsWith('/')) {
|
|
90
89
|
// const command = text.split(' ')[0].toLowerCase();
|
|
91
90
|
// switch (command) {
|
|
92
91
|
// case '/start':
|
|
93
|
-
// await ctx.reply('Welcome!');
|
|
92
|
+
// await context.ctx.reply('Welcome!');
|
|
94
93
|
// return 'welcome';
|
|
95
94
|
// case '/menu':
|
|
96
95
|
// return 'menu';
|
|
97
96
|
// default:
|
|
98
|
-
// await ctx.reply(`Unknown command: ${command}`);
|
|
97
|
+
// await context.ctx.reply(`Unknown command: ${command}`);
|
|
99
98
|
// }
|
|
100
99
|
// return;
|
|
101
100
|
// }
|
|
102
101
|
|
|
103
|
-
const text = ctx.message?.text?.trim();
|
|
102
|
+
const text = context.ctx.message?.text?.trim();
|
|
104
103
|
if (text) {
|
|
105
|
-
await ctx.reply(`You said: ${text}`);
|
|
104
|
+
await context.ctx.reply(`You said: ${text}`);
|
|
106
105
|
}
|
|
107
106
|
<% if (transitionStates.length > 0) { %>
|
|
108
107
|
// Available transitions: <%= transitionStates.join(', ') %>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "telemeister",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "TypeScript Telegram Bot Boilerplate with XState",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"typescript-eslint": "^8.39.0"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
|
-
"build": "tsc && cp -r src/templates dist/templates && pnpm run build:cli",
|
|
93
|
+
"build": "tsc && rm -rf dist/templates && cp -r src/templates dist/templates && pnpm run build:cli",
|
|
94
94
|
"build:cli": "esbuild dist/cli/cli.js --bundle --platform=node --format=esm --outfile=bin/telemeister-cli.js --external:typescript --external:tsx --external:prisma",
|
|
95
95
|
"example:recreate": "rm -rf examples/demo-bot && tsx src/cli/cli.ts create-bot demo-bot && mv demo-bot examples/demo-bot && npm pkg set --prefix examples/demo-bot dependencies.telemeister='workspace:*' && rm -rf examples/demo-bot/node_modules examples/demo-bot/pnpm-lock.yaml examples/demo-bot/dev.db examples/demo-bot/.env",
|
|
96
96
|
"lint": "eslint src/ scripts/",
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
# <%= botName %>
|
|
2
|
-
|
|
3
|
-
Telegram bot built with Telemeister - a TypeScript framework for building stateful Telegram bots with Prisma ORM.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Install dependencies
|
|
9
|
-
npm install
|
|
10
|
-
|
|
11
|
-
# Setup environment
|
|
12
|
-
cp .env.example .env
|
|
13
|
-
# Edit .env and add your bot token
|
|
14
|
-
|
|
15
|
-
# Setup database
|
|
16
|
-
npm run db:generate
|
|
17
|
-
npm run db:migrate
|
|
18
|
-
|
|
19
|
-
# Run the bot
|
|
20
|
-
npm run dev
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Project Structure
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
<%= botName %>/
|
|
27
|
-
├── bot.json # State machine config (do not commit)
|
|
28
|
-
├── prisma/
|
|
29
|
-
│ ├── schema.prisma # Database schema - edit to add custom models
|
|
30
|
-
│ └── config.ts # Prisma configuration
|
|
31
|
-
├── src/
|
|
32
|
-
│ ├── bot/ # Bot runners (polling/webhook modes)
|
|
33
|
-
│ │ ├── polling.ts # Polling mode runner
|
|
34
|
-
│ │ └── webhook.ts # Webhook mode runner
|
|
35
|
-
│ ├── handlers/ # State handlers
|
|
36
|
-
│ │ ├── index.ts # Auto-generated handler imports
|
|
37
|
-
│ │ ├── welcome/ # Example: welcome state
|
|
38
|
-
│ │ ├── menu/ # Example: menu state
|
|
39
|
-
│ │ └── idle/ # Default idle state
|
|
40
|
-
│ ├── lib/ # Database and utilities
|
|
41
|
-
│ │ └── database.ts # Database adapter implementation
|
|
42
|
-
│ ├── bot-state-types.ts # Generated types (do not edit)
|
|
43
|
-
│ └── index.ts # Bot entry point
|
|
44
|
-
├── doc/
|
|
45
|
-
│ ├── bot-diagram.md # Generated state diagram
|
|
46
|
-
│ └── bot-diagram.png # Visual diagram
|
|
47
|
-
└── dev.db # SQLite database (do not commit)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## State Management Commands
|
|
51
|
-
|
|
52
|
-
Add and manage conversation states:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# Add a new state (creates handler + updates types)
|
|
56
|
-
npm run state:add settings
|
|
57
|
-
|
|
58
|
-
# Delete a state
|
|
59
|
-
npm run state:delete settings
|
|
60
|
-
|
|
61
|
-
# Add transition between states
|
|
62
|
-
npm run state:transition:add welcome settings
|
|
63
|
-
|
|
64
|
-
# Remove transition
|
|
65
|
-
npm run state:transition:delete welcome settings
|
|
66
|
-
|
|
67
|
-
# Sync types and regenerate diagrams
|
|
68
|
-
npm run state:sync
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Database
|
|
72
|
-
|
|
73
|
-
### Custom Models
|
|
74
|
-
|
|
75
|
-
Edit `prisma/schema.prisma` to add your custom models:
|
|
76
|
-
|
|
77
|
-
```prisma
|
|
78
|
-
model User {
|
|
79
|
-
id Int @id @default(autoincrement())
|
|
80
|
-
telegramId String @unique
|
|
81
|
-
chatId String
|
|
82
|
-
currentState String @default("idle")
|
|
83
|
-
updatedAt DateTime @updatedAt
|
|
84
|
-
info UserInfo?
|
|
85
|
-
// Add your relations here:
|
|
86
|
-
orders Order[]
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
model UserInfo {
|
|
90
|
-
id Int @id @default(autoincrement())
|
|
91
|
-
userId Int @unique
|
|
92
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
93
|
-
stateData String @default("{}")
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Your custom model
|
|
97
|
-
model Order {
|
|
98
|
-
id Int @id @default(autoincrement())
|
|
99
|
-
userId Int
|
|
100
|
-
user User @relation(fields: [userId], references: [id])
|
|
101
|
-
product String
|
|
102
|
-
amount Float
|
|
103
|
-
createdAt DateTime @default(now())
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
After editing the schema, regenerate the client:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
npm run db:generate # Regenerate Prisma client
|
|
111
|
-
npm run db:migrate # Create and apply migration
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Custom Database Queries
|
|
115
|
-
|
|
116
|
-
Create `src/lib/database.ts` for your database operations:
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
import { PrismaClient } from '@prisma/client';
|
|
120
|
-
|
|
121
|
-
const prisma = new PrismaClient();
|
|
122
|
-
|
|
123
|
-
// Example: Get user with orders
|
|
124
|
-
export async function getUserWithOrders(telegramId: string) {
|
|
125
|
-
return await prisma.user.findUnique({
|
|
126
|
-
where: { telegramId },
|
|
127
|
-
include: {
|
|
128
|
-
info: true,
|
|
129
|
-
orders: {
|
|
130
|
-
orderBy: { createdAt: 'desc' },
|
|
131
|
-
take: 10,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Example: Create a new order
|
|
138
|
-
export async function createOrder(
|
|
139
|
-
telegramId: string,
|
|
140
|
-
product: string,
|
|
141
|
-
amount: number
|
|
142
|
-
) {
|
|
143
|
-
const user = await prisma.user.findUnique({
|
|
144
|
-
where: { telegramId },
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
if (!user) throw new Error('User not found');
|
|
148
|
-
|
|
149
|
-
return await prisma.order.create({
|
|
150
|
-
data: {
|
|
151
|
-
userId: user.id,
|
|
152
|
-
product,
|
|
153
|
-
amount,
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Example: Get order statistics
|
|
159
|
-
export async function getUserOrderStats(telegramId: string) {
|
|
160
|
-
const user = await prisma.user.findUnique({
|
|
161
|
-
where: { telegramId },
|
|
162
|
-
include: {
|
|
163
|
-
orders: true,
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
if (!user) return { count: 0, total: 0 };
|
|
168
|
-
|
|
169
|
-
const total = user.orders.reduce((sum, order) => sum + order.amount, 0);
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
count: user.orders.length,
|
|
173
|
-
total,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export { prisma };
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Use in handlers:
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
import { appBuilder, type AppContext } from 'telemeister/core';
|
|
184
|
-
import type { Context } from 'grammy';
|
|
185
|
-
import type { MenuTransitions } from '../../bot-state-types.js';
|
|
186
|
-
import { getUserWithOrders, createOrder } from '../../lib/database.js';
|
|
187
|
-
|
|
188
|
-
appBuilder
|
|
189
|
-
.forState('menu')
|
|
190
|
-
.onEnter(async (context: AppContext): MenuTransitions => {
|
|
191
|
-
const user = await getUserWithOrders(String(context.telegramId));
|
|
192
|
-
|
|
193
|
-
if (user?.orders.length) {
|
|
194
|
-
await context.ctx.reply(`You have ${user.orders.length} orders!`);
|
|
195
|
-
} else {
|
|
196
|
-
await context.ctx.reply('Welcome! You have no orders yet.');
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
.onResponse(async (context: AppContext, ctx: Context): MenuTransitions => {
|
|
200
|
-
if (ctx.message?.text === 'order') {
|
|
201
|
-
await createOrder(String(context.telegramId), 'Product A', 99.99);
|
|
202
|
-
await ctx.reply('Order created!');
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
console.log('✅ State handler registered: menu');
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## Available Scripts
|
|
210
|
-
|
|
211
|
-
| Command | Description |
|
|
212
|
-
|---------|-------------|
|
|
213
|
-
| `npm run dev` | Start bot in development mode (watch) |
|
|
214
|
-
| `npm run build` | Compile TypeScript |
|
|
215
|
-
| `npm run start` | Run compiled bot |
|
|
216
|
-
| `npm run db:generate` | Generate Prisma client |
|
|
217
|
-
| `npm run db:migrate` | Create and apply database migrations |
|
|
218
|
-
| `npm run db:studio` | Open Prisma Studio (database GUI) |
|
|
219
|
-
| `npm run state:add <name>` | Add new state |
|
|
220
|
-
| `npm run state:delete <name>` | Delete state |
|
|
221
|
-
| `npm run state:sync` | Sync types and handlers |
|
|
222
|
-
| `npm run state:transition:add <from> <to>` | Add transition |
|
|
223
|
-
| `npm run state:transition:delete <from> <to>` | Delete transition |
|
|
224
|
-
|
|
225
|
-
## Environment Variables
|
|
226
|
-
|
|
227
|
-
| Variable | Required | Description |
|
|
228
|
-
|----------|----------|-------------|
|
|
229
|
-
| `BOT_TOKEN` | Yes | Telegram bot token from @BotFather |
|
|
230
|
-
| `DATABASE_URL` | Yes | Database connection string |
|
|
231
|
-
| `BOT_MODE` | No | `polling` (default) or `webhook` |
|
|
232
|
-
| `WEBHOOK_URL` | If webhook | Webhook URL for production |
|
|
233
|
-
| `PORT` | If webhook | Port for webhook server (default: 3000) |
|
|
234
|
-
|
|
235
|
-
## Database Configuration
|
|
236
|
-
|
|
237
|
-
### SQLite (Development)
|
|
238
|
-
```env
|
|
239
|
-
DATABASE_URL=file:./dev.db
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### MySQL (Production)
|
|
243
|
-
```env
|
|
244
|
-
DATABASE_URL=mysql://user:password@localhost:3306/dbname
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
When switching to MySQL, update `prisma/schema.prisma`:
|
|
248
|
-
```prisma
|
|
249
|
-
datasource db {
|
|
250
|
-
provider = "mysql"
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## State Machine
|
|
255
|
-
|
|
256
|
-
The bot uses a finite state machine defined in `bot.json`:
|
|
257
|
-
|
|
258
|
-
```json
|
|
259
|
-
{
|
|
260
|
-
"welcome": ["menu"],
|
|
261
|
-
"menu": ["welcome", "settings"],
|
|
262
|
-
"settings": ["menu"]
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
Each handler can transition to its listed states by returning the state name.
|
|
267
|
-
|
|
268
|
-
## Development Tips
|
|
269
|
-
|
|
270
|
-
1. **Always regenerate types after schema changes**: `npm run db:generate`
|
|
271
|
-
2. **Use Prisma Studio for database inspection**: `npm run db:studio`
|
|
272
|
-
3. **Check the diagram**: See `doc/bot-diagram.md` for visual state flow
|
|
273
|
-
4. **State data**: Use `context.setData()` and `context.getData()` for temporary storage
|
|
274
|
-
5. **Persistent storage**: Use custom Prisma models for user data
|
|
275
|
-
|
|
276
|
-
## Deployment
|
|
277
|
-
|
|
278
|
-
1. Set production database URL
|
|
279
|
-
2. Run migrations: `npm run db:migrate`
|
|
280
|
-
3. Build: `npm run build`
|
|
281
|
-
4. Start: `npm start`
|
|
282
|
-
|
|
283
|
-
For webhook mode:
|
|
284
|
-
```bash
|
|
285
|
-
BOT_MODE=webhook WEBHOOK_URL=https://your-domain.com npm start
|
|
286
|
-
```
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom Database Queries
|
|
3
|
-
*
|
|
4
|
-
* This file contains example database operations for your bot.
|
|
5
|
-
* Copy this file to src/lib/database.ts and customize as needed.
|
|
6
|
-
*
|
|
7
|
-
* The Prisma client is available from the generated client.
|
|
8
|
-
*
|
|
9
|
-
* IMPORTANT: After editing prisma/schema.prisma, run:
|
|
10
|
-
* npm run db:generate # Regenerate Prisma client
|
|
11
|
-
* npm run db:migrate # Create and apply migration
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { PrismaClient } from '@prisma/client';
|
|
15
|
-
|
|
16
|
-
const prisma = new PrismaClient();
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Example: Get user with their info and any custom relations
|
|
20
|
-
*/
|
|
21
|
-
export async function getUserWithDetails(telegramId: string) {
|
|
22
|
-
return await prisma.user.findUnique({
|
|
23
|
-
where: { telegramId },
|
|
24
|
-
include: {
|
|
25
|
-
info: true,
|
|
26
|
-
// Add your custom relations here after updating schema
|
|
27
|
-
// orders: true,
|
|
28
|
-
// profile: true,
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Example: Update user's state data
|
|
35
|
-
*/
|
|
36
|
-
export async function updateUserData(
|
|
37
|
-
telegramId: string,
|
|
38
|
-
data: Record<string, unknown>
|
|
39
|
-
) {
|
|
40
|
-
const user = await prisma.user.findUnique({
|
|
41
|
-
where: { telegramId },
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (!user) {
|
|
45
|
-
throw new Error(`User ${telegramId} not found`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Get existing data
|
|
49
|
-
const existingInfo = await prisma.userInfo.findUnique({
|
|
50
|
-
where: { userId: user.id },
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const existingData = existingInfo
|
|
54
|
-
? JSON.parse(existingInfo.stateData)
|
|
55
|
-
: {};
|
|
56
|
-
|
|
57
|
-
// Merge and save
|
|
58
|
-
await prisma.userInfo.upsert({
|
|
59
|
-
where: { userId: user.id },
|
|
60
|
-
create: {
|
|
61
|
-
userId: user.id,
|
|
62
|
-
stateData: JSON.stringify({ ...existingData, ...data }),
|
|
63
|
-
},
|
|
64
|
-
update: {
|
|
65
|
-
stateData: JSON.stringify({ ...existingData, ...data }),
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return { ...existingData, ...data };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Example: Get user data
|
|
74
|
-
*/
|
|
75
|
-
export async function getUserData(telegramId: string) {
|
|
76
|
-
const user = await prisma.user.findUnique({
|
|
77
|
-
where: { telegramId },
|
|
78
|
-
include: { info: true },
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (!user?.info) {
|
|
82
|
-
return {};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return JSON.parse(user.info.stateData);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Example: Add a custom query for your domain
|
|
90
|
-
*
|
|
91
|
-
* After adding an Order model to schema.prisma, uncomment and customize:
|
|
92
|
-
*/
|
|
93
|
-
/*
|
|
94
|
-
export async function createOrder(
|
|
95
|
-
telegramId: string,
|
|
96
|
-
product: string,
|
|
97
|
-
amount: number
|
|
98
|
-
) {
|
|
99
|
-
const user = await prisma.user.findUnique({
|
|
100
|
-
where: { telegramId },
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (!user) throw new Error('User not found');
|
|
104
|
-
|
|
105
|
-
return await prisma.order.create({
|
|
106
|
-
data: {
|
|
107
|
-
userId: user.id,
|
|
108
|
-
product,
|
|
109
|
-
amount,
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export async function getUserOrders(telegramId: string) {
|
|
115
|
-
const user = await prisma.user.findUnique({
|
|
116
|
-
where: { telegramId },
|
|
117
|
-
include: {
|
|
118
|
-
orders: {
|
|
119
|
-
orderBy: { createdAt: 'desc' },
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return user?.orders || [];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export async function getOrderStats(telegramId: string) {
|
|
128
|
-
const orders = await getUserOrders(telegramId);
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
count: orders.length,
|
|
132
|
-
total: orders.reduce((sum, order) => sum + order.amount, 0),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
*/
|
|
136
|
-
|
|
137
|
-
// Export prisma client for direct access if needed
|
|
138
|
-
export { prisma };
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database Entry Point
|
|
3
|
-
*
|
|
4
|
-
* Implements DatabaseAdapter interface for the Telemeister framework.
|
|
5
|
-
* Uses Prisma ORM 7.x with driver adapters for database operations.
|
|
6
|
-
*
|
|
7
|
-
* Switch between SQLite and MySQL by changing DATABASE_URL in .env:
|
|
8
|
-
* - SQLite: DATABASE_URL="file:./dev.db"
|
|
9
|
-
* - MySQL: DATABASE_URL="mysql://user:password@localhost:3306/dbname"
|
|
10
|
-
*
|
|
11
|
-
* Note: When switching providers, update the provider in prisma/schema.prisma:
|
|
12
|
-
* - SQLite: provider = "sqlite"
|
|
13
|
-
* - MySQL: provider = "mysql"
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import 'dotenv/config';
|
|
17
|
-
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
|
|
18
|
-
import { PrismaClient, User, UserInfo } from '../generated/prisma/client.js';
|
|
19
|
-
import type { DatabaseAdapter, UserData } from 'telemeister/core/bot';
|
|
20
|
-
|
|
21
|
-
// Determine database provider from URL
|
|
22
|
-
const databaseUrl = process.env.DATABASE_URL || 'file:./dev.db';
|
|
23
|
-
const isMysql = databaseUrl.startsWith('mysql:');
|
|
24
|
-
|
|
25
|
-
// Create adapter based on database type
|
|
26
|
-
// For now, SQLite is fully supported. MySQL adapter requires additional setup.
|
|
27
|
-
const adapter = isMysql
|
|
28
|
-
? (() => {
|
|
29
|
-
throw new Error(
|
|
30
|
-
'MySQL adapter not yet configured. Please configure @prisma/adapter-mariadb in src/lib/database.ts'
|
|
31
|
-
);
|
|
32
|
-
})()
|
|
33
|
-
: new PrismaBetterSqlite3({
|
|
34
|
-
url: databaseUrl,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Prisma client instance with adapter
|
|
38
|
-
const prisma = new PrismaClient({ adapter });
|
|
39
|
-
|
|
40
|
-
// Re-export types from Prisma
|
|
41
|
-
export type { User, UserInfo };
|
|
42
|
-
|
|
43
|
-
// Type for user with included info relation
|
|
44
|
-
export type UserWithInfo = User & {
|
|
45
|
-
info: UserInfo | null;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Get user by Telegram ID with joined user info
|
|
50
|
-
*/
|
|
51
|
-
export async function getUserByTelegramId(telegramId: string): Promise<UserWithInfo | null> {
|
|
52
|
-
const user = await prisma.user.findUnique({
|
|
53
|
-
where: { telegramId },
|
|
54
|
-
include: { info: true },
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
return user;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Create or update a user
|
|
62
|
-
*/
|
|
63
|
-
export async function createOrUpdateUser(data: {
|
|
64
|
-
telegramId: string;
|
|
65
|
-
chatId: string;
|
|
66
|
-
currentState?: string;
|
|
67
|
-
stateData?: Record<string, unknown>;
|
|
68
|
-
}): Promise<UserWithInfo> {
|
|
69
|
-
const existingUser = await getUserByTelegramId(data.telegramId);
|
|
70
|
-
|
|
71
|
-
if (existingUser) {
|
|
72
|
-
// Update existing user
|
|
73
|
-
const updatedUser = await prisma.user.update({
|
|
74
|
-
where: { telegramId: data.telegramId },
|
|
75
|
-
data: {
|
|
76
|
-
chatId: data.chatId,
|
|
77
|
-
...(data.currentState && { currentState: data.currentState }),
|
|
78
|
-
},
|
|
79
|
-
include: { info: true },
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Update or create user info if stateData provided
|
|
83
|
-
if (data.stateData) {
|
|
84
|
-
await prisma.userInfo.upsert({
|
|
85
|
-
where: { userId: updatedUser.id },
|
|
86
|
-
create: {
|
|
87
|
-
userId: updatedUser.id,
|
|
88
|
-
stateData: JSON.stringify(data.stateData),
|
|
89
|
-
},
|
|
90
|
-
update: {
|
|
91
|
-
stateData: JSON.stringify(data.stateData),
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// Return updated user with info
|
|
96
|
-
return (await getUserByTelegramId(data.telegramId))!;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return updatedUser;
|
|
100
|
-
} else {
|
|
101
|
-
// Create new user with info
|
|
102
|
-
const newUser = await prisma.user.create({
|
|
103
|
-
data: {
|
|
104
|
-
telegramId: data.telegramId,
|
|
105
|
-
chatId: data.chatId,
|
|
106
|
-
currentState: data.currentState || 'idle',
|
|
107
|
-
info: {
|
|
108
|
-
create: {
|
|
109
|
-
stateData: JSON.stringify(data.stateData || {}),
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
include: { info: true },
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
return newUser;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Update user state and optional state data
|
|
122
|
-
*/
|
|
123
|
-
export async function updateUserState(
|
|
124
|
-
telegramId: string,
|
|
125
|
-
currentState: string,
|
|
126
|
-
stateData?: Record<string, unknown>
|
|
127
|
-
): Promise<void> {
|
|
128
|
-
const user = await prisma.user.findUnique({
|
|
129
|
-
where: { telegramId },
|
|
130
|
-
});
|
|
131
|
-
if (!user) {
|
|
132
|
-
throw new Error(`User with telegramId ${telegramId} not found`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Update user state
|
|
136
|
-
await prisma.user.update({
|
|
137
|
-
where: { telegramId },
|
|
138
|
-
data: { currentState },
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// Update state data if provided
|
|
142
|
-
if (stateData) {
|
|
143
|
-
await prisma.userInfo.upsert({
|
|
144
|
-
where: { userId: user.id },
|
|
145
|
-
create: {
|
|
146
|
-
userId: user.id,
|
|
147
|
-
stateData: JSON.stringify(stateData),
|
|
148
|
-
},
|
|
149
|
-
update: {
|
|
150
|
-
stateData: JSON.stringify(stateData),
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Disconnect from database
|
|
158
|
-
*/
|
|
159
|
-
export async function disconnectDB(): Promise<void> {
|
|
160
|
-
await prisma.$disconnect();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Database adapter implementation for Telemeister framework
|
|
165
|
-
* Implements the DatabaseAdapter interface
|
|
166
|
-
*/
|
|
167
|
-
export const databaseAdapter: DatabaseAdapter = {
|
|
168
|
-
getUserByTelegramId: async (telegramId: string): Promise<UserData | null> => {
|
|
169
|
-
const user = await getUserByTelegramId(telegramId);
|
|
170
|
-
if (!user) return null;
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
id: user.id,
|
|
174
|
-
telegramId: user.telegramId,
|
|
175
|
-
chatId: user.chatId,
|
|
176
|
-
currentState: user.currentState,
|
|
177
|
-
info: user.info,
|
|
178
|
-
};
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
createOrUpdateUser: async (data: {
|
|
182
|
-
telegramId: string;
|
|
183
|
-
chatId: string;
|
|
184
|
-
currentState?: string;
|
|
185
|
-
stateData?: Record<string, unknown>;
|
|
186
|
-
}): Promise<UserData> => {
|
|
187
|
-
const user = await createOrUpdateUser(data);
|
|
188
|
-
return {
|
|
189
|
-
id: user.id,
|
|
190
|
-
telegramId: user.telegramId,
|
|
191
|
-
chatId: user.chatId,
|
|
192
|
-
currentState: user.currentState,
|
|
193
|
-
info: user.info,
|
|
194
|
-
};
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
updateUserState: async (
|
|
198
|
-
telegramId: string,
|
|
199
|
-
currentState: string,
|
|
200
|
-
stateData?: Record<string, unknown>
|
|
201
|
-
): Promise<void> => {
|
|
202
|
-
await updateUserState(telegramId, currentState, stateData);
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
export { prisma };
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { appBuilder, type AppContext } from 'telemeister/core';
|
|
2
|
-
<% if (transitionStates.length > 0) { %>import type { <%= pascalCase(stateName) %>Transitions } from '../../bot-state-types.js';<% } %>
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* <%= stateName %> State Handler
|
|
6
|
-
*
|
|
7
|
-
* This file defines the handlers for the "<%= stateName %>" state.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
appBuilder
|
|
11
|
-
.forState('<%= stateName %>')
|
|
12
|
-
<% if (transitionStates.length > 0) { %>
|
|
13
|
-
.onEnter(async (context: AppContext): <%= pascalCase(stateName) %>Transitions => {
|
|
14
|
-
<% } else { %>
|
|
15
|
-
.onEnter(async (context: AppContext) => {
|
|
16
|
-
<% } %>
|
|
17
|
-
// Called when user enters this state
|
|
18
|
-
// Can optionally return a state name to immediately transition
|
|
19
|
-
|
|
20
|
-
// Access Grammy context via context.ctx
|
|
21
|
-
// See Grammy docs: https://grammy.dev/guide/context
|
|
22
|
-
await context.ctx.reply("Hello from <%= stateName %> state!");
|
|
23
|
-
|
|
24
|
-
// === INLINE KEYBOARD EXAMPLE ===
|
|
25
|
-
// import { InlineKeyboard } from 'grammy';
|
|
26
|
-
// const keyboard = new InlineKeyboard()
|
|
27
|
-
// .text('Button 1', 'btn1')
|
|
28
|
-
// .text('Button 2', 'btn2');
|
|
29
|
-
// await context.ctx.reply('Choose:', { reply_markup: keyboard });
|
|
30
|
-
|
|
31
|
-
// === POLL EXAMPLE ===
|
|
32
|
-
// await context.ctx.replyWithPoll(
|
|
33
|
-
// 'Question?',
|
|
34
|
-
// ['Option A', 'Option B', 'Option C'],
|
|
35
|
-
// { is_anonymous: false }
|
|
36
|
-
// );
|
|
37
|
-
|
|
38
|
-
// Database helpers (see src/lib/database.ts):
|
|
39
|
-
// import { getUserByTelegramId, createOrUpdateUser } from '../../lib/database.js';
|
|
40
|
-
// const user = await getUserByTelegramId(String(context.telegramId));
|
|
41
|
-
<% if (transitionStates.length > 0) { %>
|
|
42
|
-
// Available transitions: <%= transitionStates.join(', ') %>
|
|
43
|
-
// return "<%= transitionStates[0] %>";
|
|
44
|
-
<% } else { %>
|
|
45
|
-
// No transitions defined - add them with: npm run state:transition:add -- <%= stateName %> <target-state>
|
|
46
|
-
<% } %>
|
|
47
|
-
})
|
|
48
|
-
<% if (transitionStates.length > 0) { %>
|
|
49
|
-
.onResponse(async (context: AppContext): <%= pascalCase(stateName) %>Transitions => {
|
|
50
|
-
<% } else { %>
|
|
51
|
-
.onResponse(async (context: AppContext) => {
|
|
52
|
-
<% } %>
|
|
53
|
-
// Called when user sends any update in this state (message, callback, poll, etc.)
|
|
54
|
-
// Return a state name to transition, or nothing to stay
|
|
55
|
-
|
|
56
|
-
// Handle different update types via Grammy context (context.ctx):
|
|
57
|
-
// - context.ctx.message?.text - text messages
|
|
58
|
-
// - context.ctx.callbackQuery?.data - inline button callbacks
|
|
59
|
-
// - context.ctx.message?.photo - photo messages
|
|
60
|
-
// - context.ctx.pollAnswer - poll responses
|
|
61
|
-
// See Grammy docs: https://grammy.dev/guide/context
|
|
62
|
-
|
|
63
|
-
// === INLINE KEYBOARD CALLBACK EXAMPLE ===
|
|
64
|
-
// if (context.ctx.callbackQuery?.data) {
|
|
65
|
-
// await context.ctx.answerCallbackQuery();
|
|
66
|
-
// const data = context.ctx.callbackQuery.data;
|
|
67
|
-
// switch (data) {
|
|
68
|
-
// case 'btn1':
|
|
69
|
-
// await context.ctx.reply('You clicked Button 1!');
|
|
70
|
-
// break;
|
|
71
|
-
// case 'btn2':
|
|
72
|
-
// return 'otherState'; // Transition to another state
|
|
73
|
-
// }
|
|
74
|
-
// return;
|
|
75
|
-
// }
|
|
76
|
-
|
|
77
|
-
// === POLL ANSWER EXAMPLE ===
|
|
78
|
-
// if (context.ctx.pollAnswer) {
|
|
79
|
-
// const optionIds = context.ctx.pollAnswer.option_ids;
|
|
80
|
-
// const options = ['Option A', 'Option B', 'Option C'];
|
|
81
|
-
// const selected = optionIds.map(id => options[id]).join(', ');
|
|
82
|
-
// await context.ctx.reply(`You voted for: ${selected}`);
|
|
83
|
-
// return;
|
|
84
|
-
// }
|
|
85
|
-
|
|
86
|
-
// === COMMAND HANDLING EXAMPLE ===
|
|
87
|
-
// const text = context.ctx.message?.text?.trim();
|
|
88
|
-
// if (text?.startsWith('/')) {
|
|
89
|
-
// const command = text.split(' ')[0].toLowerCase();
|
|
90
|
-
// switch (command) {
|
|
91
|
-
// case '/start':
|
|
92
|
-
// await context.ctx.reply('Welcome!');
|
|
93
|
-
// return 'welcome';
|
|
94
|
-
// case '/menu':
|
|
95
|
-
// return 'menu';
|
|
96
|
-
// default:
|
|
97
|
-
// await context.ctx.reply(`Unknown command: ${command}`);
|
|
98
|
-
// }
|
|
99
|
-
// return;
|
|
100
|
-
// }
|
|
101
|
-
|
|
102
|
-
const text = context.ctx.message?.text?.trim();
|
|
103
|
-
if (text) {
|
|
104
|
-
await context.ctx.reply(`You said: ${text}`);
|
|
105
|
-
}
|
|
106
|
-
<% if (transitionStates.length > 0) { %>
|
|
107
|
-
// Available transitions: <%= transitionStates.join(', ') %>
|
|
108
|
-
<% } %>
|
|
109
|
-
// Database helpers (see src/lib/database.ts):
|
|
110
|
-
// import { updateUserState } from '../../lib/database.js';
|
|
111
|
-
// await updateUserState(String(context.telegramId), 'nextState', { lastMessage: text });
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
console.log('✅ State handler registered: <%= stateName %>');
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bot entry point
|
|
3
|
-
*
|
|
4
|
-
* This file starts the bot using the Telemeister framework.
|
|
5
|
-
* It imports bot runners from 'telemeister/core/bot' and the database adapter
|
|
6
|
-
* from your local database implementation.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import 'dotenv/config';
|
|
10
|
-
import { startPollingMode, startWebhookMode } from 'telemeister/core/bot';
|
|
11
|
-
import { databaseAdapter } from './lib/database.js';
|
|
12
|
-
import './handlers/index.js';
|
|
13
|
-
|
|
14
|
-
const botToken = process.env.BOT_TOKEN;
|
|
15
|
-
if (!botToken) {
|
|
16
|
-
console.error('❌ BOT_TOKEN environment variable is required');
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Type assertion after validation
|
|
21
|
-
const token: string = botToken;
|
|
22
|
-
|
|
23
|
-
const botMode = process.env.BOT_MODE || 'polling';
|
|
24
|
-
|
|
25
|
-
async function main(): Promise<void> {
|
|
26
|
-
console.log(`🚀 Starting bot in ${botMode} mode...`);
|
|
27
|
-
|
|
28
|
-
if (botMode === 'webhook') {
|
|
29
|
-
const webhookUrl = process.env.WEBHOOK_URL;
|
|
30
|
-
if (!webhookUrl) {
|
|
31
|
-
console.error('❌ WEBHOOK_URL environment variable is required for webhook mode');
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
const port = parseInt(process.env.PORT || '3000', 10);
|
|
35
|
-
|
|
36
|
-
await startWebhookMode({
|
|
37
|
-
token: token,
|
|
38
|
-
database: databaseAdapter,
|
|
39
|
-
webhookUrl,
|
|
40
|
-
port,
|
|
41
|
-
});
|
|
42
|
-
} else {
|
|
43
|
-
await startPollingMode({
|
|
44
|
-
token: token,
|
|
45
|
-
database: databaseAdapter,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
main().catch((error) => {
|
|
51
|
-
console.error('Failed to start bot:', error);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "<%= botName %>",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "tsx watch src/index.ts",
|
|
7
|
-
"dev:webhook": "BOT_MODE=webhook tsx watch src/index.ts",
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"start": "node dist/index.js",
|
|
10
|
-
"start:webhook": "BOT_MODE=webhook node dist/index.js",
|
|
11
|
-
"db:generate": "prisma generate",
|
|
12
|
-
"db:migrate": "prisma migrate dev",
|
|
13
|
-
"db:deploy": "prisma migrate deploy",
|
|
14
|
-
"db:push": "prisma db push",
|
|
15
|
-
"db:studio": "prisma studio",
|
|
16
|
-
"webhook:set": "tsx scripts/set-webhook.ts",
|
|
17
|
-
"webhook:delete": "tsx scripts/delete-webhook.ts",
|
|
18
|
-
"webhook:info": "tsx scripts/webhook-info.ts",
|
|
19
|
-
"state:add": "telemeister state:add",
|
|
20
|
-
"state:delete": "telemeister state:delete",
|
|
21
|
-
"state:sync": "telemeister state:sync",
|
|
22
|
-
"state:transition:add": "telemeister state:transition:add",
|
|
23
|
-
"state:transition:delete": "telemeister state:transition:delete"
|
|
24
|
-
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@prisma/adapter-better-sqlite3": "^7.0.0",
|
|
27
|
-
"@prisma/client": "^7.4.0",
|
|
28
|
-
"better-sqlite3": "^12.6.2",
|
|
29
|
-
"dotenv": "^16.4.7",
|
|
30
|
-
"express": "^4.21.2",
|
|
31
|
-
"grammy": "^1.35.1",
|
|
32
|
-
"telemeister": "^0.1.8"
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@types/express": "^5.0.0",
|
|
36
|
-
"@types/node": "^20.0.0",
|
|
37
|
-
"prisma": "^7.0.0",
|
|
38
|
-
"tsx": "^4.0.0",
|
|
39
|
-
"typescript": "^5.0.0"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
generator client {
|
|
2
|
-
provider = "prisma-client-js"
|
|
3
|
-
output = "../src/generated/prisma"
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
datasource db {
|
|
7
|
-
provider = "sqlite"
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
model User {
|
|
11
|
-
id Int @id @default(autoincrement())
|
|
12
|
-
telegramId String @unique
|
|
13
|
-
chatId String
|
|
14
|
-
currentState String @default("idle")
|
|
15
|
-
updatedAt DateTime @updatedAt
|
|
16
|
-
info UserInfo?
|
|
17
|
-
|
|
18
|
-
@@index([currentState])
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
model UserInfo {
|
|
22
|
-
id Int @id @default(autoincrement())
|
|
23
|
-
userId Int @unique
|
|
24
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
25
|
-
stateData String @default("{}")
|
|
26
|
-
|
|
27
|
-
// Add your custom fields below this line
|
|
28
|
-
// Example: email String?
|
|
29
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"outDir": "./dist",
|
|
12
|
-
"rootDir": "./src",
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"declarationMap": true,
|
|
15
|
-
"sourceMap": true
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|