commands.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. import re
  2. from io import BytesIO
  3. from sys import executable
  4. from struct import pack
  5. from asyncio import create_subprocess_shell
  6. from asyncio.subprocess import PIPE
  7. from datetime import datetime
  8. from ujson import dumps
  9. from tortoise.exceptions import IntegrityError
  10. from telethon.errors import MessageEmptyError
  11. from telethon.utils import get_display_name, get_peer_id
  12. from telethon.tl.types import MessageEntityCode
  13. from aiofiles.os import remove, path
  14. from aiohttp import ClientSession
  15. from emoji import is_emoji
  16. from cairosvg import svg2png
  17. from actions import (
  18. find_action,
  19. create_action,
  20. delete_action,
  21. add_gif,
  22. add_sticker,
  23. add_admin,
  24. delete_admin,
  25. add_or_update_birthday,
  26. get_birthdays,
  27. add_server,
  28. delete_server,
  29. add_allowed,
  30. delete_allowed,
  31. list_servers,
  32. get_server_ip,
  33. is_markov_enabled,
  34. enable_markov,
  35. disable_markov,
  36. set_markov_options,
  37. get_markov_option,
  38. )
  39. from utils import (
  40. make_temporary_filename,
  41. make_cache_filename,
  42. parse_kind,
  43. get_user_name,
  44. calculate_age,
  45. unparse,
  46. remove_ansi_escapes,
  47. )
  48. class Handler:
  49. def __init__(self, handler, is_restricted=False, is_public=False):
  50. self.handler = handler
  51. self.is_restricted = is_restricted
  52. self.is_public = is_public
  53. async def newadmin_handler(bot, event, command):
  54. if command.argc < 1:
  55. await event.reply("Пожалуйста, укажите пользователя!")
  56. return
  57. try:
  58. target = await bot.get_entity(command.args[0])
  59. except ValueError:
  60. await event.reply("Недопустимое имя пользователя!")
  61. return
  62. try:
  63. await add_admin(target)
  64. except IntegrityError:
  65. await event.reply("Данный пользователь уже является администратором!")
  66. return
  67. await event.reply("Готово!~~")
  68. async def deladmin_handler(bot, event, command):
  69. if command.argc < 1:
  70. await event.reply("Пожалуйста, укажите пользователя!")
  71. return
  72. try:
  73. target = await bot.get_entity(command.args[0])
  74. except ValueError:
  75. await event.reply("Недопустимое имя пользователя!")
  76. return
  77. try:
  78. await delete_admin(target)
  79. except IndexError:
  80. await event.reply("Данный пользователь не является администратором!")
  81. return
  82. await event.reply("Готово!~~")
  83. async def newaction_handler(bot, event, command):
  84. if command.argc < 3:
  85. await event.reply("Пожалуйста, укажите тип, имя и шаблон действия!")
  86. return
  87. try:
  88. kind = parse_kind(command.args[0])
  89. except ValueError:
  90. await event.reply("Неверный тип действия!!!")
  91. return
  92. try:
  93. await create_action(command.args[1], " ".join(command.args[2:]), kind)
  94. except SyntaxError:
  95. await event.reply("Недопустимое имя действия!!!")
  96. return
  97. except IntegrityError:
  98. await event.reply("Действие с таким названием уже существует!")
  99. return
  100. await event.reply("Действие создано!")
  101. async def delaction_handler(bot, event, command):
  102. if command.argc < 1:
  103. await event.reply("Пожалуйста, укажите имя действия!")
  104. return
  105. try:
  106. await delete_action(command.args[0])
  107. except SyntaxError:
  108. await event.reply("Недопустимое имя действия!!!")
  109. return
  110. except NameError:
  111. await event.reply("Действия с таким названием не существует!")
  112. return
  113. await event.reply("Действие удалено!")
  114. async def addgif_handler(bot, event, command):
  115. if command.argc < 1:
  116. await event.reply("Пожалуйста, укажите имя действия!")
  117. return
  118. gif = await event.get_reply_message()
  119. if not gif or not gif.gif:
  120. await event.reply("Пожалуйста, добавьте GIF!")
  121. return
  122. try:
  123. action = await find_action(command.args[0])
  124. await add_gif(action, gif.file.id)
  125. except SyntaxError:
  126. await event.reply("Недопустимое имя действия!!!")
  127. return
  128. except NameError:
  129. await event.reply("Нет такого действия!")
  130. return
  131. await event.reply("Готово!~~")
  132. async def addserver_handler(bot, event, command):
  133. if command.argc < 2:
  134. await event.reply("Пожалуйста, укажите имя и адрес сервера!")
  135. return
  136. try:
  137. await add_server(command.args[0], command.args[1])
  138. except SyntaxError:
  139. await event.reply("Недопустимое имя сервера!!")
  140. return
  141. except ValueError:
  142. await event.reply("Пожалуйста, введите корректный IPv4-/IPv6-адрес!")
  143. return
  144. except IntegrityError:
  145. await event.reply("Данный сервер уже был добавлен ранее!")
  146. return
  147. await event.reply("Готово!~~")
  148. async def delserver_handler(bot, event, command):
  149. if command.argc < 1:
  150. await event.reply("Пожалуйста, укажите имя сервера!")
  151. return
  152. try:
  153. await delete_server(command.args[0])
  154. except SyntaxError:
  155. await event.reply("Недопустимое имя сервера!!")
  156. return
  157. except IndexError:
  158. await event.reply("Сервер с таким именем не найден!")
  159. return
  160. await event.reply("Готово!~~")
  161. async def allow_handler(bot, event, command):
  162. try:
  163. await add_allowed(get_peer_id(event.peer_id))
  164. except IntegrityError:
  165. await event.reply("Данный чат уже добавлен в список разрешённых!")
  166. return
  167. await event.reply("Готово!~~")
  168. async def disallow_handler(bot, event, command):
  169. try:
  170. await delete_allowed(get_peer_id(event.peer_id))
  171. except IndexError:
  172. await event.reply("Данный чат не найден в списке разрешённых!!")
  173. return
  174. await event.reply("Готово!~~")
  175. async def markov_handler(bot, event, command):
  176. if command.argc < 1:
  177. await event.reply("Некорректный синтаксис команды!!")
  178. return
  179. peer_id = get_peer_id(event.peer_id)
  180. if command.args[0] == "enable":
  181. try:
  182. await enable_markov(peer_id)
  183. except:
  184. await event.reply("Ошибка!!!!!")
  185. return
  186. await event.reply("Готово!~~")
  187. elif command.args[0] == "disable":
  188. try:
  189. await disable_markov(peer_id)
  190. except:
  191. await event.reply("Ошибка!!!!!")
  192. return
  193. await event.reply("Готово!~~")
  194. elif command.args[0] == "is_enabled":
  195. await event.reply(str(await is_markov_enabled(peer_id)))
  196. elif command.args[0] == "set" and command.argc == 3:
  197. try:
  198. await set_markov_options(
  199. peer_id, **{command.args[1]: float(command.args[2])}
  200. )
  201. except:
  202. await event.reply("Ошибка!!!!!")
  203. return
  204. await event.reply("Готово!~~")
  205. elif command.args[0] == "get" and command.argc == 2:
  206. try:
  207. await event.reply(str(await get_markov_option(peer_id, command.args[1])))
  208. except:
  209. await event.reply("Ошибка!!!!!")
  210. elif command.args[0] == "say":
  211. await bot.__markov_say(bot, peer_id)
  212. elif command.args[0] == "reply":
  213. await bot.__markov_say(bot, peer_id, reply_to=peer_id)
  214. elif command.args[0] == "is_ready":
  215. await event.reply(str(bot.__markov.is_ready))
  216. elif command.args[0] == "corpus_size":
  217. await event.reply(str(len(bot.__markov.corpus)))
  218. else:
  219. await event.reply("Некорректный синтаксис команды!!!")
  220. # Very, very, VERY evil code...
  221. async def make_message_shot(bot, message):
  222. if message.sender is None:
  223. sender_id = message.peer_id.channel_id
  224. full_name = await bot.get_entity(sender_id)
  225. full_name = full_name.title
  226. else:
  227. sender_id = message.sender.id
  228. full_name = get_display_name(message.sender)
  229. output_path = make_temporary_filename("png")
  230. avatar_path = await make_cache_filename(sender_id, "png")
  231. if not await path.isfile(avatar_path):
  232. await bot.download_profile_photo(sender_id, file=avatar_path, download_big=True)
  233. # TO-DO: make it better.
  234. mproc = await create_subprocess_shell(f"mogrify -format png {avatar_path}")
  235. await mproc.communicate()
  236. if not await path.isfile(avatar_path):
  237. avatar_path = "./resources/placeholder.png"
  238. data = bytes()
  239. data += pack("I", len(output_path))
  240. data += bytes(output_path, encoding="ASCII")
  241. data += pack("I", len(avatar_path))
  242. data += bytes(avatar_path, encoding="ASCII")
  243. username = bytes(full_name, encoding="UTF-8")
  244. data += pack("I", len(username))
  245. data += username
  246. data += pack("I", sender_id % 7)
  247. text = bytes(
  248. unparse(
  249. message.raw_text, [entity for entity, _ in message.get_entities_text()]
  250. ),
  251. encoding="UTF-8",
  252. )
  253. data += pack("I", len(text))
  254. data += text
  255. proc = await create_subprocess_shell("./makeshot/makeshot", stdin=PIPE)
  256. await proc.communicate(input=data)
  257. pproc = await create_subprocess_shell(f"pngcrush -reduce -ow {output_path}")
  258. await pproc.communicate()
  259. return output_path
  260. async def save_handler(bot, event, command):
  261. message = await event.get_reply_message()
  262. if not message:
  263. await event.reply("Пожалуйста, укажите сообщение для сохранения!")
  264. return
  265. emoji = "⚡"
  266. if command.argc >= 1:
  267. emoji = command.args[0]
  268. if len(emoji) not in range(1, 6) or not all(map(is_emoji, emoji)):
  269. await event.reply("Указан некорректный эмодзи!!!")
  270. return
  271. path = await make_message_shot(bot, message)
  272. try:
  273. file = await add_sticker(bot, path, emoji)
  274. await bot.send_file(message.peer_id, file=file, reply_to=message)
  275. finally:
  276. await remove(path)
  277. async def bday_handler(bot, event, command):
  278. if command.argc >= 1:
  279. try:
  280. date = datetime.strptime(" ".join(command.args), "%d.%m.%Y")
  281. except ValueError:
  282. await event.reply(
  283. "Дата не может быть распознана. Пожалуйста, введите свой день рождения в следующем формате: 01.01.1970 (день, месяц, год)."
  284. )
  285. return
  286. if date >= datetime.now():
  287. await event.reply("День рождения не может быть в будущем...")
  288. return
  289. if await add_or_update_birthday(get_peer_id(event.peer_id), event.sender, date):
  290. await event.reply("День рождения успешно добавлен!!!")
  291. else:
  292. await event.reply("День рождения успешно обновлён!!!")
  293. return
  294. birthdays = await get_birthdays(get_peer_id(event.peer_id))
  295. if not birthdays:
  296. await event.reply("Пока пусто...")
  297. return
  298. birthdays = map(
  299. lambda birthday: (birthday.user_id, calculate_age(birthday.date)), birthdays
  300. )
  301. birthdays = sorted(birthdays, key=lambda birthday: birthday[1].days_until)
  302. birthdays_list = ""
  303. for user_id, age in birthdays:
  304. try:
  305. entity = await bot.get_entity(user_id)
  306. except ValueError:
  307. await bot.get_participants(await event.get_chat())
  308. try:
  309. entity = await bot.get_entity(user_id)
  310. except ValueError:
  311. continue
  312. birthdays_list += f"{get_user_name(entity)} ❯ {age.age_now} ➔ {age.age} ❯ {age.date_string} ❯ {age.days_until}\n"
  313. await event.reply(f"Дни рождения:\n\n{birthdays_list}")
  314. async def vpn_handler(bot, event, command):
  315. if command.argc < 1:
  316. await event.reply(
  317. f"Пожалуйста, укажите имя сервера! Доступные сервера: {await list_servers()}."
  318. )
  319. return
  320. try:
  321. ip = await get_server_ip(command.args[0])
  322. except SyntaxError:
  323. await event.reply("Недопустимое имя сервера!!")
  324. return
  325. except IndexError:
  326. await event.reply("Сервер с таким именем не найден!")
  327. return
  328. if event.sender is None:
  329. sender_id = event.peer_id.channel_id
  330. else:
  331. sender_id = event.sender.id
  332. async with ClientSession() as session:
  333. try:
  334. async with session.post(
  335. f"http://{ip}:9217/api/obtain", data={"user_id": sender_id}
  336. ) as resp:
  337. data = await resp.json()
  338. profile = data["profile"]
  339. except:
  340. await event.reply(
  341. "Произошла ошибка при попытке обращения к API сервера… :("
  342. )
  343. return
  344. try:
  345. await bot.send_message(
  346. await bot.get_entity(sender_id),
  347. f"Ваш файл конфигурации WireGuard (сервер {command.args[0]}):\n\n```{profile}```",
  348. )
  349. except:
  350. await event.reply(
  351. "Произошла ошибка при отправке файла конфигурации в Ваши личные сообщения… :с"
  352. )
  353. return
  354. await event.reply(
  355. "Готово!!~~ Файл конфигурации WireGuard отправлен в Ваши личные сообщения!"
  356. )
  357. async def run_handler(bot, event, command):
  358. if command.argc < 1:
  359. async with ClientSession() as session:
  360. try:
  361. async with session.get("https://farlands.txlyre.website/langs") as resp:
  362. text = await resp.read()
  363. text = text.decode("UTF-8")
  364. await event.reply(f"Доступные языки:\n`{text}`")
  365. except:
  366. await event.reply("Произошла ошибка при попытке обращения к API… :(")
  367. return
  368. match = re.match(r"^(\w+)(?:\s|\n)((?:\n|.)*)$", command.args_string)
  369. if not match:
  370. await event.reply("Пожалуйста, не оставляйте ввод пустым!")
  371. return
  372. language_name, text = match.groups()
  373. if text.startswith("```") and text.endswith("```"):
  374. text = text[3:-3]
  375. text = text.replace("\xa0", " ") # i hate telegram
  376. async with ClientSession() as session:
  377. try:
  378. async with session.post(
  379. f"https://farlands.txlyre.website/run/{language_name}", data=text
  380. ) as resp:
  381. if resp.status in (404, 500):
  382. info = await resp.json()
  383. await event.reply(
  384. f'Произошла ошибка при попытке обращения к API… :(\nОтвет API: {info["detail"]}'
  385. )
  386. return
  387. elif resp.status != 200:
  388. await event.reply(
  389. "Сервер API временно недоступен. Пожалуйста, попробуйте ещё раз чуть позже."
  390. )
  391. return
  392. text = await resp.read()
  393. text = text.decode("UTF-8")[:4096]
  394. except:
  395. await event.reply("Произошла ошибка при попытке обращения к API… :(")
  396. return
  397. text = remove_ansi_escapes(text).strip()
  398. text = filter(
  399. lambda c: (c in " \t\n" or ord(c) >= 32) and ord(c) not in range(128, 159), text
  400. )
  401. text = "".join(text)
  402. text = text.replace("`", "")
  403. if not text:
  404. await event.reply("<пусто>")
  405. return
  406. try:
  407. try:
  408. await event.reply(f"```\n{text}```")
  409. except ValueError:
  410. await event.reply(text, parse_mode=None)
  411. except MessageEmptyError:
  412. await event.reply("<Telegram не смог декодировать текст сообщения>")
  413. async def sylvy_handler(bot, event, command):
  414. if command.argc < 1:
  415. await event.reply("Пожалуйста, не оставляйте ввод пустым!")
  416. return
  417. async with ClientSession() as session:
  418. try:
  419. async with session.post(
  420. "https://sylvy-engine.txlyre.website/api/compute",
  421. json={"program": command.args_string, "stringify": True},
  422. ) as resp:
  423. data = await resp.json()
  424. if data["status"] != "ok":
  425. await event.reply(f'Ошибка API Sylvy: {data["data"]["message"]}')
  426. return
  427. result = data["data"]["result"]
  428. except:
  429. await event.reply("Произошла ошибка при попытке обращения к API Sylvy… :(")
  430. return
  431. if result["status"] == "TIMEOUT":
  432. await event.reply(
  433. f"Максимальное время исполнения истекло (более тридцати секунд)!!!"
  434. )
  435. return
  436. elif result["status"] != "SUCCESS":
  437. await event.reply(f'Ошибка исполнения!!!\n\n```{result["stdout"]}```')
  438. return
  439. if result["plots"]:
  440. plots = []
  441. for plot in result["plots"]:
  442. buffer = BytesIO()
  443. svg2png(bytestring=plot, write_to=buffer)
  444. plots.append(buffer.getvalue())
  445. await bot.send_file(event.peer_id, file=plots, reply_to=event)
  446. return
  447. text = ""
  448. if result["stdout"]:
  449. text += result["stdout"]
  450. text += "\n"
  451. text += result["output"]
  452. text = text.rstrip()
  453. await event.reply(
  454. text, formatting_entities=[MessageEntityCode(offset=0, length=len(text))]
  455. )
  456. COMMANDS = {
  457. "newadmin": Handler(newadmin_handler, is_restricted=True),
  458. "deladmin": Handler(deladmin_handler, is_restricted=True),
  459. "newaction": Handler(newaction_handler, is_restricted=True),
  460. "delaction": Handler(delaction_handler, is_restricted=True),
  461. "addgif": Handler(addgif_handler, is_restricted=True),
  462. "addserver": Handler(addserver_handler, is_restricted=True),
  463. "delserver": Handler(delserver_handler, is_restricted=True),
  464. "allow": Handler(allow_handler, is_restricted=True),
  465. "disallow": Handler(disallow_handler, is_restricted=True),
  466. "markov": Handler(markov_handler, is_restricted=True),
  467. "save": Handler(save_handler),
  468. "bday": Handler(bday_handler),
  469. "vpn": Handler(vpn_handler),
  470. "sylvy": Handler(sylvy_handler, is_public=True),
  471. "run": Handler(run_handler, is_public=True),
  472. }