commands.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. from io import BytesIO
  2. from sys import executable
  3. from struct import pack
  4. from asyncio import create_subprocess_shell
  5. from asyncio.subprocess import PIPE
  6. from datetime import datetime
  7. from ujson import dumps
  8. from tortoise.exceptions import IntegrityError
  9. from telethon.utils import get_display_name, get_peer_id
  10. from telethon.tl.types import MessageEntityCode
  11. from aiofiles.os import remove, path
  12. from aiohttp import ClientSession
  13. from emoji import is_emoji
  14. from cairosvg import svg2png
  15. from actions import (
  16. find_action,
  17. create_action,
  18. delete_action,
  19. add_gif,
  20. add_sticker,
  21. add_admin,
  22. delete_admin,
  23. add_or_update_birthday,
  24. get_birthdays,
  25. add_server,
  26. delete_server,
  27. add_allowed,
  28. delete_allowed,
  29. list_servers,
  30. get_server_ip,
  31. is_markov_enabled,
  32. enable_markov,
  33. disable_markov,
  34. set_markov_options,
  35. get_markov_option,
  36. markov_say,
  37. run,
  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. )
  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:
  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:
  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:
  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:
  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:
  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:
  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:
  388. await event.reply("Произошла ошибка при попытке обращения к API… :(")
  389. return
  390. await event.reply(await run(command.args_string))
  391. async def sylvy_handler(bot, event, command):
  392. if command.argc < 1:
  393. await event.reply("Пожалуйста, не оставляйте ввод пустым!")
  394. return
  395. async with ClientSession() as session:
  396. try:
  397. async with session.post(
  398. "https://sylvy-engine.txlyre.website/api/compute",
  399. json={"program": command.args_string, "stringify": True},
  400. ) as resp:
  401. data = await resp.json()
  402. if data["status"] != "ok":
  403. await event.reply(f"Ошибка API Sylvy: {data['data']['message']}")
  404. return
  405. result = data["data"]["result"]
  406. except:
  407. await event.reply("Произошла ошибка при попытке обращения к API Sylvy… :(")
  408. return
  409. if result["status"] == "TIMEOUT":
  410. await event.reply(
  411. f"Максимальное время исполнения истекло (более тридцати секунд)!!!"
  412. )
  413. return
  414. elif result["status"] != "SUCCESS":
  415. await event.reply(f"Ошибка исполнения!!!\n\n```{result['stdout']}```")
  416. return
  417. if result["plots"]:
  418. plots = []
  419. for plot in result["plots"]:
  420. buffer = BytesIO()
  421. buffer.name = "plot.png"
  422. svg2png(bytestring=plot, write_to=buffer)
  423. buffer.seek(0)
  424. plots.append(buffer)
  425. await bot.send_file(
  426. event.peer_id, file=plots, reply_to=event, force_document=False
  427. )
  428. return
  429. text = ""
  430. if result["stdout"]:
  431. text += result["stdout"]
  432. text += "\n"
  433. text += result["output"]
  434. text = text.rstrip()
  435. await event.reply(
  436. text, formatting_entities=[MessageEntityCode(offset=0, length=len(text))]
  437. )
  438. async def say_handler(bot, event, command):
  439. if not bot.markov.is_ready:
  440. await event.reply(
  441. "Генератор текста ещё не готов к использованию. Пожалуйста, попробуйте чуть позже."
  442. )
  443. else:
  444. init_state = None
  445. if command.argc > 0:
  446. init_state = command.args_string
  447. try:
  448. try:
  449. await event.delete()
  450. except:
  451. pass
  452. await markov_say(bot, get_peer_id(event.peer_id), init_state=init_state)
  453. except:
  454. try:
  455. await markov_say(bot, get_peer_id(event.peer_id))
  456. except:
  457. await event.reply("Ошибка :(")
  458. COMMANDS = {
  459. "newadmin": Handler(newadmin_handler, is_restricted=True),
  460. "deladmin": Handler(deladmin_handler, is_restricted=True),
  461. "newaction": Handler(newaction_handler, is_restricted=True),
  462. "delaction": Handler(delaction_handler, is_restricted=True),
  463. "addgif": Handler(addgif_handler, is_restricted=True),
  464. "addserver": Handler(addserver_handler, is_restricted=True),
  465. "delserver": Handler(delserver_handler, is_restricted=True),
  466. "allow": Handler(allow_handler, is_restricted=True),
  467. "disallow": Handler(disallow_handler, is_restricted=True),
  468. "markov": Handler(markov_handler, is_restricted=True),
  469. "save": Handler(save_handler),
  470. "bday": Handler(bday_handler),
  471. "vpn": Handler(vpn_handler),
  472. "sylvy": Handler(sylvy_handler, is_public=True),
  473. "run": Handler(run_handler, is_public=True),
  474. "say": Handler(say_handler, is_public=True),
  475. }