路由器(router)
Router 类(API 参考)提供了一种更为灵活的方式来结构化你的 bot,通过路由上下文对象到不同的部分代码。 它是 Composer 的 bot 的更高级版本(grammY API 参考)。
示例
这里是一个路由器的使用示例,不言自明。
ts
const router = new Router((ctx) => {
// 在此处确定路由。
return "key";
});
router.route("key", async (ctx) => {/* ... */});
router.route("other-key", async (ctx) => {/* ... */});
router.otherwise((ctx) => {/* ... */}); // 如果没有匹配的路由,则调用此方法
bot.use(router);1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
与中间件集成
自然,路由器插件与 grammY 的 中间件树 可以无缝集成。 例如,你可以在路由之后继续过滤 updates。
ts
router.route("key").on("message:text", async (ctx) => {/* ... */});
bot.use(router);
const other = router.otherwise();
other.on(":text", async (ctx) => {/* ... */});
other.use((ctx) => {/* ... */});1
2
3
4
5
2
3
4
5
你可能还想回顾一下这一 章节,了解更多关于中间件的组合。
绑定路由器和会话
路由器与 会话 可以完美结合。 作为一个示例,结合路由器与会话可以让你在聊天界面中重新创建表单。
请注意,更好的解决方案是使用 对话插件。 自该插件创建以来,此页面的其余部分已过时。 我们将保留此页面作为那些使用路由器实现表单的参考。
假设你想创建一个让用户知道他们生日剩余多少天的机器人。 为了计算天数,机器人必须知道生日的月份(例如:6月)和日期(例如:15日)。
因此,机器人必须要询问两个问题:
- 在哪个月生日?
- 在哪一天生日?
只有当两个值都已知时,机器人才能告诉用户剩余多少天。
这是如何实现这样的机器人的示例:
ts
import { Bot, Context, Keyboard, session, SessionFlavor } from "grammy";
import { Router } from "@grammyjs/router";
interface SessionData {
step: "idle" | "day" | "month"; // 我们在表单的哪一步
dayOfMonth?: number; // 生日日期
month?: number; // 生日月份
}
type MyContext = Context & SessionFlavor<SessionData>;
const bot = new Bot<MyContext>("");
// 使用会话。
bot.use(session({ initial: (): SessionData => ({ step: "idle" }) }));
// 定义一些命令。
bot.command("start", async (ctx) => {
await ctx.reply(`欢迎!
我可以告诉你还有几天到你的生日!
发送 /birthday 开始吧~`);
});
bot.command("birthday", async (ctx) => {
const day = ctx.session.dayOfMonth;
const month = ctx.session.month;
if (day !== undefined && month !== undefined) {
// 已经提供了信息!
await ctx.reply(`距离你的生日还有 ${getDays(month, day)} 天!`);
} else {
// 缺少信息,进入路由器的表单。
ctx.session.step = "day";
await ctx.reply("请把你生日的日期以数字形式发送给我~");
}
});
// 使用路由器。
const router = new Router<MyContext>((ctx) => ctx.session.step);
// 定义一个处理日期的步骤。
const day = router.route("day");
day.on("message:text", async (ctx) => {
const day = parseInt(ctx.msg.text, 10);
if (isNaN(day) || day < 1 || 31 < day) {
await ctx.reply("啊哦,日期好像无效捏,再试一次吧!");
return;
}
ctx.session.dayOfMonth = day;
// 提前进入月份的步骤
ctx.session.step = "month";
await ctx.reply("好嘞~现在请发送你的生日月份给我!", {
reply_markup: {
one_time_keyboard: true,
keyboard: new Keyboard()
.text("Jan").text("Feb").text("Mar").row()
.text("Apr").text("May").text("Jun").row()
.text("Jul").text("Aug").text("Sep").row()
.text("Oct").text("Nov").text("Dec").build(),
},
});
});
day.use((ctx) => ctx.reply("请把日期以文字消息形式发送给我!"));
// 定义一个处理月份的步骤。
const month = router.route("month");
month.on("message:text", async (ctx) => {
// 应该不会发生,除非会话数据被破坏。
const day = ctx.session.dayOfMonth;
if (day === undefined) {
await ctx.reply("咱还不知道你的生日日期和月份呢~");
ctx.session.step = "day";
return;
}
const month = months.indexOf(ctx.msg.text);
if (month === -1) {
await ctx.reply("啊哦,月份好像无效捏,请使用按钮发送~");
return;
}
ctx.session.month = month;
const diff = getDays(month, day);
await ctx.reply(
`你的生日在 ${months[month]} ${day}。
还有 ${diff} 天就到啦!`,
{ reply_markup: { remove_keyboard: true } },
);
ctx.session.step = "idle";
});
month.use((ctx) => ctx.reply("请点击其中一个按钮!"));
router.otherwise(async (ctx) => {
await ctx.reply("发送 /birthday 看看还有多久到你的生日。");
});
bot.use(router); // 注册路由器
bot.start();
// 日期转换工具
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
function getDays(month: number, day: number) {
const bday = new Date();
const now = Date.now();
bday.setMonth(month);
bday.setDate(day);
if (bday.getTime() < now) bday.setFullYear(bday.getFullYear() + 1);
const diff = (bday.getTime() - now) / (1000 * 60 * 60 * 24);
return diff;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
js
const { Bot, Context, Keyboard, session, SessionFlavor } = require("grammy");
const { Router } = require("@grammyjs/router");
const bot = new Bot("");
// 使用会话。
bot.use(session({ initial: () => ({ step: "idle" }) }));
// 定义一些命令。
bot.command("start", async (ctx) => {
await ctx.reply(`欢迎!
我可以告诉你还有几天到你的生日!
发送 /birthday 开始吧~`);
});
bot.command("birthday", async (ctx) => {
const day = ctx.session.dayOfMonth;
const month = ctx.session.month;
if (day !== undefined && month !== undefined) {
// 已经提供了信息!
await ctx.reply(`距离你的生日还有 ${getDays(month, day)} 天!`);
} else {
// 缺少信息,进入路由器的表单。
ctx.session.step = "day";
await ctx.reply("请把你生日的日期以数字形式发送给我~");
}
});
// 使用路由器。
const router = new Router((ctx) => ctx.session.step);
// 定义一个处理日期的步骤。
const day = router.route("day");
day.on("message:text", async (ctx) => {
const day = parseInt(ctx.msg.text, 10);
if (isNaN(day) || day < 1 || 31 < day) {
await ctx.reply("啊哦,日期好像无效捏,再试一次吧!");
return;
}
ctx.session.dayOfMonth = day;
// 提前进入月份的步骤
ctx.session.step = "month";
await ctx.reply("好嘞~现在请发送你的生日月份给我!", {
reply_markup: {
one_time_keyboard: true,
keyboard: new Keyboard()
.text("Jan").text("Feb").text("Mar").row()
.text("Apr").text("May").text("Jun").row()
.text("Jul").text("Aug").text("Sep").row()
.text("Oct").text("Nov").text("Dec").build(),
},
});
});
day.use((ctx) => ctx.reply("请把日期以文字消息形式发送给我!"));
// 定义一个处理月份的步骤。
const month = router.route("month");
month.on("message:text", async (ctx) => {
// 应该不会发生,除非会话数据被破坏。
const day = ctx.session.dayOfMonth;
if (day === undefined) {
await ctx.reply("咱还不知道你的生日日期和月份呢~");
ctx.session.step = "day";
return;
}
const month = months.indexOf(ctx.msg.text);
if (month === -1) {
await ctx.reply("啊哦,月份好像无效捏,请使用按钮发送~");
return;
}
ctx.session.month = month;
const diff = getDays(month, day);
await ctx.reply(
`你的生日在 ${months[month]} ${day}。
还有 ${diff} 天就到啦!`,
{ reply_markup: { remove_keyboard: true } },
);
ctx.session.step = "idle";
});
month.use((ctx) => ctx.reply("请点击其中一个按钮!"));
router.otherwise(async (ctx) => {
await ctx.reply("发送 /birthday 看看还有多久到你的生日。");
});
bot.use(router); // 注册路由器
bot.start();
// 日期转换工具
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
function getDays(month, day) {
const bday = new Date();
const now = Date.now();
bday.setMonth(month);
bday.setDate(day);
if (bday.getTime() < now) bday.setFullYear(bday.getFullYear() + 1);
const diff = (bday.getTime() - now) / (1000 * 60 * 60 * 24);
return diff;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
ts
import { Bot, Context, Keyboard, session, SessionFlavor } from "npm:grammy";
import { Router } from "npm:@grammyjs/router";
interface SessionData {
step: "idle" | "day" | "month"; // 我们在表单的哪一步
dayOfMonth?: number; // 生日日期
month?: number; // 生日月份
}
type MyContext = Context & SessionFlavor<SessionData>;
const bot = new Bot<MyContext>("");
// 使用会话。
bot.use(session({ initial: (): SessionData => ({ step: "idle" }) }));
// 定义一些命令。
bot.command("start", async (ctx) => {
await ctx.reply(`欢迎!
我可以告诉你还有几天到你的生日!
发送 /birthday 开始吧~`);
});
bot.command("birthday", async (ctx) => {
const day = ctx.session.dayOfMonth;
const month = ctx.session.month;
if (day !== undefined && month !== undefined) {
// 已经提供了信息!
await ctx.reply(`距离你的生日还有 ${getDays(month, day)} 天!`);
} else {
// 缺少信息,进入路由器的表单。
ctx.session.step = "day";
await ctx.reply("请把你生日的日期以数字形式发送给我~");
}
});
// 使用路由器。
const router = new Router<MyContext>((ctx) => ctx.session.step);
// 定义一个处理日期的步骤。
const day = router.route("day");
day.on("message:text", async (ctx) => {
const day = parseInt(ctx.msg.text, 10);
if (isNaN(day) || day < 1 || 31 < day) {
await ctx.reply("啊哦,日期好像无效捏,再试一次吧!");
return;
}
ctx.session.dayOfMonth = day;
// 提前进入月份的步骤
ctx.session.step = "month";
await ctx.reply("好嘞~现在请发送你的生日月份给我!", {
reply_markup: {
one_time_keyboard: true,
keyboard: new Keyboard()
.text("Jan").text("Feb").text("Mar").row()
.text("Apr").text("May").text("Jun").row()
.text("Jul").text("Aug").text("Sep").row()
.text("Oct").text("Nov").text("Dec").build(),
},
});
});
day.use((ctx) => ctx.reply("请把日期以文字消息形式发送给我!"));
// 定义一个处理月份的步骤。
const month = router.route("month");
month.on("message:text", async (ctx) => {
// 应该不会发生,除非会话数据被破坏。
const day = ctx.session.dayOfMonth;
if (day === undefined) {
await ctx.reply("咱还不知道你的生日日期和月份呢~");
ctx.session.step = "day";
return;
}
const month = months.indexOf(ctx.msg.text);
if (month === -1) {
await ctx.reply("啊哦,月份好像无效捏,请使用按钮发送~");
return;
}
ctx.session.month = month;
const diff = getDays(month, day);
await ctx.reply(
`你的生日在 ${months[month]} ${day}。
还有 ${diff} 天就到啦!`,
{ reply_markup: { remove_keyboard: true } },
);
ctx.session.step = "idle";
});
month.use((ctx) => ctx.reply("请点击其中一个按钮!"));
router.otherwise(async (ctx) => {
await ctx.reply("发送 /birthday 看看还有多久到你的生日。");
});
bot.use(router); // 注册路由器
bot.start();
// 日期转换工具
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
function getDays(month: number, day: number) {
const bday = new Date();
const now = Date.now();
bday.setMonth(month);
bday.setDate(day);
if (bday.getTime() < now) bday.setFullYear(bday.getFullYear() + 1);
const diff = (bday.getTime() - now) / (1000 * 60 * 60 * 24);
return diff;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
请注意,会话有一个属性 step,它存储表单的步骤,即当前正在填写的值。 路由器用于跳转到不同的中间件,完成 month 和 day 字段的填写。 如果两个值都已知,机器人计算剩余天数并发送给用户。