python.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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 contextlib
  9. import itertools
  10. import logging
  11. import sys
  12. from types import ModuleType
  13. import os
  14. from typing import Any
  15. import telethon
  16. from meval import meval
  17. from telethon.errors.rpcerrorlist import MessageIdInvalidError
  18. from telethon.tl.types import Message
  19. from .. import loader, main, utils
  20. from ..inline.types import InlineCall
  21. from ..log import HikkaException
  22. logger = logging.getLogger(__name__)
  23. class FakeDbException(Exception):
  24. def __init__(self, *args, **kwargs):
  25. super().__init__(*args, **kwargs)
  26. class FakeDb:
  27. def __getattr__(self, *args, **kwargs):
  28. raise FakeDbException("Database read-write permission required")
  29. @loader.tds
  30. class PythonMod(loader.Module):
  31. """Evaluates python code"""
  32. strings = {
  33. "name": "Python",
  34. "eval": "<b>🎬 Code:</b>\n<code>{}</code>\n<b>🪄 Result:</b>\n<code>{}</code>",
  35. "err": "<b>🎬 Code:</b>\n<code>{}</code>\n\n<b>🚫 Error:</b>\n{}",
  36. "db_permission": (
  37. "⚠️ <b>Do not use </b><code>db.set</code><b>, </b><code>db.get</code><b> "
  38. "and other db operations. You have core modules to control anything you "
  39. "want</b>\n\n<i>Theses commands may <b><u>crash</u></b> your userbot or "
  40. "even make it <b><u>unusable</u></b>! Do it on your own risk</i>\n\n<i>"
  41. "If you issue any errors after allowing this option, <b><u>you will not "
  42. "get any help in support chat</u></b>!</i>"
  43. ),
  44. }
  45. strings_ru = {
  46. "eval": "<b>🎬 Код:</b>\n<code>{}</code>\n<b>🪄 Результат:</b>\n<code>{}</code>",
  47. "err": "<b>🎬 Код:</b>\n<code>{}</code>\n\n<b>🚫 Ошибка:</b>\n{}",
  48. "db_permission": (
  49. "⚠️ <b>Не используй </b><code>db.set</code><b>, </b><code>db.get</code><b>"
  50. " и другие операции с базой данных. У тебя есть встроенные модуля для"
  51. " управления ей</b>\n\n<i>Эти команды могут <b><u>нарушить работу</u></b>"
  52. " юзербота, или вообще <b><u>сломать</u></b> его! Используй эти команды на"
  53. " свой страх и риск</i>\n\n<i>Если появятся какие-либо проблемы, вызванные"
  54. " после этой команды, <b><u>ты не получишь помощи в чате</u></b>!</i>"
  55. ),
  56. "_cmd_doc_eval": "Алиас для команды .e",
  57. "_cmd_doc_e": "Выполняет Python кодировка",
  58. "_cls_doc": "Выполняет Python код",
  59. }
  60. async def client_ready(self, client, _):
  61. self._phone = (await client.get_me()).phone
  62. @loader.owner
  63. async def evalcmd(self, message: Message):
  64. """Alias for .e command"""
  65. await self.ecmd(message)
  66. async def inline__allow(self, call: InlineCall):
  67. await call.answer("Now you can access db through .e command", show_alert=True)
  68. self._db.set(main.__name__, "enable_db_eval", True)
  69. await call.delete()
  70. @loader.owner
  71. async def ecmd(self, message: Message):
  72. """Evaluates python code"""
  73. ret = self.strings("eval")
  74. try:
  75. it = await meval(
  76. utils.get_args_raw(message),
  77. globals(),
  78. **await self.getattrs(message),
  79. )
  80. except FakeDbException:
  81. await self.inline.form(
  82. self.strings("db_permission"),
  83. message=message,
  84. reply_markup=[
  85. [
  86. {
  87. "text": "✅ Allow",
  88. "callback": self.inline__allow,
  89. },
  90. {"text": "🚫 Cancel", "action": "close"},
  91. ]
  92. ],
  93. )
  94. return
  95. except Exception:
  96. item = HikkaException.from_exc_info(*sys.exc_info())
  97. exc = (
  98. "\n<b>🪐 Full stack:</b>\n\n"
  99. + "\n".join(item.full_stack.splitlines()[:-1])
  100. + "\n\n"
  101. + "😵 "
  102. + item.full_stack.splitlines()[-1]
  103. )
  104. exc = exc.replace(str(self._phone), "📵")
  105. if os.environ.get("DATABASE_URL"):
  106. exc = exc.replace(
  107. os.environ.get("DATABASE_URL"),
  108. "postgre://**************************",
  109. )
  110. if os.environ.get("hikka_session"):
  111. exc = exc.replace(
  112. os.environ.get("hikka_session"),
  113. "StringSession(**************************)",
  114. )
  115. await utils.answer(
  116. message,
  117. self.strings("err").format(
  118. utils.escape_html(utils.get_args_raw(message)),
  119. exc,
  120. ),
  121. )
  122. return
  123. ret = ret.format(
  124. utils.escape_html(utils.get_args_raw(message)),
  125. utils.escape_html(
  126. str(it.stringify())
  127. if hasattr(it, "stringify") and callable(it.stringify)
  128. else str(it)
  129. ),
  130. )
  131. ret = ret.replace(str(self._phone), "📵")
  132. if postgre := os.environ.get("DATABASE_URL") or main.get_config_key(
  133. "postgre_uri"
  134. ):
  135. ret = ret.replace(postgre, "postgre://**************************")
  136. if redis := os.environ.get("REDIS_URL") or main.get_config_key("redis_uri"):
  137. ret = ret.replace(redis, "redis://**************************")
  138. if os.environ.get("hikka_session"):
  139. ret = ret.replace(
  140. os.environ.get("hikka_session"),
  141. "StringSession(**************************)",
  142. )
  143. with contextlib.suppress(MessageIdInvalidError):
  144. await utils.answer(message, ret)
  145. async def getattrs(self, message: Message) -> dict:
  146. reply = await message.get_reply_message()
  147. return {
  148. **{
  149. "message": message,
  150. "client": self._client,
  151. "reply": reply,
  152. "r": reply,
  153. **self.get_sub(telethon.tl.types),
  154. **self.get_sub(telethon.tl.functions),
  155. "event": message,
  156. "chat": message.to_id,
  157. "telethon": telethon,
  158. "utils": utils,
  159. "main": main,
  160. "loader": loader,
  161. "f": telethon.tl.functions,
  162. "c": self._client,
  163. "m": message,
  164. "lookup": self.lookup,
  165. "self": self,
  166. },
  167. **(
  168. {
  169. "db": self._db,
  170. }
  171. if self._db.get(main.__name__, "enable_db_eval", False)
  172. else {
  173. "db": FakeDb(),
  174. }
  175. ),
  176. }
  177. def get_sub(self, obj: Any, _depth: int = 1) -> dict:
  178. """Get all callable capitalised objects in an object recursively, ignoring _*"""
  179. return {
  180. **dict(
  181. filter(
  182. lambda x: x[0][0] != "_"
  183. and x[0][0].upper() == x[0][0]
  184. and callable(x[1]),
  185. obj.__dict__.items(),
  186. )
  187. ),
  188. **dict(
  189. itertools.chain.from_iterable(
  190. [
  191. self.get_sub(y[1], _depth + 1).items()
  192. for y in filter(
  193. lambda x: x[0][0] != "_"
  194. and isinstance(x[1], ModuleType)
  195. and x[1] != obj
  196. and x[1].__package__.rsplit(".", _depth)[0]
  197. == "telethon.tl",
  198. obj.__dict__.items(),
  199. )
  200. ]
  201. )
  202. ),
  203. }