api_protection.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. # █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
  2. # █▀█ █ █ █ █▀█ █▀▄ █
  3. # © Copyright 2022
  4. # https://t.me/hikariatama
  5. #
  6. # 🔒 Licensed under the GNU AGPLv3
  7. # 🌐 https://www.gnu.org/licenses/agpl-3.0.html
  8. # meta pic: https://img.icons8.com/emoji/344/shield-emoji.png
  9. # meta developer: @hikariatama
  10. import asyncio
  11. import io
  12. import json
  13. import logging
  14. import time
  15. from telethon.tl.types import Message
  16. from .. import loader, utils
  17. from ..inline.types import InlineCall
  18. logger = logging.getLogger(__name__)
  19. @loader.tds
  20. class APIRatelimiterMod(loader.Module):
  21. """Helps userbot avoid spamming Telegram API"""
  22. strings = {
  23. "name": "APIRatelimiter",
  24. "warning": (
  25. "🚫 <b>WARNING!</b>\n\nYour account exceeded the limit of requests,"
  26. " specified in config. In order to prevent Telegram API Flood, userbot has"
  27. " been <b>fully frozen</b> for {} seconds. Further info is provided in"
  28. " attached file. \n\nIt is recommended to get help in"
  29. " <code>{prefix}support</code> group!\n\nIf you think, that it is an"
  30. " intended behavior, then wait until userbot gets unlocked and next time,"
  31. " when you will be going to perform such an operation, use"
  32. " <code>{prefix}suspend_api_protect</code> &lt;time in seconds&gt;"
  33. ),
  34. "args_invalid": "🚫 <b>Invalid arguments</b>",
  35. "suspended_for": "✅ <b>API Flood Protection is disabled for {} seconds</b>",
  36. "test": (
  37. "⚠️ <b>This action will expose your account to flooding Telegram API.</b>"
  38. " <i>In order to confirm, that you really know, what you are doing,"
  39. " complete this simple test - find the emoji, differing from others</i>"
  40. ),
  41. "on": "✅ <b>Protection enabled</b>",
  42. "off": "🚫 <b>Protection disabled</b>",
  43. "u_sure": "⚠️ <b>Are you sure?</b>",
  44. }
  45. strings_ru = {
  46. "warning": (
  47. "🚫 <b>ВНИМАНИЕ!</b>\n\nАккаунт вышел за лимиты запросов, указанные в"
  48. " конфиге. С целью предотвращения флуда Telegram API, юзербот был"
  49. " <b>полностью заморожен</b> на {} секунд. Дополнительная информация"
  50. " прикреплена в файле ниже. \n\nРекомендуется обратиться за помощью в"
  51. " <code>{prefix}support</code> группу!\n\nЕсли ты считаешь, что это"
  52. " запланированное поведение юзербота, просто подожди, пока закончится"
  53. " таймер и в следующий раз, когда запланируешь выполнять такую"
  54. " ресурсозатратную операцию, используй"
  55. " <code>{prefix}suspend_api_protect</code> &lt;время в секундах&gt;"
  56. ),
  57. "args_invalid": "🚫 <b>Неверные аргументы</b>",
  58. "suspended_for": "✅ <b>Защита API отключена на {} секунд</b>",
  59. "test": (
  60. "⚠️ <b>Это действие открывает юзерботу возможность флудить Telegram"
  61. " API.</b> <i>Для того, чтобы убедиться, что ты действительно уверен в том,"
  62. " что делаешь - реши простенький тест - найди отличающийся эмодзи.</i>"
  63. ),
  64. "on": "✅ <b>Защита включена</b>",
  65. "off": "🚫 <b>Защита отключена</b>",
  66. "u_sure": "⚠️ <b>Ты уверен?</b>",
  67. }
  68. _ratelimiter = []
  69. _suspend_until = 0
  70. _lock = False
  71. def __init__(self):
  72. self.config = loader.ModuleConfig(
  73. loader.ConfigValue(
  74. "time_sample",
  75. 15,
  76. lambda: "Time sample DO NOT TOUCH",
  77. validator=loader.validators.Integer(minimum=1),
  78. ),
  79. loader.ConfigValue(
  80. "threshold",
  81. 100,
  82. lambda: "Threshold DO NOT TOUCH",
  83. validator=loader.validators.Integer(minimum=10),
  84. ),
  85. loader.ConfigValue(
  86. "local_floodwait",
  87. 30,
  88. lambda: "Local FW DO NOT TOUCH",
  89. validator=loader.validators.Integer(minimum=10, maximum=3600),
  90. ),
  91. )
  92. async def client_ready(self, *_):
  93. asyncio.ensure_future(self._install_protection())
  94. async def _install_protection(self):
  95. await asyncio.sleep(30) # Restart lock
  96. if hasattr(self._client._call, "_old_call_rewritten"):
  97. raise loader.SelfUnload("Already installed")
  98. old_call = self._client._call
  99. async def new_call(
  100. sender: "MTProtoSender", # type: ignore
  101. request: "TLRequest", # type: ignore
  102. ordered: bool = False,
  103. flood_sleep_threshold: int = None,
  104. ):
  105. if time.perf_counter() > self._suspend_until and not self.get(
  106. "disable_protection",
  107. True,
  108. ):
  109. request_name = type(request).__name__
  110. self._ratelimiter += [[request_name, time.perf_counter()]]
  111. self._ratelimiter = list(
  112. filter(
  113. lambda x: time.perf_counter() - x[1]
  114. < int(self.config["time_sample"]),
  115. self._ratelimiter,
  116. )
  117. )
  118. if (
  119. len(self._ratelimiter) > int(self.config["threshold"])
  120. and not self._lock
  121. ):
  122. self._lock = True
  123. report = io.BytesIO(
  124. json.dumps(
  125. self._ratelimiter,
  126. indent=4,
  127. ).encode("utf-8")
  128. )
  129. report.name = "local_fw_report.json"
  130. await self.inline.bot.send_document(
  131. self.tg_id,
  132. report,
  133. caption=self.strings("warning").format(
  134. self.config["local_floodwait"],
  135. prefix=self.get_prefix(),
  136. ),
  137. )
  138. # It is intented to use time.sleep instead of asyncio.sleep
  139. time.sleep(int(self.config["local_floodwait"]))
  140. self._lock = False
  141. return await old_call(sender, request, ordered, flood_sleep_threshold)
  142. self._client._call = new_call
  143. self._client._old_call_rewritten = old_call
  144. self._client._call._hikka_overwritten = True
  145. logger.debug("Successfully installed ratelimiter")
  146. async def on_unload(self):
  147. if hasattr(self._client, "_old_call_rewritten"):
  148. self._client._call = self._client._old_call_rewritten
  149. delattr(self._client, "_old_call_rewritten")
  150. logger.debug("Successfully uninstalled ratelimiter")
  151. async def suspend_api_protectcmd(self, message: Message):
  152. """<time in seconds> - Suspend API Ratelimiter for n seconds"""
  153. args = utils.get_args_raw(message)
  154. if not args or not args.isdigit():
  155. await utils.answer(message, self.strings("args_invalid"))
  156. return
  157. self._suspend_until = time.perf_counter() + int(args)
  158. await utils.answer(message, self.strings("suspended_for").format(args))
  159. async def api_fw_protectioncmd(self, message: Message):
  160. """Only for people, who know what they're doing"""
  161. await self.inline.form(
  162. message=message,
  163. text=self.strings("u_sure"),
  164. reply_markup=[
  165. {"text": "🚫 No", "action": "close"},
  166. {"text": "✅ Yes", "callback": self._finish},
  167. ],
  168. )
  169. async def _finish(self, call: InlineCall):
  170. state = self.get("disable_protection", True)
  171. self.set("disable_protection", not state)
  172. await call.edit(self.strings("on" if state else "off"))