commands.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197
  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 PIL import Image
  15. import magic
  16. from actions import (
  17. find_action,
  18. create_action,
  19. delete_action,
  20. add_gif,
  21. add_sticker,
  22. add_admin,
  23. delete_admin,
  24. add_or_update_birthday,
  25. get_birthdays,
  26. add_server,
  27. delete_server,
  28. add_allowed,
  29. delete_allowed,
  30. list_servers,
  31. get_server_ip,
  32. is_markov_enabled,
  33. enable_markov,
  34. disable_markov,
  35. set_markov_options,
  36. get_markov_option,
  37. markov_say,
  38. run,
  39. )
  40. from utils import (
  41. Command,
  42. make_temporary_filename,
  43. make_cache_filename,
  44. parse_kind,
  45. get_user_name,
  46. calculate_age,
  47. unparse,
  48. )
  49. from rand import roll_dices
  50. from chess0 import IllegalMove, GameOver
  51. class Handler:
  52. def __init__(self, handler, is_restricted=False, is_public=False):
  53. self.handler = handler
  54. self.is_restricted = is_restricted
  55. self.is_public = is_public
  56. async def newadmin_handler(bot, event, command):
  57. if command.argc < 1:
  58. await event.reply("Пожалуйста, укажите пользователя!")
  59. return
  60. try:
  61. target = await bot.get_entity(command.args[0])
  62. except ValueError:
  63. await event.reply("Недопустимое имя пользователя!")
  64. return
  65. try:
  66. await add_admin(target)
  67. except IntegrityError:
  68. await event.reply("Данный пользователь уже является администратором!")
  69. return
  70. await event.reply("Готово!~~")
  71. async def deladmin_handler(bot, event, command):
  72. if command.argc < 1:
  73. await event.reply("Пожалуйста, укажите пользователя!")
  74. return
  75. try:
  76. target = await bot.get_entity(command.args[0])
  77. except ValueError:
  78. await event.reply("Недопустимое имя пользователя!")
  79. return
  80. try:
  81. await delete_admin(target)
  82. except IndexError:
  83. await event.reply("Данный пользователь не является администратором!")
  84. return
  85. await event.reply("Готово!~~")
  86. async def newaction_handler(bot, event, command):
  87. if command.argc < 3:
  88. await event.reply("Пожалуйста, укажите тип, имя и шаблон действия!")
  89. return
  90. try:
  91. kind = parse_kind(command.args[0])
  92. except ValueError:
  93. await event.reply("Неверный тип действия!!!")
  94. return
  95. try:
  96. await create_action(command.args[1], " ".join(command.args[2:]), kind)
  97. except SyntaxError:
  98. await event.reply("Недопустимое имя действия!!!")
  99. return
  100. except IntegrityError:
  101. await event.reply("Действие с таким названием уже существует!")
  102. return
  103. await event.reply("Действие создано!")
  104. async def delaction_handler(bot, event, command):
  105. if command.argc < 1:
  106. await event.reply("Пожалуйста, укажите имя действия!")
  107. return
  108. try:
  109. await delete_action(command.args[0])
  110. except SyntaxError:
  111. await event.reply("Недопустимое имя действия!!!")
  112. return
  113. except NameError:
  114. await event.reply("Действия с таким названием не существует!")
  115. return
  116. await event.reply("Действие удалено!")
  117. async def addgif_handler(bot, event, command):
  118. if command.argc < 1:
  119. await event.reply("Пожалуйста, укажите имя действия!")
  120. return
  121. gif = await event.get_reply_message()
  122. if not gif or not gif.gif:
  123. await event.reply("Пожалуйста, добавьте GIF!")
  124. return
  125. try:
  126. action = await find_action(command.args[0])
  127. await add_gif(action, gif.file.id)
  128. except SyntaxError:
  129. await event.reply("Недопустимое имя действия!!!")
  130. return
  131. except NameError:
  132. await event.reply("Нет такого действия!")
  133. return
  134. await event.reply("Готово!~~")
  135. async def addserver_handler(bot, event, command):
  136. if command.argc < 2:
  137. await event.reply("Пожалуйста, укажите имя и адрес сервера!")
  138. return
  139. try:
  140. await add_server(command.args[0], command.args[1])
  141. except SyntaxError:
  142. await event.reply("Недопустимое имя сервера!!")
  143. return
  144. except ValueError:
  145. await event.reply("Пожалуйста, введите корректный IPv4-/IPv6-адрес!")
  146. return
  147. except IntegrityError:
  148. await event.reply("Данный сервер уже был добавлен ранее!")
  149. return
  150. await event.reply("Готово!~~")
  151. async def delserver_handler(bot, event, command):
  152. if command.argc < 1:
  153. await event.reply("Пожалуйста, укажите имя сервера!")
  154. return
  155. try:
  156. await delete_server(command.args[0])
  157. except SyntaxError:
  158. await event.reply("Недопустимое имя сервера!!")
  159. return
  160. except IndexError:
  161. await event.reply("Сервер с таким именем не найден!")
  162. return
  163. await event.reply("Готово!~~")
  164. async def allow_handler(bot, event, command):
  165. try:
  166. await add_allowed(get_peer_id(event.peer_id))
  167. except IntegrityError:
  168. await event.reply("Данный чат уже добавлен в список разрешённых!")
  169. return
  170. await event.reply("Готово!~~")
  171. async def disallow_handler(bot, event, command):
  172. try:
  173. await delete_allowed(get_peer_id(event.peer_id))
  174. except IndexError:
  175. await event.reply("Данный чат не найден в списке разрешённых!!")
  176. return
  177. await event.reply("Готово!~~")
  178. async def markov_handler(bot, event, command):
  179. if command.argc < 1:
  180. await event.reply("Некорректный синтаксис команды!!")
  181. return
  182. peer_id = get_peer_id(event.peer_id)
  183. if command.args[0] == "enable":
  184. try:
  185. await enable_markov(peer_id)
  186. except Exception:
  187. await event.reply("Ошибка!!!!!")
  188. return
  189. await event.reply("Готово!~~")
  190. elif command.args[0] == "disable":
  191. try:
  192. await disable_markov(peer_id)
  193. except Exception:
  194. await event.reply("Ошибка!!!!!")
  195. return
  196. await event.reply("Готово!~~")
  197. elif command.args[0] == "is_enabled":
  198. await event.reply(str(await is_markov_enabled(peer_id)))
  199. elif command.args[0] == "set" and command.argc == 3:
  200. try:
  201. await set_markov_options(
  202. peer_id, **{command.args[1]: float(command.args[2])}
  203. )
  204. except Exception:
  205. await event.reply("Ошибка!!!!!")
  206. return
  207. await event.reply("Готово!~~")
  208. elif command.args[0] == "get" and command.argc == 2:
  209. try:
  210. await event.reply(str(await get_markov_option(peer_id, command.args[1])))
  211. except Exception:
  212. await event.reply("Ошибка!!!!!")
  213. elif command.args[0] == "say":
  214. if not bot.markov.is_ready:
  215. await event.reply("Not ready:(")
  216. else:
  217. await markov_say(bot, peer_id)
  218. elif command.args[0] == "reply":
  219. if not bot.markov.is_ready:
  220. await event.reply("Not ready:(")
  221. else:
  222. await markov_say(bot, peer_id, reply_to=event)
  223. elif command.args[0] == "is_ready":
  224. await event.reply(str(bot.markov.is_ready))
  225. elif command.args[0] == "corpus_size":
  226. await event.reply(str(len(bot.markov.corpus)))
  227. elif command.args[0] == "rebuild":
  228. bot.markov.rebuild()
  229. await event.reply("Готово!~~")
  230. elif command.args[0] == "counter":
  231. await event.reply(str(bot.markov.counter))
  232. elif command.args[0] == "counter_reset":
  233. bot.markov.counter = 0
  234. await event.reply("Готово!~~")
  235. elif command.args[0] == "reset_corpus":
  236. bot.markov.corpus = []
  237. await event.reply("Готово!~~")
  238. elif command.args[0] == "unready":
  239. bot.markov.chain = None
  240. await event.reply("Готово!~~")
  241. elif command.args[0] == "save":
  242. bot.markov.save()
  243. await event.reply("Готово!~~")
  244. else:
  245. await event.reply("Некорректный синтаксис команды!!!")
  246. # Very, very, VERY evil code...
  247. async def make_message_shot(bot, message):
  248. if message.sender is None:
  249. sender_id = message.peer_id.channel_id
  250. full_name = await bot.get_entity(sender_id)
  251. full_name = full_name.title
  252. else:
  253. sender_id = message.sender.id
  254. full_name = get_display_name(message.sender)
  255. output_path = make_temporary_filename("png")
  256. avatar_path = await make_cache_filename(sender_id, "png")
  257. if not await path.isfile(avatar_path):
  258. await bot.download_profile_photo(sender_id, file=avatar_path, download_big=True)
  259. # TO-DO: make it better.
  260. mproc = await create_subprocess_shell(f"mogrify -format png {avatar_path}")
  261. await mproc.communicate()
  262. if not await path.isfile(avatar_path):
  263. avatar_path = "./resources/placeholder.png"
  264. data = bytes()
  265. data += pack("I", len(output_path))
  266. data += bytes(output_path, encoding="ASCII")
  267. data += pack("I", len(avatar_path))
  268. data += bytes(avatar_path, encoding="ASCII")
  269. username = bytes(full_name, encoding="UTF-8")
  270. data += pack("I", len(username))
  271. data += username
  272. data += pack("I", sender_id % 7)
  273. text = bytes(
  274. unparse(
  275. message.raw_text, [entity for entity, _ in message.get_entities_text()]
  276. ),
  277. encoding="UTF-8",
  278. )
  279. data += pack("I", len(text))
  280. data += text
  281. proc = await create_subprocess_shell("./makeshot/makeshot", stdin=PIPE)
  282. await proc.communicate(input=data)
  283. pproc = await create_subprocess_shell(f"pngcrush -reduce -ow {output_path}")
  284. await pproc.communicate()
  285. return output_path
  286. async def save_handler(bot, event, command):
  287. message = await event.get_reply_message()
  288. if not message:
  289. await event.reply("Пожалуйста, укажите сообщение для сохранения!")
  290. return
  291. emoji = "⚡"
  292. if command.argc >= 1:
  293. emoji = command.args[0]
  294. if len(emoji) not in range(1, 6) or not all(map(is_emoji, emoji)):
  295. await event.reply("Указан некорректный эмодзи!!!")
  296. return
  297. path = await make_message_shot(bot, message)
  298. try:
  299. file = await add_sticker(bot, path, emoji)
  300. await bot.send_file(message.peer_id, file=file, reply_to=message)
  301. finally:
  302. await remove(path)
  303. async def bday_handler(bot, event, command):
  304. if command.argc >= 1:
  305. try:
  306. date = datetime.strptime(" ".join(command.args), "%d.%m.%Y")
  307. except ValueError:
  308. await event.reply(
  309. "Дата не может быть распознана. Пожалуйста, введите свой день рождения в следующем формате: 01.01.1970 (день, месяц, год)."
  310. )
  311. return
  312. if date >= datetime.now():
  313. await event.reply("День рождения не может быть в будущем...")
  314. return
  315. if await add_or_update_birthday(get_peer_id(event.peer_id), event.sender, date):
  316. await event.reply("День рождения успешно добавлен!!!")
  317. else:
  318. await event.reply("День рождения успешно обновлён!!!")
  319. return
  320. birthdays = await get_birthdays(get_peer_id(event.peer_id))
  321. if not birthdays:
  322. await event.reply("Пока пусто...")
  323. return
  324. birthdays = map(
  325. lambda birthday: (birthday.user_id, calculate_age(birthday.date)), birthdays
  326. )
  327. birthdays = sorted(birthdays, key=lambda birthday: birthday[1].days_until)
  328. birthdays_list = ""
  329. for user_id, age in birthdays:
  330. try:
  331. entity = await bot.get_entity(user_id)
  332. except ValueError:
  333. await bot.get_participants(await event.get_chat())
  334. try:
  335. entity = await bot.get_entity(user_id)
  336. except ValueError:
  337. continue
  338. birthdays_list += f"{get_user_name(entity)} ❯ {age.age_now} ➔ {age.age} ❯ {age.date_string} ❯ {age.days_until}\n"
  339. await event.reply(f"Дни рождения:\n\n{birthdays_list}")
  340. async def vpn_handler(bot, event, command):
  341. if command.argc < 1:
  342. await event.reply(
  343. f"Пожалуйста, укажите имя сервера! Доступные сервера: {await list_servers()}."
  344. )
  345. return
  346. try:
  347. ip = await get_server_ip(command.args[0])
  348. except SyntaxError:
  349. await event.reply("Недопустимое имя сервера!!")
  350. return
  351. except IndexError:
  352. await event.reply("Сервер с таким именем не найден!")
  353. return
  354. if event.sender is None:
  355. sender_id = event.peer_id.channel_id
  356. else:
  357. sender_id = event.sender.id
  358. async with ClientSession() as session:
  359. try:
  360. async with session.post(
  361. f"http://{ip}:9217/api/obtain", data={"user_id": sender_id}
  362. ) as resp:
  363. data = await resp.json()
  364. profile = data["profile"]
  365. except Exception:
  366. await event.reply(
  367. "Произошла ошибка при попытке обращения к API сервера… :("
  368. )
  369. return
  370. try:
  371. await bot.send_message(
  372. await bot.get_entity(sender_id),
  373. f"Ваш файл конфигурации WireGuard (сервер {command.args[0]}):\n\n```{profile}```",
  374. )
  375. except Exception:
  376. await event.reply(
  377. "Произошла ошибка при отправке файла конфигурации в Ваши личные сообщения… :с"
  378. )
  379. return
  380. await event.reply(
  381. "Готово!!~~ Файл конфигурации WireGuard отправлен в Ваши личные сообщения!"
  382. )
  383. async def run_handler(bot, event, command):
  384. if command.argc < 1:
  385. async with ClientSession() as session:
  386. try:
  387. async with session.get("https://farlands.txlyre.website/langs") as resp:
  388. text = await resp.read()
  389. text = text.decode("UTF-8")
  390. await event.reply(f"Доступные языки:\n`{text}`")
  391. except Exception:
  392. await event.reply("Произошла ошибка при попытке обращения к API… :(")
  393. return
  394. try:
  395. _, _, result = await run(command.args_string)
  396. await event.reply(result)
  397. except (ValueError, MessageEmptyError):
  398. await event.reply("<Telegram не смог декодировать текст сообщения>")
  399. async def bash_handler(bot, event, command):
  400. try:
  401. _, _, result = await run(f"bash {command.args_string}")
  402. await event.reply(result)
  403. except (ValueError, MessageEmptyError):
  404. await event.reply("<Telegram не смог декодировать текст сообщения>")
  405. async def sylvy_handler(bot, event, command):
  406. if command.argc < 1:
  407. await event.reply("Пожалуйста, не оставляйте ввод пустым!")
  408. return
  409. async with ClientSession() as session:
  410. try:
  411. async with session.post(
  412. "https://sylvy-engine.txlyre.website/api/compute",
  413. json={"program": command.args_string, "stringify": True},
  414. ) as resp:
  415. data = await resp.json()
  416. if data["status"] != "ok":
  417. await event.reply(f"Ошибка API Sylvy: {data['data']['message']}")
  418. return
  419. result = data["data"]["result"]
  420. except Exception:
  421. await event.reply("Произошла ошибка при попытке обращения к API Sylvy… :(")
  422. return
  423. if result["status"] == "TIMEOUT":
  424. await event.reply(
  425. "Максимальное время исполнения истекло (более тридцати секунд)!!!"
  426. )
  427. return
  428. elif result["status"] != "SUCCESS":
  429. await event.reply(f"Ошибка исполнения!!!\n\n```{result['stdout']}```")
  430. return
  431. if result["plots"]:
  432. plots = []
  433. for plot in result["plots"]:
  434. buffer = BytesIO()
  435. buffer.name = "plot.png"
  436. svg2png(bytestring=plot, write_to=buffer)
  437. buffer.seek(0)
  438. plots.append(buffer)
  439. await bot.send_file(
  440. event.peer_id, file=plots, reply_to=event, force_document=False
  441. )
  442. return
  443. text = ""
  444. if result["stdout"]:
  445. text += result["stdout"]
  446. text += "\n"
  447. text += result["output"]
  448. text = text.rstrip()
  449. await event.reply(
  450. text, formatting_entities=[MessageEntityCode(offset=0, length=len(text))]
  451. )
  452. async def say_handler(bot, event, command):
  453. if not bot.markov.is_ready:
  454. await event.reply(
  455. "Генератор текста ещё не готов к использованию. Пожалуйста, попробуйте чуть позже."
  456. )
  457. else:
  458. init_state = None
  459. if command.argc > 0:
  460. init_state = command.args_string
  461. try:
  462. try:
  463. await event.delete()
  464. except Exception:
  465. pass
  466. await markov_say(bot, get_peer_id(event.peer_id), init_state=init_state)
  467. except Exception:
  468. try:
  469. await markov_say(bot, get_peer_id(event.peer_id))
  470. except Exception:
  471. await event.reply("Ошибка :(")
  472. async def roll_handler(bot, event, command):
  473. if command.argc < 1:
  474. await event.reply("Пожалуйста, не оставляйте ввод пустым!")
  475. return
  476. try:
  477. text = await roll_dices(command.args_string)
  478. except Exception:
  479. await event.reply("Ошибка :(")
  480. return
  481. await event.reply(
  482. text,
  483. formatting_entities=[MessageEntityPre(language="", offset=0, length=len(text))],
  484. )
  485. def chess_render(chess, id, as_list=True):
  486. board = svg2png(chess.svg(id, shallow=isinstance(id, str)))
  487. if as_list:
  488. return [board]
  489. return board
  490. def chess_game_stats(chess, id):
  491. if not chess.has_moves(id):
  492. return "Ходов ещё не сделано."
  493. text = f"Последние два хода: {chess.moves(id, 2)}\nВсе ходы [{chess.moves_count(id)}]: {chess.moves(id)}"
  494. if chess.is_checkmate(id):
  495. text = f"Мат!\n{text}"
  496. elif chess.is_check(id):
  497. text = f"Шах!\n{text}"
  498. draw = chess.draw(id)
  499. if draw:
  500. text = f"Ничья: {str(e)}\n{text}"
  501. return text
  502. def chess_game_over(chess, id, e):
  503. return [
  504. chess_render(chess, id, False),
  505. f"Конец игры: {str(e)}",
  506. chess_game_stats(chess, id),
  507. ]
  508. async def chess_start_handler(chess, id):
  509. await chess.begin(id)
  510. return chess_render(chess, id)
  511. async def chess_from_handler(chess, id, moves):
  512. try:
  513. await chess.begin(id, moves, strict=not isinstance(id, str))
  514. except GameOver as e:
  515. return chess_game_over(chess, id, e)
  516. except IllegalMove as e:
  517. chess.end(id)
  518. move = str(e)
  519. if move:
  520. return [f"Некорректный ход: {move}"]
  521. return ["Некорректная последовательность ходов."]
  522. reply = chess_render(chess, id)
  523. if chess.is_check(id):
  524. reply.append("Шах!")
  525. draw = chess.draw(id)
  526. if draw:
  527. reply.append(f"Ничья: {str(draw)}")
  528. if isinstance(id, str):
  529. reply.append(f"Ход {'белых' if chess.turn(id) else 'чёрных'}.")
  530. return reply
  531. async def chess_stop_handler(chess, id):
  532. try:
  533. stats = chess_game_stats(chess, id)
  534. except KeyError:
  535. return ["Нет активной игры."]
  536. chess.end(id)
  537. return ["Игра завершена.", stats]
  538. async def chess_move_handler(chess, id, move):
  539. is_group = isinstance(id, str)
  540. try:
  541. await chess.move(id, move)
  542. if not is_group:
  543. await chess.move(id)
  544. except KeyError:
  545. return ["Нет активной игры."]
  546. except GameOver as e:
  547. return chess_game_over(chess, id, e)
  548. except IllegalMove:
  549. return ["Некорректный ход."]
  550. reply = chess_render(chess, id)
  551. if chess.is_check(id):
  552. reply.append("Шах!")
  553. draw = chess.draw(id)
  554. if draw:
  555. reply.append(f"Ничья: {str(draw)}")
  556. return reply
  557. async def chess_undo_handler(chess, id):
  558. is_group = isinstance(id, str)
  559. try:
  560. chess.undo(id)
  561. if not is_group:
  562. chess.undo(id)
  563. except KeyError:
  564. return ["Нет активной игры."]
  565. except IndexError:
  566. return ["Нечего отменять."]
  567. except GameOver as e:
  568. return chess_game_over(chess, id, e)
  569. reply = ["Последний ход отменён.", svg2png(chess.svg(id))]
  570. if chess.is_check(id):
  571. reply.append("Шах!")
  572. draw = chess.draw(id)
  573. if draw:
  574. reply.append(f"Ничья: {str(draw)}")
  575. return reply
  576. async def chess_skip_handler(chess, id):
  577. is_group = isinstance(id, str)
  578. try:
  579. await chess.skip(id)
  580. if not is_group:
  581. await chess.move(id)
  582. except KeyError:
  583. return ["Нет активной игры."]
  584. except GameOver as e:
  585. return chess_game_over(chess, id, e)
  586. reply = chess_render(chess, id)
  587. if chess.is_check(id):
  588. reply.append("Шах!")
  589. draw = chess.draw(id)
  590. if draw:
  591. reply.append(f"Ничья: {str(e)}")
  592. return reply
  593. async def chess_pass_handler(chess, id):
  594. is_group = isinstance(id, str)
  595. try:
  596. await chess.move(id)
  597. if not is_group:
  598. await chess.move(id)
  599. except KeyError:
  600. return ["Нет активной игры."]
  601. except GameOver as e:
  602. return chess_game_over(chess, id, e)
  603. reply = chess_render(chess, id)
  604. if chess.is_check(id):
  605. reply.append("Шах!")
  606. draw = chess.draw(id)
  607. if draw:
  608. reply.append(f"Ничья: {str(e)}")
  609. return reply
  610. async def chess_board_handler(chess, id):
  611. try:
  612. e = chess.game_over(id)
  613. if e is not None:
  614. return chess_game_over(chess, id, e)
  615. reply = chess_render(chess, id)
  616. except KeyError:
  617. return ["Нет активной игры."]
  618. if chess.is_check(id):
  619. reply.append("Шах!")
  620. draw = chess.draw(id)
  621. if draw:
  622. reply.append(f"Ничья: {str(e)}")
  623. if isinstance(id, str):
  624. reply.append(f"Ход {'белых' if chess.turn(id) else 'чёрных'}.")
  625. return reply
  626. async def chess_moves_handler(chess, id):
  627. try:
  628. e = chess.game_over(id)
  629. if e is not None:
  630. return chess_game_over(chess, id, e)
  631. text = chess_game_stats(chess, id)
  632. except KeyError:
  633. return ["Нет активной игры."]
  634. if isinstance(id, str):
  635. text += f"\nХод {'белых' if chess.turn(id) else 'чёрных'}."
  636. return [text]
  637. async def chess_set_handler(chess, id, moves):
  638. try:
  639. await chess.set(id, moves, strict=not isinstance(id, str))
  640. except GameOver as e:
  641. return chess_game_over(chess, id, e)
  642. except IllegalMove as e:
  643. move = str(e)
  644. if move:
  645. return [f"Некорректный ход: {move}"]
  646. return ["Некорректная последовательность ходов."]
  647. reply = chess_render(chess, id)
  648. if chess.is_check(id):
  649. reply.append("Шах!")
  650. draw = chess.draw(id)
  651. if draw:
  652. reply.append(f"Ничья: {str(e)}")
  653. if isinstance(id, str):
  654. reply.append(f"Ход {'белых' if chess.turn(id) else 'чёрных'}.")
  655. return reply
  656. async def chess_anim_handler(chess, id, arg):
  657. count = None
  658. if arg.strip():
  659. try:
  660. count = int(arg)
  661. if count < 2:
  662. raise ValueError
  663. except Exception:
  664. return ["Некорректное значение аргумента."]
  665. try:
  666. frames = chess.animate(id, count=count, shallow=isinstance(id, str))
  667. except KeyError:
  668. return ["Нет активной игры."]
  669. if not frames:
  670. return ["Ходов ещё не сделано."]
  671. frames = list(map(lambda f: Image.open(BytesIO(svg2png(f))), frames))
  672. buffer = BytesIO()
  673. buffer.name = "board.gif"
  674. frames[0].save(
  675. fp=buffer,
  676. format="GIF",
  677. append_images=frames[1:],
  678. save_all=True,
  679. duration=500,
  680. loop=0,
  681. )
  682. buffer.seek(0)
  683. return [buffer]
  684. async def chess_fen_handler(chess, id):
  685. return [chess.fen(id)]
  686. CHESS_COMMANDS = {
  687. "start": (chess_start_handler, "Начать новую игру", 0),
  688. "from": (
  689. chess_from_handler,
  690. "Начать новую игру с доской в указанном состоянии",
  691. 1,
  692. True,
  693. ),
  694. "end": (chess_stop_handler, "Завершить игру", 0),
  695. "move": (chess_move_handler, "Сделать ход", 1),
  696. "undo": (chess_undo_handler, "Отменить ход", 0),
  697. "skip": (chess_skip_handler, "Пропустить ход", 0),
  698. "pass": (chess_pass_handler, "Сделать ход вместо вас", 0),
  699. "board": (chess_board_handler, "Показать доску", 0),
  700. "moves": (chess_moves_handler, "Показать историю ходов и состояние игры", 0),
  701. "set": (chess_set_handler, "Установить состояние доски", 1, True),
  702. "create": (chess_start_handler, "Создать общую доску", 0),
  703. "createfrom": (
  704. chess_from_handler,
  705. "Создать общую доску в указанном состоянии",
  706. 1,
  707. True,
  708. ),
  709. "fen": (chess_fen_handler, "Получить представление доски в FEN", 0),
  710. "anim": (chess_anim_handler, "Создать анимацию последних N ходов", 0, True),
  711. }
  712. CHESS_ALIASES = {
  713. "s": "start",
  714. "f": "from",
  715. "m": "move",
  716. "b": "board",
  717. "u": "undo",
  718. "s": "skip",
  719. "p": "pass",
  720. "ms": "moves",
  721. "i": "moves",
  722. "c": "create",
  723. "cf": "createfrom",
  724. "a": "anim",
  725. }
  726. async def chess_handler(bot, event, command):
  727. if not bot.chess:
  728. await event.reply("Извините, данная функция недоступна....")
  729. return
  730. if command.argc < 1:
  731. buffer = "Доступные субкоманды:\n"
  732. for command in CHESS_COMMANDS:
  733. buffer += f"- {command} - {CHESS_COMMANDS[command][1]}.\n"
  734. await event.reply(buffer.strip())
  735. return
  736. name = command.args[0].lower()
  737. if name in CHESS_ALIASES:
  738. name = CHESS_ALIASES[name]
  739. elif name not in CHESS_COMMANDS:
  740. await event.reply(f"Неизвестная субкоманда: '{name}'.")
  741. return
  742. subcommand = CHESS_COMMANDS[name]
  743. if len(subcommand) == 4 and subcommand[3]:
  744. if command.argc < subcommand[2]:
  745. await event.reply(
  746. f"Некорректное количество аргументов для субкоманды '{name}': требуется как минимум {subcommand[2]}, а получено {command.argc - 1}."
  747. )
  748. return
  749. args = [" ".join(command.args[1:])]
  750. else:
  751. if command.argc - 1 != subcommand[2]:
  752. await event.reply(
  753. f"Некорректное количество аргументов для субкоманды '{name}': требуется {subcommand[2]}, а получено {command.argc - 1}."
  754. )
  755. return
  756. args = command.args[1:]
  757. peer_id = str(get_peer_id(event.peer_id))
  758. result = await subcommand[0](
  759. bot.chess,
  760. (
  761. peer_id
  762. if name in ("create", "createfrom")
  763. or (peer_id in bot.chess.sessions and name not in ("start", "from"))
  764. else event.sender.id
  765. ),
  766. *args,
  767. )
  768. for reply in result:
  769. if isinstance(reply, BytesIO):
  770. await bot.send_file(
  771. event.peer_id, file=reply, reply_to=event, force_document=True
  772. )
  773. elif isinstance(reply, bytes):
  774. buffer = BytesIO()
  775. buffer.name = "board.png"
  776. buffer.write(reply)
  777. buffer.seek(0)
  778. await bot.send_file(
  779. event.peer_id, file=buffer, reply_to=event, force_document=False
  780. )
  781. else:
  782. await event.reply(reply)
  783. if (
  784. name in ("move", "skip", "undo", "pass")
  785. and peer_id in bot.chess.sessions
  786. and bot.chess.game_over(peer_id) is None
  787. ):
  788. await event.reply(f"Ход {'белых' if bot.chess.turn(peer_id) else 'чёрных'}.")
  789. _chess_handler = Handler(chess_handler, is_public=True)
  790. async def wandscript(image, program):
  791. try:
  792. async with ClientSession() as sess:
  793. async with sess.post(
  794. "https://wandscript.txlyre.website/api/wand",
  795. data={"image": BytesIO(image), "program": program},
  796. ) as resp:
  797. return resp.status != 200, await resp.read()
  798. except Exception:
  799. return None, None
  800. return None, None
  801. mime_table = {
  802. "image/jpeg": ".jpg",
  803. "image/png": ".png",
  804. "image/webp": ".webp",
  805. "image/gif": ".gif",
  806. "image/bmp": ".bmp",
  807. }
  808. async def wand_handler(bot, event, command):
  809. media = None
  810. is_document = False
  811. is_wsp = False
  812. if event.photo:
  813. media = event.photo
  814. elif event.document:
  815. media = event.document
  816. is_document = True
  817. elif event.message.is_reply:
  818. reply = await event.message.get_reply_message()
  819. if reply.photo:
  820. media = reply.photo
  821. elif reply.document:
  822. media = reply.document
  823. is_document = True
  824. if media is None:
  825. image = b"\x89WSP\r\n\x1a\n" + pack("!BHHBBBB", 0x63, 1, 1, 0, 0, 0, 0)
  826. is_document = True
  827. is_wsp = True
  828. else:
  829. image = await bot.download_media(media, file=bytes)
  830. program = command.args_string
  831. if is_wsp or program:
  832. err, image = await wandscript(image, program)
  833. if err is None:
  834. await event.reply(
  835. "Произошла неизвестная ошибка при попытке обращения к API… :("
  836. )
  837. return
  838. if err:
  839. await event.reply(f"Ошибка WandScript: {image.decode('UTF-8')}")
  840. return
  841. mime_type = magic.from_buffer(image, mime=True)
  842. ext = mime_table.get(mime_type)
  843. if ext:
  844. image = BytesIO(image)
  845. image.name = f"output{ext}"
  846. await bot.send_file(
  847. event.peer_id,
  848. file=image,
  849. reply_to=event,
  850. mime_type=mime_type,
  851. force_document=is_document,
  852. )
  853. _bash_handler = Handler(bash_handler, is_public=True)
  854. _wand_handler = Handler(wand_handler, is_public=True)
  855. COMMANDS = {
  856. "newadmin": Handler(newadmin_handler, is_restricted=True),
  857. "deladmin": Handler(deladmin_handler, is_restricted=True),
  858. "newaction": Handler(newaction_handler, is_restricted=True),
  859. "delaction": Handler(delaction_handler, is_restricted=True),
  860. "addgif": Handler(addgif_handler, is_restricted=True),
  861. "addserver": Handler(addserver_handler, is_restricted=True),
  862. "delserver": Handler(delserver_handler, is_restricted=True),
  863. "allow": Handler(allow_handler, is_restricted=True),
  864. "disallow": Handler(disallow_handler, is_restricted=True),
  865. "markov": Handler(markov_handler, is_restricted=True),
  866. "save": Handler(save_handler),
  867. "bday": Handler(bday_handler),
  868. # "vpn": Handler(vpn_handler),
  869. "sylvy": Handler(sylvy_handler, is_public=True),
  870. "run": Handler(run_handler, is_public=True),
  871. "bash": _bash_handler,
  872. "sh": _bash_handler,
  873. "say": Handler(say_handler, is_public=True),
  874. "roll": Handler(roll_handler, is_public=True),
  875. "chess": _chess_handler,
  876. "c": _chess_handler,
  877. "wand": _wand_handler,
  878. "w": _wand_handler,
  879. }