Лимит запросов пользователей (ratelimiter
)
ratelimiter — это middleware для ограничения скорости ботов Telegram, созданных с помощью grammY или Telegraf. Проще говоря, это плагин, который поможет вам предотвратить сильную спам рассылку в ваших ботах. Чтобы лучше понять суть ratelimiter, вы можете взглянуть на следующую иллюстрацию:
Как именно это работает?
При нормальных обстоятельствах каждый запрос будет обработан и получит ответ от вашего бота, а значит, заспамить его будет не так уж сложно. Каждый пользователь может отправлять несколько запросов в секунду, и вашему коду придется обрабатывать каждый запрос, но как это остановить? С помощью ratelimiter!
Ограничение скорости пользователей, а не серверов Telegram!
Обратите внимание, что этот пакет НЕ ограничивает входящие запросы от серверов Telegram, вместо этого он отслеживает входящие запросы по from
и отклоняет их по прибытии, поэтому на ваши сервера не ложится дополнительная нагрузка по обработке.
Настройка
Этот плагин предоставляет 5 настраиваемых опций:
time
: Временной интервал, в течение которого будут отслеживаться запросы (по умолчаниюFrame 1000
мс).limit
: Количество запросов, разрешенных в каждомтаймфрейме
(по умолчанию1
).storage
: Тип хранилища, которое будет использоваться для отслеживания пользователей и их запросов. По умолчанию используетсяClient MEMORY
, который использует in-memory Map, но вы также можете передать клиент Redis (подробнее в О storage_STORE Client ).on
: Функция, описывающая, что делать, если пользователь превысил лимит (по умолчанию игнорирует дополнительные запросы).Limit Exceeded key
: Функция, возвращающая уникальный ключ, сгенерированный для каждого пользователя (по умолчанию используетсяGenerator from
). Этот ключ используется для идентификации пользователя, поэтому он должен быть уникальным, специфичным для пользователя и иметь строковый формат..id
О storageClient
Вариант MEMORY
или отслеживание в памяти подходит для большинства ботов, однако если вы реализуете кластеризацию для своего бота, вы не сможете эффективно использовать хранилище в памяти. Именно поэтому предусмотрена возможность использования Redis. Вы можете передать клиент Redis из ioredis или redis, если вы используете Deno. В действительности, любой драйвер Redis, реализующий методы incr
и pexpire
, должен работать просто отлично. ratelimiter не зависит от драйвера.
Примечание: Для использования клиента хранилища Redis с ratelimiter на вашем сервере должен быть установлен 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
Ручная настройка
Как упоминалось ранее, вы можете передать объект Options
в метод limit()
, чтобы изменить поведение ratelimiter.
import Redis from "ioredis";
import { limit } from "@grammyjs/ratelimiter";
const redis = new Redis(...);
bot.use(
limit({
// Разрешите обрабатывать только 3 сообщения каждые 2 секунды.
timeFrame: 2000,
limit: 3,
// По умолчанию используется значение «MEMORY_STORE». Если вы не хотите использовать Redis, не передавайте storageClient вообще.
storageClient: redis,
// Эта функция вызывается при превышении лимита.
onLimitExceeded: async (ctx) => {
await ctx.reply("Пожалуйста, воздержитесь от отправки слишком большого количества запросов!");
},
// Обратите внимание, что ключ должен быть числом в строковом формате, например «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({
// Разрешите обрабатывать только 3 сообщения каждые 2 секунды.
timeFrame: 2000,
limit: 3,
// По умолчанию используется значение «MEMORY_STORE». Если вы не хотите использовать Redis, не передавайте storageClient вообще.
storageClient: redis,
// Эта функция вызывается при превышении лимита.
onLimitExceeded: async (ctx) => {
await ctx.reply("Пожалуйста, воздержитесь от отправки слишком большого количества запросов!");
},
// Обратите внимание, что ключ должен быть числом в строковом формате, например «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({
// Разрешите обрабатывать только 3 сообщения каждые 2 секунды.
timeFrame: 2000,
limit: 3,
// По умолчанию используется значение «MEMORY_STORE». Если вы не хотите использовать Redis, не передавайте storageClient вообще.
storageClient: redis,
// Эта функция вызывается при превышении лимита.
onLimitExceeded: async (ctx) => {
await ctx.reply("Пожалуйста, воздержитесь от отправки слишком большого количества запросов!");
},
// Обратите внимание, что ключ должен быть числом в строковом формате, например «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
Как видно из примера выше, каждому пользователю разрешено отправлять 3 запроса каждые 2 секунды. Если пользователь отправляет больше запросов, бот отвечает Пожалуйста, воздержитесь от отправки слишком большого количества запросов. Этот запрос не будет отправлен дальше и сразу же будет пропущен, так как мы не вызываем next() в middleware.
Примечание: Чтобы избежать переполнения серверов Telegram,
on
выполняется только один раз в каждомLimit Exceeded таймфрейме
.
Другим вариантом использования может быть ограничение входящих запросов от чата, а не от конкретного пользователя:
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
в качестве уникального ключа для ограничения скорости.