# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
# █▀█ █ █ █ █▀█ █▀▄ █
# © Copyright 2022
# https://t.me/hikariatama
#
# 🔒 Licensed under the GNU AGPLv3
# 🌐 https://www.gnu.org/licenses/agpl-3.0.html
import asyncio
import io
import json
import logging
import time
from telethon.tl import functions
from telethon.tl.tlobject import TLRequest
from telethon.tl.types import Message
from .. import loader, utils
from ..inline.types import InlineCall
from ..web.debugger import WebDebugger
logger = logging.getLogger(__name__)
GROUPS = [
"auth",
"account",
"users",
"contacts",
"messages",
"updates",
"photos",
"upload",
"help",
"channels",
"bots",
"payments",
"stickers",
"phone",
"langpack",
"folders",
"stats",
]
def decapitalize(string: str) -> str:
return string[0].lower() + string[1:]
CONSTRUCTORS = {
decapitalize(
method.__class__.__name__.rsplit("Request", 1)[0]
): method.CONSTRUCTOR_ID
for method in utils.array_sum(
[
[
method
for method in dir(getattr(functions, group))
if isinstance(method, TLRequest)
]
for group in GROUPS
]
)
}
@loader.tds
class APIRatelimiterMod(loader.Module):
"""Helps userbot avoid spamming Telegram API"""
strings = {
"name": "APILimiter",
"warning": (
"⚠️"
" WARNING!\n\nYour account exceeded the limit of requests, specified"
" in config. In order to prevent Telegram API Flood, userbot has been"
" fully frozen for {} seconds. Further info is provided in attached"
" file. \n\nIt is recommended to get help in {prefix}support
"
" group!\n\nIf you think, that it is an intended behavior, then wait until"
" userbot gets unlocked and next time, when you will be going to perform"
" such an operation, use {prefix}suspend_api_protect
<time"
" in seconds>"
),
"args_invalid": (
"🚫 Invalid arguments"
),
"suspended_for": (
"👌 API Flood Protection"
" is disabled for {} seconds"
),
"on": (
"👌 Protection enabled"
),
"off": (
"👌 Protection"
" disabled"
),
"u_sure": "⚠️ Are you sure?",
"_cfg_time_sample": "Time sample through which the bot will count requests",
"_cfg_threshold": "Threshold of requests to trigger protection",
"_cfg_local_floodwait": (
"Freeze userbot for this amount of time, if request limit exceeds"
),
"_cfg_forbidden_methods": (
"Forbid specified methods from being executed throughout external modules"
),
"btn_no": "🚫 No",
"btn_yes": "✅ Yes",
"web_pin": (
"🔓 Click the button below to show Werkzeug debug PIN. Do not give it to"
" anyone."
),
"web_pin_btn": "🐞 Show Werkzeug PIN",
"proxied_url": "🌐 Proxied URL",
"local_url": "🏠 Local URL",
}
strings_ru = {
"warning": (
"⚠️"
" ВНИМАНИЕ!\n\nАккаунт вышел за лимиты запросов, указанные в"
" конфиге. С целью предотвращения флуда Telegram API, юзербот был"
" полностью заморожен на {} секунд. Дополнительная информация"
" прикреплена в файле ниже. \n\nРекомендуется обратиться за помощью в"
" {prefix}support
группу!\n\nЕсли ты считаешь, что это"
" запланированное поведение юзербота, просто подожди, пока закончится"
" таймер и в следующий раз, когда запланируешь выполнять такую"
" ресурсозатратную операцию, используй"
" {prefix}suspend_api_protect
<время в секундах>"
),
"args_invalid": (
"🚫 Неверные аргументы"
),
"suspended_for": (
"👌 Защита API отключена"
" на {} секунд"
),
"on": "👌 Защита включена",
"off": (
"👌 Защита отключена"
),
"u_sure": "⚠️ Ты уверен?",
"_cfg_time_sample": (
"Временной промежуток, по которому будет считаться количество запросов"
),
"_cfg_threshold": "Порог запросов, при котором будет срабатывать защита",
"_cfg_local_floodwait": (
"Заморозить юзербота на это количество секунд, если лимит запросов превышен"
),
"_cfg_forbidden_methods": (
"Запретить выполнение указанных методов во всех внешних модулях"
),
"btn_no": "🚫 Нет",
"btn_yes": "✅ Да",
"web_pin": (
"🔓 Нажми на кнопку ниже, чтобы показать Werkzeug debug PIN. Не давай его"
" никому."
),
"web_pin_btn": "🐞 Показать Werkzeug PIN",
"proxied_url": "🌐 Проксированная ссылка",
"local_url": "🏠 Локальная ссылка",
}
strings_de = {
"warning": (
"⚠️"
" Achtung!\n\nDas Konto hat die in der Konfiguration angegebenen"
" Grenzwerte für Anfragen überschritten. Um Telegram API-Flooding zu"
" verhindern, wurde der ganze Userbot für {} Sekunden"
" eingefroren. Weitere Informationen finden Sie im unten angefügten"
" Datei.\n\nWir empfehlen Ihnen, sich mit Hilfe der {prefix}"
"support
Gruppe zu helfen!\n\nWenn du denkst, dass dies"
" geplantes Verhalten des Userbots ist, dann warte einfach, bis der"
" Timer abläuft und versuche beim nächsten Mal, eine so ressourcen"
" intensive Operation wie {prefix}suspend_api_protect
"
" <Zeit in Sekunden> zu planen."
),
"args_invalid": (
"🚫 Ungültige"
" Argumente"
),
"suspended_for": (
"👌 API Flood"
" Protection ist für {} Sekunden deaktiviert"
),
"on": (
"👌 Schutz aktiviert"
),
"off": (
"👌 Schutz deaktiviert"
),
"u_sure": "⚠️ Bist du sicher?",
"_cfg_time_sample": "Zeitintervall, in dem die Anfragen gezählt werden",
"_cfg_threshold": (
"Schwellenwert für Anfragen, ab dem der Schutz aktiviert wird"
),
"_cfg_local_floodwait": (
"Einfrieren des Userbots für diese Anzahl von Sekunden, wenn der Grenzwert"
" überschritten wird"
),
"_cfg_forbidden_methods": "Verbotene Methoden in allen externen Modulen",
"btn_no": "🚫 Nein",
"btn_yes": "✅ Ja",
"web_pin": (
"🔓 Drücke auf die Schaltfläche unten, um den Werkzeug debug PIN"
" anzuzeigen. Gib ihn niemandem."
),
"web_pin_btn": "🐞 Werkzeug PIN anzeigen",
"proxied_url": "🌐 Proxied URL",
"local_url": "🏠 Lokale URL",
}
strings_tr = {
"warning": (
"⚠️ Dikkat!\n\nHesap"
" yapılandırmasında belirtilen sınır değerlerini aştı. Telegram API"
" sızmalarını önlemek için tüm Userbot {} sanie donduruldu. Daha"
" fazla bilgi için aşağıya eklenen dosyaya bakın.\n\nLütfen"
" {prefix}support
grubu ile yardım almak için destek"
" olun!\n\nEğer bu, Userbot'un planlanmış davranışı olduğunu"
" düşünüyorsanız, zamanlayıcı bittiğinde ve"
" {prefix}suspend_api_protect
<saniye cinsinden süre>"
" gibi kaynak tüketen bir işlemi planladığınızda yeniden deneyin."
),
"args_invalid": (
"🚫 Geçersiz"
" argümanlar"
),
"suspended_for": (
"👌 API Flood koruması {}"
" saniyeliğine durduruldu."
),
"on": (
"👌 Koruma"
" aktifleştirildi."
),
"off": (
"👌 Koruma"
" de-aktifleştirildi"
),
"u_sure": "⚠️ Emin misin?",
"_cfg_time_sample": "Saniyede sayılan isteklerin zaman aralığı",
"_cfg_threshold": "Korumanın etkinleşeceği sınır değeri",
"_cfg_local_floodwait": (
"Telegram API sınır değeri aşıldığında kullanıcı botu bir süre durdurulur"
),
"_cfg_forbidden_methods": (
"Belirtili metodların harici modüller tarafından çalıştırılmasını yasakla"
),
"btn_no": "🚫 Hayır",
"btn_yes": "✅ Evet",
"web_pin": (
"🔓 Werkzeug hata ayıklama PIN'ini göstermek için aşağıdaki düğmeyi"
" tıklayın. Onu kimseye vermeyin."
),
"web_pin_btn": "🐞 Werkzeug PIN'ini göster",
"proxied_url": "🌐 Proxied URL",
"local_url": "🏠 Lokal URL",
}
strings_uz = {
"warning": (
"⚠️"
" Ogohlantirish!\n\nBu hisob uchun konfiguratsiyada ko'rsatilgan"
" chegaralar chegarani o'zgartirgan.\n\nTelegram API Flood"
" to'xtatish uchun, bu hammasi userbot uchun {} sekundni"
" blokirovka qilindi. Batafsil ma'lumot uchun pastdagi faylni o'qing.\n\n"
"Yordam uchun {prefix}support
guruhidan foydalaning!\n\nAgar"
" siz hisobni botning yordamchisi bo'lishi kerak bo'lgan amalni bajarishga"
" imkoniyat berishga o'xshaysiz, unda faqat blokirovkani to'xtatish uchun"
" {prefix}suspend_api_protect
<sekund> dan foydalaning."
),
"args_invalid": (
"🚫 Noto'g'ri argument"
),
"suspended_for": (
"👌 API Flood"
" himoya {} sekund uchun to'xtatildi"
),
"on": "👌 Himoya yoqildi",
"off": (
"👌 Himoya o'chirildi"
),
"u_sure": "⚠️ Siz ishonchingiz komilmi?",
"_cfg_time_sample": "Sekundda qabul qilinadigan so'rovlar soni chegarasi",
"_cfg_threshold": "Himoya yoqish uchun qiymatni chegaralash",
"_cfg_local_floodwait": (
"Foydalanuvchi botni ushbu soniya davomida blokirovka qiladi, agar"
" chegaralar qiymati oshsa"
),
"_cfg_forbidden_methods": "Barcha tashqi modullarda taqiqlangan usullar",
"btn_no": "🚫 Yo'q",
"btn_yes": "✅ Ha",
"web_pin": (
"🔓 Werkzeug Debug PIN kodini ko'rsatish uchun quyidagi tugmani bosing."
" Uni hech kimga bermang."
),
"web_pin_btn": "🐞 Werkzeug PIN-ni ko'rsatish",
"proxied_url": "🌐 Proxied URL",
"local_url": "🏠 Lokal URL",
}
strings_es = {
"warning": (
"⚠️"
" ¡Advertencia!\n\nDe acuerdo con la configuración de esta cuenta,"
" las siguientes limitaciones serán aplicadas.\n\nSe bloqueará a todos"
" los bots de los usuarios por {} segundos para evitar el exceso de las"
" limitaciones de Telegram API. Para más información, consulta el archivo"
" siguiente.\n\nPara obtener ayuda, use el grupo"
" {prefix}support
!\n\nPara permitir que la cuenta funcione,"
" use {prefix}suspend_api_protect
para desbloquear."
),
"args_invalid": (
"🚫 Argumentos"
" inválidos"
),
"suspended_for": (
"👌"
" Se ha desactivado la protección de API por {} segundos"
),
"on": (
"👌 Protección"
" activada"
),
"off": (
"👌 Protección"
" desactivada"
),
"u_sure": "⚠️ ¿Estás seguro?",
"_cfg_time_sample": (
"El tiempo en segundos durante el cual se exceden las limitaciones"
),
"_cfg_threshold": "El valor por encima del cual se exceden las limitaciones",
"_cfg_local_floodwait": (
"El tiempo en segundos durante el cual se bloquea al usuario para el bot"
),
"_cfg_forbidden_methods": (
"Los comandos prohibidos por todas las extensiones externas"
),
"btn_no": "🚫 No",
"btn_yes": "✅ Sí",
"web_pin": (
"🔓 Haga clic en el botón de abajo para mostrar el PIN de depuración de"
" Werkzeug. No se lo des a nadie."
),
"web_pin_btn": "🐞 Mostrar el PIN de Werkzeug",
"proxied_url": "🌐 URL de proxy",
"local_url": "🏠 URL local",
}
strings_kk = {
"warning": (
"⚠️"
" Ескерту!\n\nБұл есептің конфигурациясына сәйкес, келесі"
" шектелген шарттар қолданылады.\n\nTelegram API үлеслерінен қорғалмасы"
" үшін, барлық пайдаланушылардың боттары {} секунд құлыпталады."
" Көбірек ақпарат үшін келесі файлды қараңыз.\n\nАнықтама үшін"
" {prefix}support
топын пайдаланыңыз!\n\nЕгер сізге"
" бұл есептің боттың көмекшісі болуы керек болса, құлыпталуын өшіру үшін"
" {prefix}suspend_api_protect
<секунд> пайдаланыңыз."
),
"args_invalid": (
"🚫 Жарамсыз"
" аргументтер"
),
"suspended_for": (
"👌"
" API үлеслерін қорғалуы {} секунд үшін өшірілді"
),
"on": "👌 Қорғалу қосылды",
"off": (
"👌 Қорғалу өшірілді"
),
"u_sure": "⚠️ Сіз әлімдісіз бе?",
"_cfg_time_sample": "API үлеслерінен қорғалуы үшін көрсетілген уақыт (секунд)",
"_cfg_threshold": "API үлеслерінен қорғалуы үшін көрсетілген қаншалық",
"_cfg_local_floodwait": "Бот үшін пайдаланушыны құлыпталу уақыты (секунд)",
"_cfg_forbidden_methods": (
"Барлық сыртқы қосымшалардың қолданылуының тыйым салынған командалары"
),
"btn_no": "🚫 Жоқ",
"btn_yes": "✅ Иә",
"web_pin": (
"🔓 Werkzeug дебаг PIN кодын көрсету үшін төмендегі түймешікті"
" басыңыз. Оны кімсіне де бермеңіз."
),
"web_pin_btn": "🐞 Werkzeug PIN кодын көрсету",
"proxied_url": "🌐 Прокси URL",
"local_url": "🏠 Жергілікті URL",
}
_ratelimiter = []
_suspend_until = 0
_lock = False
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"time_sample",
15,
lambda: self.strings("_cfg_time_sample"),
validator=loader.validators.Integer(minimum=1),
),
loader.ConfigValue(
"threshold",
100,
lambda: self.strings("_cfg_threshold"),
validator=loader.validators.Integer(minimum=10),
),
loader.ConfigValue(
"local_floodwait",
30,
lambda: self.strings("_cfg_local_floodwait"),
validator=loader.validators.Integer(minimum=10, maximum=3600),
),
loader.ConfigValue(
"forbidden_methods",
["joinChannel", "importChatInvite"],
lambda: self.strings("_cfg_forbidden_methods"),
validator=loader.validators.MultiChoice(
[
"sendReaction",
"joinChannel",
"importChatInvite",
]
),
on_change=lambda: self._client.forbid_constructors(
map(
lambda x: CONSTRUCTORS[x], self.config["forbidden_constructors"]
)
),
),
)
async def client_ready(self):
asyncio.ensure_future(self._install_protection())
async def _install_protection(self):
await asyncio.sleep(30) # Restart lock
if hasattr(self._client._call, "_old_call_rewritten"):
raise loader.SelfUnload("Already installed")
old_call = self._client._call
async def new_call(
sender: "MTProtoSender", # type: ignore
request: "TLRequest", # type: ignore
ordered: bool = False,
flood_sleep_threshold: int = None,
):
if time.perf_counter() > self._suspend_until and not self.get(
"disable_protection",
True,
):
request_name = type(request).__name__
self._ratelimiter += [[request_name, time.perf_counter()]]
self._ratelimiter = list(
filter(
lambda x: time.perf_counter() - x[1]
< int(self.config["time_sample"]),
self._ratelimiter,
)
)
if (
len(self._ratelimiter) > int(self.config["threshold"])
and not self._lock
):
self._lock = True
report = io.BytesIO(
json.dumps(
self._ratelimiter,
indent=4,
).encode("utf-8")
)
report.name = "local_fw_report.json"
await self.inline.bot.send_document(
self.tg_id,
report,
caption=self.strings("warning").format(
self.config["local_floodwait"],
prefix=self.get_prefix(),
),
)
# It is intented to use time.sleep instead of asyncio.sleep
time.sleep(int(self.config["local_floodwait"]))
self._lock = False
return await old_call(sender, request, ordered, flood_sleep_threshold)
self._client._call = new_call
self._client._old_call_rewritten = old_call
self._client._call._hikka_overwritten = True
logger.debug("Successfully installed ratelimiter")
async def on_unload(self):
if hasattr(self._client, "_old_call_rewritten"):
self._client._call = self._client._old_call_rewritten
delattr(self._client, "_old_call_rewritten")
logger.debug("Successfully uninstalled ratelimiter")
@loader.command(
ru_doc="<время в секундах> - Заморозить защиту API на N секунд",
de_doc=" - API-Schutz für N Sekunden einfrieren",
tr_doc=" - API korumasını N saniye dondur",
uz_doc=" - API himoyasini N soniya o'zgartirish",
es_doc=" - Congela la protección de la API durante N segundos",
kk_doc="<секунд> - API қорғауын N секундтік уақытта құлыптау",
)
async def suspend_api_protect(self, message: Message):
"""