@monixlite/grammy-scenes 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -163
- package/package.json +1 -1
- package/src/index.js +74 -32
package/README.md
CHANGED
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
# @monixlite/grammy-scenes
|
|
2
2
|
|
|
3
|
-
A lightweight
|
|
4
|
-
The module implements step-based scenes with declarative step definitions, automatic transitions, session-based state management, and an extensible navigation system.
|
|
3
|
+
A lightweight Finite State Machine (FSM) engine for Telegram bots built on grammY. The library provides step-based scenes with declarative definitions, session-backed state, deterministic transitions, and extensible navigation.
|
|
5
4
|
|
|
6
5
|
---
|
|
7
6
|
|
|
8
7
|
## Installation
|
|
9
8
|
|
|
10
|
-
```
|
|
9
|
+
```
|
|
11
10
|
npm install @monixlite/grammy-scenes
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
## Requirements
|
|
15
|
-
|
|
16
|
-
• Node.js >= 16
|
|
17
|
-
• grammY >= 1.19
|
|
18
|
-
|
|
19
13
|
---
|
|
20
14
|
|
|
21
|
-
## Core
|
|
15
|
+
## Core Concepts
|
|
16
|
+
|
|
17
|
+
A scene is a sequence of ordered steps grouped under a shared title. Each step is defined declaratively and reacts to Telegram updates via callbacks.
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
Each step is described by an object containing:
|
|
19
|
+
The active scene state is stored in `ctx.session.scene`.
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
• a set of callbacks reacting to Telegram events (enter, message, callbackQuery)
|
|
21
|
+
Each step definition contains:
|
|
28
22
|
|
|
29
|
-
|
|
23
|
+
* scene identifier (title + step)
|
|
24
|
+
* callbacks for lifecycle and update handling
|
|
30
25
|
|
|
31
26
|
---
|
|
32
27
|
|
|
33
28
|
## Basic Usage
|
|
34
29
|
|
|
35
|
-
```
|
|
30
|
+
```
|
|
36
31
|
const { Bot, session } = require("grammy");
|
|
37
32
|
const { Scenes } = require("@monixlite/grammy-scenes");
|
|
38
33
|
|
|
@@ -46,7 +41,7 @@ const bot = new Bot("..."); {
|
|
|
46
41
|
};
|
|
47
42
|
|
|
48
43
|
|
|
49
|
-
const
|
|
44
|
+
const scenes = new Scenes([
|
|
50
45
|
[
|
|
51
46
|
{
|
|
52
47
|
scene: {
|
|
@@ -54,7 +49,7 @@ const scene = new Scenes([
|
|
|
54
49
|
step: "phone",
|
|
55
50
|
},
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
action: {
|
|
58
53
|
enter: async (ctx) => {
|
|
59
54
|
await ctx.reply("• Enter your phone number:");
|
|
60
55
|
},
|
|
@@ -71,26 +66,26 @@ const scene = new Scenes([
|
|
|
71
66
|
step: "confirm",
|
|
72
67
|
},
|
|
73
68
|
|
|
74
|
-
|
|
69
|
+
action: {
|
|
75
70
|
enter: async (ctx) => {
|
|
76
71
|
const { phone } = ctx.session;
|
|
77
72
|
|
|
78
73
|
|
|
79
|
-
await ctx.reply(
|
|
74
|
+
await ctx.reply([
|
|
80
75
|
`• Your phone number: ${phone}`,
|
|
81
|
-
`• Is this correct
|
|
82
|
-
]
|
|
76
|
+
`• Is this correct?`,
|
|
77
|
+
].join("\n"), {
|
|
83
78
|
reply_markup: {
|
|
84
79
|
inline_keyboard: [
|
|
85
80
|
[
|
|
86
81
|
{
|
|
87
82
|
text: "• Yes",
|
|
88
|
-
callback_data: "confirm:yes"
|
|
83
|
+
callback_data: "confirm:yes",
|
|
89
84
|
},
|
|
90
|
-
|
|
85
|
+
|
|
91
86
|
{
|
|
92
87
|
text: "• No",
|
|
93
|
-
callback_data: "confirm:no"
|
|
88
|
+
callback_data: "confirm:no",
|
|
94
89
|
},
|
|
95
90
|
],
|
|
96
91
|
],
|
|
@@ -98,7 +93,7 @@ const scene = new Scenes([
|
|
|
98
93
|
});
|
|
99
94
|
},
|
|
100
95
|
|
|
101
|
-
|
|
96
|
+
callbacks: [
|
|
102
97
|
{
|
|
103
98
|
match: /^confirm:yes$/,
|
|
104
99
|
handler: async (ctx) => {
|
|
@@ -106,12 +101,13 @@ const scene = new Scenes([
|
|
|
106
101
|
|
|
107
102
|
|
|
108
103
|
await ctx.answerCallbackQuery();
|
|
109
|
-
await ctx.reply(`• Thank you!
|
|
104
|
+
await ctx.reply(`• Thank you! ${phone} saved.`);
|
|
110
105
|
|
|
111
106
|
|
|
112
107
|
return "stop";
|
|
113
108
|
},
|
|
114
109
|
},
|
|
110
|
+
|
|
115
111
|
{
|
|
116
112
|
match: /^confirm:no$/,
|
|
117
113
|
handler: async (ctx) => {
|
|
@@ -126,7 +122,7 @@ const scene = new Scenes([
|
|
|
126
122
|
},
|
|
127
123
|
],
|
|
128
124
|
], {
|
|
129
|
-
["
|
|
125
|
+
["action:enter:extra:reply_markup:inline_keyboard:cancel"]: {
|
|
130
126
|
enabled: true,
|
|
131
127
|
|
|
132
128
|
component: {
|
|
@@ -144,19 +140,20 @@ bot.callbackQuery("scene:cancel", async (ctx) => {
|
|
|
144
140
|
await ctx.editMessageReplyMarkup(null);
|
|
145
141
|
await ctx.answerCallbackQuery();
|
|
146
142
|
|
|
147
|
-
|
|
143
|
+
|
|
144
|
+
await ctx.reply("• Scene cancelled");
|
|
148
145
|
});
|
|
149
146
|
|
|
150
147
|
|
|
151
148
|
bot.command("start", async (ctx) => {
|
|
152
|
-
|
|
149
|
+
await scenes.enter(ctx, {
|
|
153
150
|
title: "auth",
|
|
154
151
|
step: "phone",
|
|
155
152
|
});
|
|
156
153
|
});
|
|
157
154
|
|
|
158
155
|
|
|
159
|
-
bot.use(
|
|
156
|
+
bot.use(scenes.middleware);
|
|
160
157
|
|
|
161
158
|
bot.start();
|
|
162
159
|
```
|
|
@@ -165,20 +162,18 @@ bot.start();
|
|
|
165
162
|
|
|
166
163
|
## Scene Definition
|
|
167
164
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
```js
|
|
165
|
+
```
|
|
171
166
|
{
|
|
172
167
|
scene: {
|
|
173
168
|
title: string,
|
|
174
169
|
step: string,
|
|
175
170
|
},
|
|
176
171
|
|
|
177
|
-
|
|
172
|
+
action: {
|
|
178
173
|
enter?: (ctx) => Promise<void>,
|
|
179
174
|
update?: (ctx) => Promise<SceneResult | void>,
|
|
180
175
|
message?: (ctx) => Promise<SceneResult | void>,
|
|
181
|
-
|
|
176
|
+
callbacks?: Array<{
|
|
182
177
|
match: RegExp,
|
|
183
178
|
handler: (ctx) => Promise<SceneResult | void>,
|
|
184
179
|
}>,
|
|
@@ -186,59 +181,35 @@ Each scene step is described by the following structure:
|
|
|
186
181
|
}
|
|
187
182
|
```
|
|
188
183
|
|
|
189
|
-
`SceneResult`
|
|
184
|
+
`SceneResult` determines the next transition.
|
|
190
185
|
|
|
191
186
|
---
|
|
192
187
|
|
|
193
188
|
## Callbacks
|
|
194
189
|
|
|
195
|
-
###
|
|
196
|
-
|
|
197
|
-
Triggered when entering a scene step.
|
|
198
|
-
Used to send messages, keyboards, or initialize data.
|
|
199
|
-
|
|
200
|
-
Details:
|
|
201
|
-
|
|
202
|
-
• Executed automatically on step entry
|
|
203
|
-
• Can freely use `ctx.reply`
|
|
204
|
-
• If the cancel button option is enabled, it is automatically appended to the inline keyboard
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
### callbacks.update(ctx)
|
|
190
|
+
### enter(ctx)
|
|
209
191
|
|
|
210
|
-
|
|
192
|
+
Executed on step entry.
|
|
211
193
|
|
|
212
|
-
|
|
194
|
+
* Used for messages and keyboards
|
|
195
|
+
* Cancel button can be injected automatically
|
|
213
196
|
|
|
214
|
-
|
|
215
|
-
• Receives the raw grammY context
|
|
216
|
-
• Can be used for global step logic (timeouts, guards, media handling)
|
|
217
|
-
• Return value controls navigation using standard SceneResult rules
|
|
218
|
-
|
|
219
|
-
---
|
|
197
|
+
### update(ctx)
|
|
220
198
|
|
|
221
|
-
|
|
199
|
+
Executed on any update while the step is active.
|
|
222
200
|
|
|
223
|
-
|
|
201
|
+
* Runs before `message` and `callbacks`
|
|
202
|
+
* Suitable for guards, timeouts, media
|
|
203
|
+
* Return value controls navigation
|
|
224
204
|
|
|
225
|
-
|
|
205
|
+
### message(ctx)
|
|
226
206
|
|
|
227
|
-
|
|
207
|
+
Triggered on text messages during the step.
|
|
228
208
|
|
|
229
|
-
|
|
209
|
+
### callbacks[]
|
|
230
210
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
```js
|
|
234
|
-
{
|
|
235
|
-
match: RegExp,
|
|
236
|
-
handler: async (ctx) => SceneResult
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
When `match` matches `ctx.callbackQuery.data`, the corresponding handler is executed.
|
|
241
|
-
Its return value is processed using the same transition logic as `callbacks.message`.
|
|
211
|
+
CallbackQuery handlers matched by regexp.
|
|
212
|
+
The handler return value follows standard transition rules.
|
|
242
213
|
|
|
243
214
|
---
|
|
244
215
|
|
|
@@ -246,120 +217,73 @@ Its return value is processed using the same transition logic as `callbacks.mess
|
|
|
246
217
|
|
|
247
218
|
### scenes.enter(ctx, scene)
|
|
248
219
|
|
|
249
|
-
Forces
|
|
250
|
-
|
|
251
|
-
```js
|
|
252
|
-
await scenes.enter(ctx, {
|
|
253
|
-
title: "auth",
|
|
254
|
-
step: "phone",
|
|
255
|
-
});
|
|
256
|
-
```
|
|
220
|
+
Forces entering a scene step.
|
|
257
221
|
|
|
258
222
|
Behavior:
|
|
259
223
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
---
|
|
224
|
+
* Writes to `ctx.session.scene`
|
|
225
|
+
* Resolves step
|
|
226
|
+
* Executes `enter`
|
|
227
|
+
* Temporarily patches `ctx.reply` for cancel button injection
|
|
266
228
|
|
|
267
|
-
###
|
|
229
|
+
### Manual Termination
|
|
268
230
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
```js
|
|
231
|
+
```
|
|
272
232
|
ctx.session.scene = null;
|
|
273
233
|
```
|
|
274
234
|
|
|
275
|
-
After this, the middleware stops processing scene events.
|
|
276
|
-
|
|
277
235
|
---
|
|
278
236
|
|
|
279
237
|
## Middleware
|
|
280
238
|
|
|
281
|
-
```
|
|
239
|
+
```
|
|
282
240
|
bot.use(session(...));
|
|
283
241
|
bot.use(scenes.middleware);
|
|
284
242
|
```
|
|
285
243
|
|
|
286
244
|
The middleware:
|
|
287
245
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
• Executes step transitions
|
|
246
|
+
* Resolves the active step
|
|
247
|
+
* Dispatches updates to callbacks
|
|
248
|
+
* Applies transitions
|
|
292
249
|
|
|
293
|
-
|
|
250
|
+
Must be registered after session middleware.
|
|
294
251
|
|
|
295
252
|
---
|
|
296
253
|
|
|
297
254
|
## Step Navigation
|
|
298
255
|
|
|
299
|
-
Transitions are
|
|
256
|
+
Transitions are driven by callback return values.
|
|
300
257
|
|
|
301
|
-
###
|
|
258
|
+
### Transitions
|
|
302
259
|
|
|
303
|
-
|
|
304
|
-
|
|
260
|
+
* `undefined`, `"next"`, `">"` → next step
|
|
261
|
+
* `"prev"`, `"<"` → previous step
|
|
262
|
+
* `"stop"`, `"exit"`, `"!"` → terminate scene
|
|
263
|
+
* `"^step"` → jump within current scene
|
|
264
|
+
* `"^scene:step"` → jump to another scene
|
|
265
|
+
* `{ step }` → absolute step
|
|
266
|
+
* `{ scene: { title, step } }` → absolute scene
|
|
305
267
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
### All Transitions
|
|
268
|
+
### Termination
|
|
309
269
|
|
|
310
|
-
|
|
311
|
-
• `"stop" | "exit" | "!"` → terminate the scene
|
|
312
|
-
• `"<" | "prev"` → move to the previous step
|
|
313
|
-
• `">" | "next"` → move to the next step
|
|
314
|
-
• `"^step"` → jump to a step within the current scene
|
|
315
|
-
• `"^scene:step"` → jump to another scene
|
|
316
|
-
• `{ step: "..." }` → jump to a specific step
|
|
317
|
-
• `{ scene: { title, step } }` → jump to another scene
|
|
318
|
-
|
|
319
|
-
---
|
|
270
|
+
Scene termination sets:
|
|
320
271
|
|
|
321
|
-
### Relative Transitions
|
|
322
|
-
|
|
323
|
-
• `undefined` / `"next"` / `">"` → next step
|
|
324
|
-
• `"prev"` / `"<"` → previous step
|
|
325
|
-
|
|
326
|
-
---
|
|
327
|
-
|
|
328
|
-
### Absolute Transitions
|
|
329
|
-
|
|
330
|
-
• `"^step"` → step within the current scene
|
|
331
|
-
• `"^scene:step"` → jump to another scene
|
|
332
|
-
|
|
333
|
-
---
|
|
334
|
-
|
|
335
|
-
### Object-Based Transitions
|
|
336
|
-
|
|
337
|
-
```js
|
|
338
|
-
return { step: "confirm" };
|
|
339
272
|
```
|
|
340
|
-
|
|
341
|
-
```js
|
|
342
|
-
return {
|
|
343
|
-
scene: {
|
|
344
|
-
title: "auth",
|
|
345
|
-
step: "phone",
|
|
346
|
-
},
|
|
347
|
-
};
|
|
273
|
+
ctx.session.scene = null;
|
|
348
274
|
```
|
|
349
275
|
|
|
350
276
|
---
|
|
351
277
|
|
|
352
278
|
## Options
|
|
353
279
|
|
|
354
|
-
|
|
280
|
+
### action:enter:extra:reply_markup:inline_keyboard:cancel
|
|
355
281
|
|
|
356
|
-
|
|
282
|
+
Injects a cancel button into all `enter` replies.
|
|
357
283
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
```js
|
|
284
|
+
```
|
|
361
285
|
{
|
|
362
|
-
"
|
|
286
|
+
["action:enter:extra:reply_markup:inline_keyboard:cancel"]: {
|
|
363
287
|
enabled: true,
|
|
364
288
|
|
|
365
289
|
component: {
|
|
@@ -370,29 +294,23 @@ Adds a cancel button to all messages sent inside `callbacks.enter`.
|
|
|
370
294
|
}
|
|
371
295
|
```
|
|
372
296
|
|
|
373
|
-
|
|
297
|
+
Notes:
|
|
374
298
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
• Scene cancellation logic must be implemented by the user
|
|
299
|
+
* Only affects `enter`
|
|
300
|
+
* Cancellation logic is user-defined
|
|
378
301
|
|
|
379
302
|
---
|
|
380
303
|
|
|
381
|
-
##
|
|
382
|
-
|
|
383
|
-
### new Scenes(scenes, options)
|
|
384
|
-
|
|
385
|
-
Creates a scene manager instance.
|
|
304
|
+
## API
|
|
386
305
|
|
|
387
|
-
|
|
306
|
+
### new Scenes(scenes, options, DEV?)
|
|
388
307
|
|
|
389
|
-
|
|
390
|
-
• `options` — configuration object
|
|
308
|
+
Creates a scene manager backed by `ScenesMiddleware`.
|
|
391
309
|
|
|
392
310
|
Exports:
|
|
393
311
|
|
|
394
|
-
|
|
395
|
-
|
|
312
|
+
* `scenes.middleware`
|
|
313
|
+
* `scenes.enter(ctx, scene)`
|
|
396
314
|
|
|
397
315
|
---
|
|
398
316
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,59 +1,68 @@
|
|
|
1
1
|
class ScenesMiddleware {
|
|
2
2
|
constructor({
|
|
3
3
|
scenes,
|
|
4
|
-
options
|
|
4
|
+
options,
|
|
5
|
+
DEV,
|
|
5
6
|
}) {
|
|
6
7
|
this.scenes = scenes;
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
this.map = {}; {
|
|
9
11
|
for (const group of scenes) {
|
|
10
12
|
for (const item of group) {
|
|
11
13
|
const { title, step } = item.scene;
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
if (!this.
|
|
15
|
-
this.
|
|
16
|
+
if (!this.map[title]) {
|
|
17
|
+
this.map[title] = {
|
|
18
|
+
order: [],
|
|
19
|
+
steps: {},
|
|
20
|
+
};
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
|
|
19
|
-
this.
|
|
24
|
+
this.map[title].order.push(step);
|
|
25
|
+
this.map[title].steps[step] = item;
|
|
20
26
|
};
|
|
21
27
|
};
|
|
22
28
|
};
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
this.options = {
|
|
26
|
-
["
|
|
32
|
+
["action:enter:extra:reply_markup:inline_keyboard:cancel"]: {
|
|
27
33
|
enabled: true,
|
|
28
34
|
|
|
29
35
|
component: {
|
|
30
|
-
text: "
|
|
36
|
+
text: "Cancel",
|
|
31
37
|
callback_data: "scene:cancel",
|
|
32
38
|
},
|
|
33
39
|
},
|
|
34
40
|
|
|
35
41
|
...options,
|
|
36
42
|
};
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
this.DEV = DEV;
|
|
37
46
|
};
|
|
38
47
|
|
|
39
48
|
|
|
40
49
|
scene = (scene) => {
|
|
41
|
-
return this.
|
|
50
|
+
return (this.map?.[scene?.title]?.steps?.[scene?.step] ?? null);
|
|
42
51
|
};
|
|
43
52
|
|
|
44
53
|
step = (scene, dir) => {
|
|
45
|
-
const
|
|
46
|
-
if (!
|
|
54
|
+
const entry = this.map?.[scene?.title]; {
|
|
55
|
+
if (!entry) return null;
|
|
47
56
|
};
|
|
48
57
|
|
|
49
58
|
|
|
50
|
-
const index =
|
|
59
|
+
const index = entry.order.indexOf(scene?.step); {
|
|
51
60
|
if (index == -1) return null;
|
|
52
61
|
};
|
|
53
62
|
|
|
54
63
|
|
|
55
|
-
if (dir == "next") return (
|
|
56
|
-
if (dir == "prev") return (
|
|
64
|
+
if (dir == "next") return (entry.order[(index + 1)] ?? null);
|
|
65
|
+
if (dir == "prev") return (entry.order[(index - 1)] ?? null);
|
|
57
66
|
|
|
58
67
|
|
|
59
68
|
return null;
|
|
@@ -65,15 +74,19 @@ class ScenesMiddleware {
|
|
|
65
74
|
|
|
66
75
|
|
|
67
76
|
const current = this.scene(scene); {
|
|
68
|
-
if (!current?.
|
|
77
|
+
if (!current?.action?.enter) return;
|
|
69
78
|
};
|
|
70
79
|
|
|
71
80
|
|
|
72
|
-
const original = ctx.reply.bind(ctx);
|
|
81
|
+
const original = ctx.reply.bind(ctx);
|
|
82
|
+
|
|
83
|
+
try {
|
|
73
84
|
ctx.reply = (text, extra = {}) => {
|
|
74
|
-
const keyboard =
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
const keyboard = [
|
|
86
|
+
...(extra.reply_markup?.inline_keyboard || []),
|
|
87
|
+
]; {
|
|
88
|
+
if (this.options?.["action:enter:extra:reply_markup:inline_keyboard:cancel"]?.enabled) keyboard.push([
|
|
89
|
+
this.options?.["action:enter:extra:reply_markup:inline_keyboard:cancel"]?.component,
|
|
77
90
|
]);
|
|
78
91
|
};
|
|
79
92
|
|
|
@@ -81,15 +94,17 @@ class ScenesMiddleware {
|
|
|
81
94
|
return original(text, {
|
|
82
95
|
...extra,
|
|
83
96
|
reply_markup: {
|
|
97
|
+
...(extra?.reply_markup || {}),
|
|
84
98
|
inline_keyboard: keyboard,
|
|
85
99
|
},
|
|
86
100
|
});
|
|
87
101
|
};
|
|
88
|
-
};
|
|
89
102
|
|
|
90
103
|
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
await current.action.enter(ctx);
|
|
105
|
+
} finally {
|
|
106
|
+
ctx.reply = original;
|
|
107
|
+
};
|
|
93
108
|
};
|
|
94
109
|
|
|
95
110
|
handle = async (ctx, scene, result) => {
|
|
@@ -103,7 +118,17 @@ class ScenesMiddleware {
|
|
|
103
118
|
const step = this.step({
|
|
104
119
|
title: scene.title,
|
|
105
120
|
step: scene.step,
|
|
106
|
-
}, "prev");
|
|
121
|
+
}, "prev"); {
|
|
122
|
+
if (!step) {
|
|
123
|
+
if (this.DEV) {
|
|
124
|
+
console.warn(`[grammy-scenes] No previous step found for scene "${scene.title}" (current step: "${scene.step}")`);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
ctx.session.scene = null;
|
|
129
|
+
return;
|
|
130
|
+
};
|
|
131
|
+
};
|
|
107
132
|
|
|
108
133
|
|
|
109
134
|
return await this.enter(ctx, {
|
|
@@ -116,7 +141,17 @@ class ScenesMiddleware {
|
|
|
116
141
|
const step = this.step({
|
|
117
142
|
title: scene.title,
|
|
118
143
|
step: scene.step,
|
|
119
|
-
}, "next");
|
|
144
|
+
}, "next"); {
|
|
145
|
+
if (!step) {
|
|
146
|
+
if (this.DEV) {
|
|
147
|
+
console.warn(`[grammy-scenes] No next step found for scene "${scene.title}" (current step: "${scene.step}")`);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
ctx.session.scene = null;
|
|
152
|
+
return;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
120
155
|
|
|
121
156
|
|
|
122
157
|
return await this.enter(ctx, {
|
|
@@ -167,32 +202,38 @@ class ScenesMiddleware {
|
|
|
167
202
|
|
|
168
203
|
|
|
169
204
|
const current = this.scene(scene); {
|
|
170
|
-
if (!current?.
|
|
205
|
+
if (!current?.action) return next();
|
|
171
206
|
};
|
|
172
207
|
|
|
173
208
|
|
|
174
|
-
if (ctx?.update && current.
|
|
175
|
-
const result = await current.
|
|
209
|
+
if (ctx?.update && current.action?.update) {
|
|
210
|
+
const result = await current.action.update(ctx);
|
|
176
211
|
|
|
177
212
|
return await this.handle(ctx, scene, result);
|
|
178
213
|
};
|
|
179
214
|
|
|
180
215
|
|
|
181
|
-
if (ctx?.message && current.
|
|
216
|
+
if (ctx?.message && current.action?.message) {
|
|
182
217
|
if (ctx?.message?.caption) ctx.message.text ??= ctx.message.caption;
|
|
183
218
|
|
|
184
219
|
|
|
185
|
-
const result = await current.
|
|
220
|
+
const result = await current.action.message(ctx);
|
|
186
221
|
|
|
187
222
|
return await this.handle(ctx, scene, result);
|
|
188
223
|
};
|
|
189
224
|
|
|
190
225
|
|
|
191
|
-
if (ctx?.callbackQuery && current.callbacks
|
|
226
|
+
if (ctx?.callbackQuery && current.action?.callbacks) {
|
|
227
|
+
if (!Array.isArray(current.action.callbacks)) return next();
|
|
228
|
+
|
|
229
|
+
|
|
192
230
|
const data = ctx.callbackQuery.data;
|
|
193
231
|
|
|
194
232
|
|
|
195
|
-
for (const q of current.callbacks
|
|
233
|
+
for (const q of current.action.callbacks) {
|
|
234
|
+
if (!q?.match?.test || !q?.handler) continue;
|
|
235
|
+
|
|
236
|
+
|
|
196
237
|
if (q.match.test(data)) {
|
|
197
238
|
const result = await q.handler(ctx);
|
|
198
239
|
|
|
@@ -208,14 +249,15 @@ class ScenesMiddleware {
|
|
|
208
249
|
|
|
209
250
|
|
|
210
251
|
class Scenes {
|
|
211
|
-
constructor(scenes, options = {}) {
|
|
252
|
+
constructor(scenes = [], options = {}, DEV = false) {
|
|
212
253
|
this.instance = new ScenesMiddleware({
|
|
213
254
|
scenes: scenes,
|
|
214
255
|
options: options,
|
|
256
|
+
DEV: DEV,
|
|
215
257
|
});
|
|
216
258
|
|
|
217
259
|
|
|
218
|
-
this.middleware = this.instance.middleware;
|
|
260
|
+
this.middleware = this.instance.middleware.bind(this.instance);
|
|
219
261
|
this.enter = this.instance.enter.bind(this.instance);
|
|
220
262
|
};
|
|
221
263
|
};
|