core.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. """Inline buttons, galleries and other Telegram-Bot-API stuff"""
  2. # ©️ Dan Gazizullin, 2021-2023
  3. # This file is a part of Hikka Userbot
  4. # 🌐 https://github.com/hikariatama/Hikka
  5. # You can redistribute it and/or modify it under the terms of the GNU AGPLv3
  6. # 🔑 https://www.gnu.org/licenses/agpl-3.0.html
  7. import asyncio
  8. import contextlib
  9. import logging
  10. import time
  11. import typing
  12. from aiogram import Bot, Dispatcher
  13. from aiogram.types import ParseMode
  14. from aiogram.utils.exceptions import TerminatedByOtherGetUpdates, Unauthorized
  15. from hikkatl.errors.rpcerrorlist import InputUserDeactivatedError, YouBlockedUserError
  16. from hikkatl.tl.functions.contacts import UnblockRequest
  17. from hikkatl.tl.types import Message
  18. from hikkatl.utils import get_display_name
  19. from .. import utils
  20. from ..database import Database
  21. from ..tl_cache import CustomTelegramClient
  22. from ..translations import Translator
  23. from .bot_pm import BotPM
  24. from .events import Events
  25. from .form import Form
  26. from .gallery import Gallery
  27. from .list import List
  28. from .query_gallery import QueryGallery
  29. from .token_obtainment import TokenObtainment
  30. from .utils import Utils
  31. logger = logging.getLogger(__name__)
  32. class InlineManager(
  33. Utils,
  34. Events,
  35. TokenObtainment,
  36. Form,
  37. Gallery,
  38. QueryGallery,
  39. List,
  40. BotPM,
  41. ):
  42. """
  43. Inline buttons, galleries and other Telegram-Bot-API stuff
  44. :param client: Telegram client
  45. :param db: Database instance
  46. :param allmodules: All modules
  47. :type client: hikka.tl_cache.CustomTelegramClient
  48. :type db: hikka.database.Database
  49. :type allmodules: hikka.loader.Modules
  50. """
  51. def __init__(
  52. self,
  53. client: CustomTelegramClient,
  54. db: Database,
  55. allmodules: "Modules", # type: ignore # noqa: F821
  56. ):
  57. """Initialize InlineManager to create forms"""
  58. self._client = client
  59. self._db = db
  60. self._allmodules = allmodules
  61. self.translator: Translator = allmodules.translator
  62. self._units: typing.Dict[str, dict] = {}
  63. self._custom_map: typing.Dict[str, callable] = {}
  64. self.fsm: typing.Dict[str, str] = {}
  65. self._web_auth_tokens: typing.List[str] = []
  66. self._error_events: typing.Dict[str, asyncio.Event] = {}
  67. self._markup_ttl = 60 * 60 * 24
  68. self.init_complete = False
  69. self._token = db.get("hikka.inline", "bot_token", False)
  70. self._me: int = None
  71. self._name: str = None
  72. self._dp: Dispatcher = None
  73. self._task: asyncio.Future = None
  74. self._cleaner_task: asyncio.Future = None
  75. self.bot: Bot = None
  76. self.bot_id: int = None
  77. self.bot_username: str = None
  78. async def _cleaner(self):
  79. """Cleans outdated inline units"""
  80. while True:
  81. for unit_id, unit in self._units.copy().items():
  82. if (unit.get("ttl") or (time.time() + self._markup_ttl)) < time.time():
  83. del self._units[unit_id]
  84. await asyncio.sleep(5)
  85. async def register_manager(
  86. self,
  87. after_break: bool = False,
  88. ignore_token_checks: bool = False,
  89. ):
  90. """
  91. Register manager
  92. :param after_break: Loop marker
  93. :param ignore_token_checks: If `True`, will not check for token
  94. :type after_break: bool
  95. :type ignore_token_checks: bool
  96. :return: None
  97. :rtype: None
  98. """
  99. self._me = self._client.tg_id
  100. self._name = get_display_name(self._client.hikka_me)
  101. if not ignore_token_checks:
  102. is_token_asserted = await self._assert_token()
  103. if not is_token_asserted:
  104. self.init_complete = False
  105. return
  106. self.init_complete = True
  107. self.bot = Bot(token=self._token, parse_mode=ParseMode.HTML)
  108. Bot.set_current(self.bot)
  109. self._bot = self.bot
  110. self._dp = Dispatcher(self.bot)
  111. try:
  112. bot_me = await self.bot.get_me()
  113. self.bot_username = bot_me.username
  114. self.bot_id = bot_me.id
  115. except Unauthorized:
  116. logger.critical("Token expired, revoking...")
  117. return await self._dp_revoke_token(False)
  118. try:
  119. m = await self._client.send_message(self.bot_username, "/start hikka init")
  120. except (InputUserDeactivatedError, ValueError):
  121. self._db.set("hikka.inline", "bot_token", None)
  122. self._token = False
  123. if not after_break:
  124. return await self.register_manager(True)
  125. self.init_complete = False
  126. return False
  127. except YouBlockedUserError:
  128. await self._client(UnblockRequest(id=self.bot_username))
  129. try:
  130. m = await self._client.send_message(
  131. self.bot_username, "/start hikka init"
  132. )
  133. except Exception:
  134. logger.critical("Can't unblock users bot", exc_info=True)
  135. return False
  136. except Exception:
  137. self.init_complete = False
  138. logger.critical("Initialization of inline manager failed!", exc_info=True)
  139. return False
  140. await self._client.delete_messages(self.bot_username, m)
  141. self._dp.register_inline_handler(
  142. self._inline_handler,
  143. lambda _: True,
  144. )
  145. self._dp.register_callback_query_handler(
  146. self._callback_query_handler,
  147. lambda _: True,
  148. )
  149. self._dp.register_chosen_inline_handler(
  150. self._chosen_inline_handler,
  151. lambda _: True,
  152. )
  153. self._dp.register_message_handler(
  154. self._message_handler,
  155. lambda *_: True,
  156. content_types=["any"],
  157. )
  158. old = self.bot.get_updates
  159. revoke = self._dp_revoke_token
  160. async def new(*args, **kwargs):
  161. nonlocal revoke, old
  162. try:
  163. return await old(*args, **kwargs)
  164. except TerminatedByOtherGetUpdates:
  165. await revoke()
  166. except Unauthorized:
  167. logger.critical("Got Unauthorized")
  168. await self._stop()
  169. self.bot.get_updates = new
  170. self._task = asyncio.ensure_future(self._dp.start_polling())
  171. self._cleaner_task = asyncio.ensure_future(self._cleaner())
  172. async def _stop(self):
  173. """Stop the bot"""
  174. self._task.cancel()
  175. self._dp.stop_polling()
  176. self._cleaner_task.cancel()
  177. def pop_web_auth_token(self, token: str) -> bool:
  178. """
  179. Check if web confirmation button was pressed
  180. :param token: Token to check
  181. :type token: str
  182. :return: `True` if token was found, `False` otherwise
  183. :rtype: bool
  184. """
  185. if token not in self._web_auth_tokens:
  186. return False
  187. self._web_auth_tokens.remove(token)
  188. return True
  189. async def _invoke_unit(self, unit_id: str, message: Message) -> Message:
  190. event = asyncio.Event()
  191. self._error_events[unit_id] = event
  192. q: "InlineResults" = None # type: ignore # noqa: F821
  193. exception: Exception = None
  194. async def result_getter():
  195. nonlocal unit_id, q
  196. with contextlib.suppress(Exception):
  197. q = await self._client.inline_query(self.bot_username, unit_id)
  198. async def event_poller():
  199. nonlocal exception
  200. await asyncio.wait_for(event.wait(), timeout=10)
  201. if self._error_events.get(unit_id):
  202. exception = self._error_events[unit_id]
  203. result_getter_task = asyncio.ensure_future(result_getter())
  204. event_poller_task = asyncio.ensure_future(event_poller())
  205. _, pending = await asyncio.wait(
  206. [result_getter_task, event_poller_task],
  207. return_when=asyncio.FIRST_COMPLETED,
  208. )
  209. for task in pending:
  210. task.cancel()
  211. self._error_events.pop(unit_id, None)
  212. if exception:
  213. raise exception # skipcq: PYL-E0702
  214. if not q:
  215. raise Exception("No query results")
  216. return await q[0].click(
  217. utils.get_chat_id(message) if isinstance(message, Message) else message,
  218. reply_to=(
  219. message.reply_to_msg_id if isinstance(message, Message) else None
  220. ),
  221. )