commands.py 20 KB

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