限制用户速率 (ratelimiter
)
ratelimiter 是用 grammY 或 Telegraf bot 框架制作的一个限速中间件。 简单来说,它是一个帮助你的 bot 阻塞垃圾信息的插件。 要更好地理解 ratelimiter,你可以看下面的图示:
它是如何工作的?
在正常情况下,每个请求都会被你的 bot 处理,这意味着发送垃圾信息不会很困难。 每个用户每秒可以发送多次请求,你的脚本必须处理每个请求,但是如何阻止它呢? 用 ratelimiter!
限制用户速率,而不是 Telegram 服务器!
你应该注意,这个插件 不会 限制来自 Telegram 服务器的请求,而是通过 from
跟踪请求,当请求到达时,它会被拒绝,从而不会增加到你的服务器的处理负荷。
可定制性
这个插件可以定制的选项有 5 个:
time
:对请求进行监测的时间范围(默认为Frame 1000
毫秒)。limit
:在每个time
内允许的请求数量(默认为Frame 1
)。storage
:一个用于跟踪用户和他们的请求的存储类型。默认值是Client MEMORY
,它使用内存中的 Map,但你也可以传入 Redis 客户端(更多信息在 关于 storage_STORE Client 中)。on
:如果用户超出限制,则执行的函数(默认值是忽略额外的请求)。Limit Exceeded key
:用于生成每个用户的唯一键的函数(默认值是使用Generator from
)。这个键用于标识用户,因此它应该是唯一的,用户特定的,并且是字符串格式。.id
关于 storageClient
MEMORY
或者内存中的跟踪是适用于大多数 bot 的,但如果你实现了 bot 集群,你将无法有效地使用内存存储。 这就是为什么也提供了 Redis 选项。 如果你使用 Deno,你可以传入 ioredis 或 redis 的客户端。 任何实现了 incr
和 pexpire
方法的 Redis 驱动器都可以正常工作。 ratelimiter 与驱动器无关。
请注意:如果你使用 Redis 存储类型,你必须在你的服务器上安装 Redis-server 2.6.0 及以上版本。 不支持老版本的 Redis。
如何使用
这里有两种方式使用 ratelimiter:
默认配置
这个示例演示了最简单的方式来使用默认行为的 ratelimiter:
import { limit } from "@grammyjs/ratelimiter";
// 将每个用户的信息处理限制在每秒一条信息。
bot.use(limit());
2
3
4
const { limit } = require("@grammyjs/ratelimiter");
// 将每个用户的信息处理限制在每秒一条信息。
bot.use(limit());
2
3
4
import { limit } from "https://deno.land/x/grammy_ratelimiter@v1.2.0/mod.ts";
// 将每个用户的信息处理限制在每秒一条信息。
bot.use(limit());
2
3
4
手动配置
正如前面所说,你可以向 limit()
方法传入一个 Options
对象来改变 ratelimiter 的行为。
import Redis from "ioredis";
import { limit } from "@grammyjs/ratelimiter";
const redis = new Redis(...);
bot.use(
limit({
// 每 2 秒只允许处理 3 条信息。
timeFrame: 2000,
limit: 3,
// "MEMORY_STORE" 是默认值。如果你不想使用 Redis,请不要传入 storageClient。
storageClient: redis,
// 当超过限制时执行调用。
onLimitExceeded: async (ctx) => {
await ctx.reply("Please refrain from sending too many requests!");
},
// 请注意,这个键应该是一个字符串格式的数字,如 "123456789"。
keyGenerator: (ctx) => {
return ctx.from?.id.toString();
},
})
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const Redis = require("ioredis");
const { limit } = require("@grammyjs/ratelimiter");
const redis = new Redis(...);
bot.use(
limit({
// 每 2 秒只允许处理 3 条信息。
timeFrame: 2000,
limit: 3,
// "MEMORY_STORE" 是默认值。如果你不想使用 Redis,请不要传入 storageClient。
storageClient: redis,
// 当超过限制时执行调用。
onLimitExceeded: async (ctx) => {
await ctx.reply("Please refrain from sending too many requests!");
},
// 请注意,这个键应该是一个字符串格式的数字,如 "123456789"。
keyGenerator: (ctx) => {
return ctx.from?.id.toString();
},
})
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { connect } from "https://deno.land/x/redis/mod.ts";
import { limit } from "https://deno.land/x/grammy_ratelimiter@v1.2.0/mod.ts";
const redis = await connect(...);
bot.use(
limit({
// 每 2 秒只允许处理 3 条信息。
timeFrame: 2000,
limit: 3,
// "MEMORY_STORE" 是默认值。如果你不想使用 Redis,请不要传入 storageClient。
storageClient: redis,
// 当超过限制时执行调用。
onLimitExceeded: async (ctx) => {
await ctx.reply("Please refrain from sending too many requests!");
},
// 请注意,这个键应该是一个字符串格式的数字,如 "123456789"。
keyGenerator: (ctx) => {
return ctx.from?.id.toString();
},
})
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
正如你在上面的示例中看到的,每个用户每 2 秒钟最多只能发送 3 次请求。 如果该用户发送更多请求,机器人会回复 Please refrain from sending too many requests!。 由于我们不调用 next(),这个请求将被立即关闭。
请注意:为了避免 Telegram 服务器被请求淹没,
on
只会在每个Limit Exceeded time
中执行一次。Frame
另一个用例是限制来自聊天室的请求而不是特定用户的请求:
import { limit } from "@grammyjs/ratelimiter";
bot.use(
limit({
keyGenerator: (ctx) => {
if (ctx.hasChatType(["group", "supergroup"])) {
// 请注意,这个键应该是一个字符串格式的数字,如 "123456789"。
return ctx.chat.id.toString();
}
},
}),
);
2
3
4
5
6
7
8
9
10
11
12
const { limit } = require("@grammyjs/ratelimiter");
bot.use(
limit({
keyGenerator: (ctx) => {
if (ctx.hasChatType(["group", "supergroup"])) {
// 请注意,这个键应该是一个字符串格式的数字,如 "123456789"。
return ctx.chat.id.toString();
}
},
}),
);
2
3
4
5
6
7
8
9
10
11
12
import { limit } from "https://deno.land/x/grammy_ratelimiter@v1.2.0/mod.ts";
bot.use(
limit({
keyGenerator: (ctx) => {
if (ctx.hasChatType(["group", "supergroup"])) {
// 请注意,这个键应该是一个字符串格式的数字,如 "123456789"。
return ctx.chat.id.toString();
}
},
}),
);
2
3
4
5
6
7
8
9
10
11
12
在这个示例中,我使用 chat
作为限制的唯一键。