@monixlite/grammy-scenes 1.0.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 +127 -0
- package/package.json +20 -0
- package/src/index.js +199 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @monixlite/grammy-scenes
|
|
2
|
+
|
|
3
|
+
Scene middleware for grammY with step-based navigation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @monixlite/grammy-scenes
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
* Node.js >= 16
|
|
14
|
+
* grammY >= 1.19
|
|
15
|
+
|
|
16
|
+
## Basic Usage
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
const { Bot, session } = require("grammy");
|
|
20
|
+
const { Scenes } = require("@monixlite/grammy-scenes");
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const bot = new Bot(""); {
|
|
24
|
+
bot.use(session({
|
|
25
|
+
initial: () => ({
|
|
26
|
+
scene: null,
|
|
27
|
+
step: null,
|
|
28
|
+
name: null,
|
|
29
|
+
age: null,
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const scene = new Scenes([
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
scene: {
|
|
39
|
+
title: "auth",
|
|
40
|
+
step: "phone",
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
callbacks: {
|
|
44
|
+
enter: async (ctx) => {
|
|
45
|
+
await ctx.reply("Введите номер:");
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
message: async (ctx) => {
|
|
49
|
+
await ctx.reply(`Номер: ${ctx.message.text.trim()}`);
|
|
50
|
+
|
|
51
|
+
return "stop";
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
bot.command("start", async (ctx) => {
|
|
60
|
+
await scene.enter(ctx, {
|
|
61
|
+
title: "auth",
|
|
62
|
+
step: "phone",
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
bot.use(scene.middleware);
|
|
68
|
+
|
|
69
|
+
bot.start();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Scene Definition
|
|
73
|
+
|
|
74
|
+
Каждый шаг сцены описывается объектом:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
{
|
|
78
|
+
scene: {
|
|
79
|
+
title: string,
|
|
80
|
+
step: string,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
callbacks: {
|
|
84
|
+
enter?: (ctx) => Promise<void>,
|
|
85
|
+
message?: (ctx) => Promise<"next" | "stop" | void>,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### callbacks.enter
|
|
91
|
+
|
|
92
|
+
Вызывается при входе в шаг сцены.
|
|
93
|
+
|
|
94
|
+
### callbacks.message
|
|
95
|
+
|
|
96
|
+
Вызывается при получении сообщения пользователя.
|
|
97
|
+
Возврат `"stop"` завершает сцену.
|
|
98
|
+
|
|
99
|
+
## Scene Control
|
|
100
|
+
|
|
101
|
+
### Enter scene
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
await scenes.enter(ctx, {
|
|
105
|
+
title: "auth",
|
|
106
|
+
step: "phone",
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Cancel scene manually
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
ctx.session.scene = null;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Middleware
|
|
117
|
+
|
|
118
|
+
Middleware должен быть подключён **после** session middleware:
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
bot.use(session(...));
|
|
122
|
+
bot.use(scenes.middleware);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monixlite/grammy-scenes",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scene middleware for grammY with step-based navigation",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"grammy",
|
|
9
|
+
"telegram",
|
|
10
|
+
"bot",
|
|
11
|
+
"scenes",
|
|
12
|
+
"middleware",
|
|
13
|
+
"monixlite"
|
|
14
|
+
],
|
|
15
|
+
"author": "monixlite",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"grammy": "^1.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
class ScenesMiddleware {
|
|
2
|
+
constructor({ scenes }) {
|
|
3
|
+
this.scenes = scenes;
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
this.steps = {}; {
|
|
7
|
+
for (const group of scenes) {
|
|
8
|
+
for (const item of group) {
|
|
9
|
+
const { title, step } = item.scene;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if (!this.steps[title]) {
|
|
13
|
+
this.steps[title] = [];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
this.steps[title].push(step);
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
scene = (scene) => {
|
|
25
|
+
return this.scenes.flat().find((x) => (x.scene.title == scene?.title && x.scene.step == scene?.step));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
step = (scene, dir) => {
|
|
29
|
+
const list = this.steps[scene?.title]; {
|
|
30
|
+
if (!list) return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const index = list.indexOf(scene?.step); {
|
|
35
|
+
if (index == -1) return null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if (dir == "next") return (list[(index + 1)] ?? null);
|
|
40
|
+
if (dir == "prev") return (list[(index - 1)] ?? null);
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
enter = async (ctx, scene) => {
|
|
48
|
+
ctx.session.scene = scene;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
const current = this.scene(scene); {
|
|
52
|
+
if (!current?.callbacks?.enter) return;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
const original = ctx.reply.bind(ctx); {
|
|
57
|
+
ctx.reply = (text, extra = {}) => {
|
|
58
|
+
return original(text, {
|
|
59
|
+
...extra,
|
|
60
|
+
reply_markup: {
|
|
61
|
+
inline_keyboard: [
|
|
62
|
+
...(extra.reply_markup?.inline_keyboard || []),
|
|
63
|
+
[
|
|
64
|
+
{
|
|
65
|
+
text: "Отменить",
|
|
66
|
+
callback_data: "scene:cancel",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
await current.callbacks.enter(ctx);
|
|
77
|
+
ctx.reply = original;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
handle = async (ctx, scene, result) => {
|
|
81
|
+
if (result?.exit || (["!", "exit", "stop"]).includes(result)) {
|
|
82
|
+
ctx.session.scene = null;
|
|
83
|
+
return;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if (result?.prev || (["<", "prev"]).includes(result)) {
|
|
88
|
+
const step = this.step({
|
|
89
|
+
title: scene.title,
|
|
90
|
+
step: scene.step,
|
|
91
|
+
}, "prev");
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
return await this.enter(ctx, {
|
|
95
|
+
title: scene.title,
|
|
96
|
+
step: step,
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (!result || result?.next || ([">", "next"]).includes(result)) {
|
|
101
|
+
const step = this.step({
|
|
102
|
+
title: scene.title,
|
|
103
|
+
step: scene.step,
|
|
104
|
+
}, "next");
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
return await this.enter(ctx, {
|
|
108
|
+
title: scene.title,
|
|
109
|
+
step: step,
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if (result?.scene) return await this.enter(ctx, {
|
|
115
|
+
title: result.scene.title,
|
|
116
|
+
step: result.scene.step,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (result?.step) return await this.enter(ctx, {
|
|
120
|
+
title: scene.title,
|
|
121
|
+
step: result.step,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
const transition = String(result).match(/^\^(.*)$/); {
|
|
126
|
+
if (!transition || !transition?.[1]) {
|
|
127
|
+
ctx.session.scene = null;
|
|
128
|
+
return;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
const parts = transition[1].split(":"); {
|
|
133
|
+
if (parts.length == 1) return await this.enter(ctx, {
|
|
134
|
+
title: scene.title,
|
|
135
|
+
step: parts[0],
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
return await this.enter(ctx, {
|
|
141
|
+
title: parts[0],
|
|
142
|
+
step: parts[1],
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
middleware = async (ctx, next) => {
|
|
149
|
+
const { scene } = ctx.session; {
|
|
150
|
+
if (!scene?.title || !scene?.step) return next();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
const current = this.scene(scene); {
|
|
155
|
+
if (!current?.callbacks) return next();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if (ctx.message && current.callbacks.message) {
|
|
160
|
+
const result = await current.callbacks.message(ctx);
|
|
161
|
+
|
|
162
|
+
return await this.handle(ctx, scene, result);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if (ctx.callbackQuery && current.callbacks.query) {
|
|
167
|
+
const data = ctx.callbackQuery.data;
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
for (const q of current.callbacks.query) {
|
|
171
|
+
if (q.match.test(data)) {
|
|
172
|
+
const result = await q.handler(ctx);
|
|
173
|
+
|
|
174
|
+
return await this.handle(ctx, scene, result);
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
return next();
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class Scenes {
|
|
186
|
+
constructor(scenes) {
|
|
187
|
+
this.instance = new ScenesMiddleware({
|
|
188
|
+
scenes: scenes,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
this.middleware = this.instance.middleware;
|
|
193
|
+
this.enter = this.instance.enter.bind(this.instance);
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
Scenes,
|
|
199
|
+
};
|