Rate Limit Users (ratelimiter
)
ratelimiter is a rate-limiting middleware for Telegram bots made with grammY or Telegraf bot frameworks. In simple terms, it is a plugin that helps you deflect heavy spamming in your bots. To understand ratelimiter better, you can take a look at the following illustration:
How Does It Work Exactly?
Under normal circumstances every request will be processed and answered by your bot which means spamming it will not be that difficult. Each user might send multiple requests per second and your script has to process each request, but how can you stop it? With ratelimiter!
Rate-Limiting Users, Not Telegram Servers!
You should note that this package DOES NOT rate limit the incoming requests from Telegram servers, instead, it tracks the incoming requests by from
and dismisses them on arrival, so no further processing load is added to your servers.
Customizability
This plugin exposes 5 customizable options:
time
: The time frame during which the requests will be monitored (defaults toFrame 1000
ms).limit
: The number of requests allowed within eachtime
(defaults toFrame 1
).storage
: The type of storage to use for keeping track of users and their requests. The default value isClient MEMORY
which uses an in-memory Map, but you can also pass in a Redis client as well (more info at About storage_STORE Client ).on
: A function that describes what to do if the user exceeds the limit (ignores the extra requests by default).Limit Exceeded key
: A function that returns a unique key generated for each user (it usesGenerator from
by default). This key is used to identify the user, therefore it should be unique, user specific and in string format..id
About storageClient
The MEMORY
or the in-memory tracking is suitable for most bots, however if you implement clustering for your bot you will not be able to use the in-memory storage effectively. That’s why the Redis option is provided as well. You can pass a Redis client from ioredis or redis in case you use Deno. In reality, any Redis driver that implements the incr
and pexpire
methods should work just fine. ratelimiter is driver agnostic.
Note: You must have redis-server 2.6.0 and above on your server to use Redis storage client with ratelimiter. Older versions of Redis are not supported.
How to Use
There are two ways of using ratelimiter:
- Accepting the defaults (Default Configuration).
- Passing a custom object containing your settings (Manual Configuration).
Default Configuration
This snippet demonstrates the easiest way of using ratelimiter, which is accepting the default behavior:
import { limit } from "@grammyjs/ratelimiter";
// Limits message handling to a message per second for each user.
bot.use(limit());
2
3
4
const { limit } = require("@grammyjs/ratelimiter");
// Limits message handling to a message per second for each user.
bot.use(limit());
2
3
4
import { limit } from "https://deno.land/x/grammy_ratelimiter@v1.2.0/mod.ts";
// Limits message handling to a message per second for each user.
bot.use(limit());
2
3
4
Manual Configuration
As mentioned earlier, you can pass an Options
object to the limit()
method to alter the limiter’s behavior.
import Redis from "ioredis";
import { limit } from "@grammyjs/ratelimiter";
const redis = new Redis(...);
bot.use(
limit({
// Allow only 3 messages to be handled every 2 seconds.
timeFrame: 2000,
limit: 3,
// "MEMORY_STORE" is the default value. If you do not want to use Redis, do not pass storageClient at all.
storageClient: redis,
// This is called when the limit is exceeded.
onLimitExceeded: async (ctx) => {
await ctx.reply("Please refrain from sending too many requests!");
},
// Note that the key should be a number in string format such as "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({
// Allow only 3 messages to be handled every 2 seconds.
timeFrame: 2000,
limit: 3,
// "MEMORY_STORE" is the default value. If you do not want to use Redis, do not pass storageClient at all.
storageClient: redis,
// This is called when the limit is exceeded.
onLimitExceeded: async (ctx) => {
await ctx.reply("Please refrain from sending too many requests!");
},
// Note that the key should be a number in string format such as "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({
// Allow only 3 messages to be handled every 2 seconds.
timeFrame: 2000,
limit: 3,
// "MEMORY_STORE" is the default value. If you do not want to use Redis, do not pass storageClient at all.
storageClient: redis,
// This is called when the limit is exceeded.
onLimitExceeded: async (ctx) => {
await ctx.reply("Please refrain from sending too many requests!");
},
// Note that the key should be a number in string format such as "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
As you can see in the example above, each user is allowed to send 3 requests every 2 seconds. If said user sends more requests, the bot replies with Please refrain from sending too many requests. That request will not travel further and dies immediately as we do not call next() in the middleware.
Note: To avoid flooding Telegram servers,
on
is only executed once in everyLimit Exceeded time
.Frame
Another use case would be limiting the incoming requests from a chat instead of a specific user:
import { limit } from "@grammyjs/ratelimiter";
bot.use(
limit({
keyGenerator: (ctx) => {
if (ctx.hasChatType(["group", "supergroup"])) {
// Note that the key should be a number in string format, such as "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"])) {
// Note that the key should be a number in string format, such as "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"])) {
// Note that the key should be a number in string format, such as "123456789".
return ctx.chat.id.toString();
}
},
}),
);
2
3
4
5
6
7
8
9
10
11
12
In this example, I have used chat
as the unique key for rate-limiting.