commands.py 20 KB

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