token_obtainment.py 9.6 KB

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