token_obtainment.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. import asyncio
  9. import io
  10. import logging
  11. import re
  12. import requests
  13. from telethon.errors.rpcerrorlist import YouBlockedUserError
  14. from telethon.tl.functions.contacts import UnblockRequest
  15. from .. import utils
  16. from .types import InlineUnit
  17. logger = logging.getLogger(__name__)
  18. photo = io.BytesIO(
  19. requests.get(
  20. "https://github.com/hikariatama/Hikka/raw/master/assets/bot_pfp.png"
  21. ).content
  22. )
  23. photo.name = "avatar.png"
  24. class TokenObtainment(InlineUnit):
  25. async def _create_bot(self):
  26. # This is called outside of conversation, so we can start the new one
  27. # We create new bot
  28. logger.info("User don't have bot, attempting creating new one")
  29. async with self._client.conversation("@BotFather", exclusive=False) as conv:
  30. m = await conv.send_message("/newbot")
  31. r = await conv.get_response()
  32. logger.debug(f">> {m.raw_text}")
  33. logger.debug(f"<< {r.raw_text}")
  34. if "20" in r.raw_text:
  35. return False
  36. await m.delete()
  37. await r.delete()
  38. if self._db.get("hikka.inline", "custom_bot", False):
  39. username = self._db.get("hikka.inline", "custom_bot").strip("@")
  40. username = f"@{username}"
  41. try:
  42. await self._client.get_entity(username)
  43. except ValueError:
  44. pass
  45. else:
  46. # Generate and set random username for bot
  47. uid = utils.rand(6)
  48. username = f"@hikka_{uid}_bot"
  49. else:
  50. # Generate and set random username for bot
  51. uid = utils.rand(6)
  52. username = f"@hikka_{uid}_bot"
  53. for msg in [
  54. f"🌘 Hikka Userbot of {self._name}"[:64],
  55. username,
  56. "/setuserpic",
  57. username,
  58. ]:
  59. m = await conv.send_message(msg)
  60. r = await conv.get_response()
  61. logger.debug(f">> {m.raw_text}")
  62. logger.debug(f"<< {r.raw_text}")
  63. await m.delete()
  64. await r.delete()
  65. try:
  66. m = await conv.send_file(photo)
  67. r = await conv.get_response()
  68. logger.debug(">> <Photo>")
  69. logger.debug(f"<< {r.raw_text}")
  70. except Exception:
  71. # In case user was not able to send photo to
  72. # BotFather, it is not a critical issue, so
  73. # just ignore it
  74. m = await conv.send_message("/cancel")
  75. r = await conv.get_response()
  76. logger.debug(f">> {m.raw_text}")
  77. logger.debug(f"<< {r.raw_text}")
  78. await m.delete()
  79. await r.delete()
  80. # Re-attempt search. If it won't find newly created (or not created?) bot
  81. # it will return `False`, that's why `init_complete` will be `False`
  82. return await self._assert_token(False)
  83. async def _assert_token(
  84. self,
  85. create_new_if_needed=True,
  86. revoke_token=False,
  87. ):
  88. # If the token is set in db
  89. if self._token:
  90. # Just return `True`
  91. return True
  92. logger.info("Bot token not found in db, attempting search in BotFather")
  93. await utils.dnd(self._client, await self._client.get_entity("@BotFather"), True)
  94. # Start conversation with BotFather to attempt search
  95. async with self._client.conversation("@BotFather", exclusive=False) as conv:
  96. # Wrap it in try-except in case user banned BotFather
  97. try:
  98. # Try sending command
  99. m = await conv.send_message("/token")
  100. except YouBlockedUserError:
  101. # If user banned BotFather, unban him
  102. await self._client(UnblockRequest(id="@BotFather"))
  103. # And resend message
  104. m = await conv.send_message("/token")
  105. r = await conv.get_response()
  106. logger.debug(f">> {m.raw_text}")
  107. logger.debug(f"<< {r.raw_text}")
  108. await m.delete()
  109. await r.delete()
  110. # User do not have any bots yet, so just create new one
  111. if not hasattr(r, "reply_markup") or not hasattr(r.reply_markup, "rows"):
  112. # Cancel current conversation (search)
  113. # bc we don't need it anymore
  114. await conv.cancel_all()
  115. return await self._create_bot() if create_new_if_needed else False
  116. for row in r.reply_markup.rows:
  117. for button in row.buttons:
  118. if self._db.get(
  119. "hikka.inline", "custom_bot", False
  120. ) and self._db.get(
  121. "hikka.inline", "custom_bot", False
  122. ) != button.text.strip(
  123. "@"
  124. ):
  125. continue
  126. if not self._db.get(
  127. "hikka.inline",
  128. "custom_bot",
  129. False,
  130. ) and not re.search(r"@hikka_[0-9a-zA-Z]{6}_bot", button.text):
  131. continue
  132. m = await conv.send_message(button.text)
  133. r = await conv.get_response()
  134. logger.debug(f">> {m.raw_text}")
  135. logger.debug(f"<< {r.raw_text}")
  136. if revoke_token:
  137. await m.delete()
  138. await r.delete()
  139. m = await conv.send_message("/revoke")
  140. r = await conv.get_response()
  141. logger.debug(f">> {m.raw_text}")
  142. logger.debug(f"<< {r.raw_text}")
  143. await m.delete()
  144. await r.delete()
  145. m = await conv.send_message(button.text)
  146. r = await conv.get_response()
  147. logger.debug(f">> {m.raw_text}")
  148. logger.debug(f"<< {r.raw_text}")
  149. token = r.raw_text.splitlines()[1]
  150. # Save token to database, now this bot is ready-to-use
  151. self._db.set("hikka.inline", "bot_token", token)
  152. self._token = token
  153. await m.delete()
  154. await r.delete()
  155. # Enable inline mode or change its
  156. # placeholder in case it is not set
  157. for msg in [
  158. "/setinline",
  159. button.text,
  160. "HikkaQuery",
  161. "/setinlinefeedback",
  162. button.text,
  163. "Enabled",
  164. "/setuserpic",
  165. button.text,
  166. ]:
  167. m = await conv.send_message(msg)
  168. r = await conv.get_response()
  169. logger.debug(f">> {m.raw_text}")
  170. logger.debug(f"<< {r.raw_text}")
  171. await m.delete()
  172. await r.delete()
  173. try:
  174. m = await conv.send_file(photo)
  175. r = await conv.get_response()
  176. logger.debug(">> <Photo>")
  177. logger.debug(f"<< {r.raw_text}")
  178. except Exception:
  179. # In case user was not able to send photo to
  180. # BotFather, it is not a critical issue, so
  181. # just ignore it
  182. m = await conv.send_message("/cancel")
  183. r = await conv.get_response()
  184. logger.debug(f">> {m.raw_text}")
  185. logger.debug(f"<< {r.raw_text}")
  186. await m.delete()
  187. await r.delete()
  188. # Return `True` to say, that everything is okay
  189. return True
  190. # And we are not returned after creation
  191. return await self._create_bot() if create_new_if_needed else False
  192. async def _reassert_token(self):
  193. is_token_asserted = await self._assert_token(revoke_token=True)
  194. if not is_token_asserted:
  195. self.init_complete = False
  196. else:
  197. await self._register_manager(ignore_token_checks=True)
  198. async def _dp_revoke_token(self, already_initialised: bool = True):
  199. if already_initialised:
  200. await self._stop()
  201. logger.error("Got polling conflict. Attempting token revocation...")
  202. self._db.set("hikka.inline", "bot_token", None)
  203. self._token = None
  204. if already_initialised:
  205. asyncio.ensure_future(self._reassert_token())
  206. else:
  207. return await self._reassert_token()