commands.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995
  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. 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. from rand import roll_dices
  48. from chess0 import IllegalMove, GameOver
  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 Exception:
  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 Exception:
  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 Exception:
  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 Exception:
  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 Exception:
  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 Exception:
  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 Exception:
  390. await event.reply("Произошла ошибка при попытке обращения к API… :(")
  391. return
  392. try:
  393. _, _, result = await run(command.args_string)
  394. await event.reply(result)
  395. except (ValueError, MessageEmptyError):
  396. await event.reply("<Telegram не смог декодировать текст сообщения>")
  397. async def sylvy_handler(bot, event, command):
  398. if command.argc < 1:
  399. await event.reply("Пожалуйста, не оставляйте ввод пустым!")
  400. return
  401. async with ClientSession() as session:
  402. try:
  403. async with session.post(
  404. "https://sylvy-engine.txlyre.website/api/compute",
  405. json={"program": command.args_string, "stringify": True},
  406. ) as resp:
  407. data = await resp.json()
  408. if data["status"] != "ok":
  409. await event.reply(f"Ошибка API Sylvy: {data['data']['message']}")
  410. return
  411. result = data["data"]["result"]
  412. except Exception:
  413. await event.reply("Произошла ошибка при попытке обращения к API Sylvy… :(")
  414. return
  415. if result["status"] == "TIMEOUT":
  416. await event.reply(
  417. "Максимальное время исполнения истекло (более тридцати секунд)!!!"
  418. )
  419. return
  420. elif result["status"] != "SUCCESS":
  421. await event.reply(f"Ошибка исполнения!!!\n\n```{result['stdout']}```")
  422. return
  423. if result["plots"]:
  424. plots = []
  425. for plot in result["plots"]:
  426. buffer = BytesIO()
  427. buffer.name = "plot.png"
  428. svg2png(bytestring=plot, write_to=buffer)
  429. buffer.seek(0)
  430. plots.append(buffer)
  431. await bot.send_file(
  432. event.peer_id, file=plots, reply_to=event, force_document=False
  433. )
  434. return
  435. text = ""
  436. if result["stdout"]:
  437. text += result["stdout"]
  438. text += "\n"
  439. text += result["output"]
  440. text = text.rstrip()
  441. await event.reply(
  442. text, formatting_entities=[MessageEntityCode(offset=0, length=len(text))]
  443. )
  444. async def say_handler(bot, event, command):
  445. if not bot.markov.is_ready:
  446. await event.reply(
  447. "Генератор текста ещё не готов к использованию. Пожалуйста, попробуйте чуть позже."
  448. )
  449. else:
  450. init_state = None
  451. if command.argc > 0:
  452. init_state = command.args_string
  453. try:
  454. try:
  455. await event.delete()
  456. except Exception:
  457. pass
  458. await markov_say(bot, get_peer_id(event.peer_id), init_state=init_state)
  459. except Exception:
  460. try:
  461. await markov_say(bot, get_peer_id(event.peer_id))
  462. except Exception:
  463. await event.reply("Ошибка :(")
  464. async def roll_handler(bot, event, command):
  465. if command.argc < 1:
  466. await event.reply("Пожалуйста, не оставляйте ввод пустым!")
  467. return
  468. try:
  469. text = await roll_dices(command.args_string)
  470. except Exception:
  471. await event.reply("Ошибка :(")
  472. return
  473. await event.reply(
  474. text,
  475. formatting_entities=[MessageEntityPre(language="", offset=0, length=len(text))],
  476. )
  477. def chess_render(chess, id, as_list=True):
  478. board = svg2png(chess.svg(id, shallow=isinstance(id, str)))
  479. if as_list:
  480. return [board]
  481. return board
  482. async def chess_start_handler(chess, id):
  483. await chess.begin(id)
  484. return chess_render(chess, id)
  485. def chess_game_stats(chess, id):
  486. if not chess.has_moves(id):
  487. return "Ходов ещё не сделано."
  488. text = f"Последние два хода: {chess.moves(id, 2)}\nВсе ходы [{chess.moves_count(id)}]: {chess.moves(id)}"
  489. if chess.is_check(id):
  490. text = f"Шах!\n{text}"
  491. return text
  492. def chess_game_over(chess, id, e):
  493. board = chess_render(chess, id, False)
  494. stats = chess_game_stats(chess, id)
  495. chess.end(id)
  496. return [
  497. board,
  498. f"Конец игры: {str(e)}",
  499. stats
  500. ]
  501. async def chess_from_handler(chess, id, moves):
  502. try:
  503. await chess.begin(id, moves, strict=not isinstance(id, str))
  504. except GameOver as e:
  505. return chess_game_over(chess, id, e)
  506. except IllegalMove as e:
  507. chess.end(id)
  508. move = str(e)
  509. if move:
  510. return [f"Некорректный ход: {move}"]
  511. return ["Некорректная последовательность ходов."]
  512. reply = chess_render(chess, id)
  513. if chess.is_check(id):
  514. reply.append("Шах!")
  515. if isinstance(id, str):
  516. reply.append(f"Ход {'белых' if chess.turn(id) else 'чёрных'}.")
  517. return reply
  518. async def chess_stop_handler(chess, id):
  519. try:
  520. stats = chess_game_stats(chess, id)
  521. except KeyError:
  522. return ["Нет активной игры."]
  523. chess.end(id)
  524. return ["Игра завершена.", stats]
  525. async def chess_move_handler(chess, id, move):
  526. is_group = isinstance(id, str)
  527. try:
  528. await chess.move(id, move)
  529. if not is_group:
  530. await chess.move(id)
  531. except KeyError:
  532. return ["Нет активной игры."]
  533. except GameOver as e:
  534. return chess_game_over(chess, id, e)
  535. except IllegalMove:
  536. return ["Некорректный ход."]
  537. reply = chess_render(chess, id)
  538. if chess.is_check(id):
  539. reply.append("Шах!")
  540. return reply
  541. async def chess_undo_handler(chess, id):
  542. is_group = isinstance(id, str)
  543. try:
  544. chess.undo(id)
  545. if not is_group:
  546. chess.undo(id)
  547. except KeyError:
  548. return ["Нет активной игры."]
  549. except IndexError:
  550. return ["Нечего отменять."]
  551. return ["Последний ход отменён.", svg2png(chess.svg(id))]
  552. async def chess_skip_handler(chess, id):
  553. is_group = isinstance(id, str)
  554. try:
  555. await chess.skip(id)
  556. if not is_group:
  557. await chess.move(id)
  558. except KeyError:
  559. return ["Нет активной игры."]
  560. except GameOver as e:
  561. return chess_game_over(chess, id, e)
  562. reply = chess_render(chess, id)
  563. if chess.is_check(id):
  564. reply.append("Шах!")
  565. return reply
  566. async def chess_pass_handler(chess, id):
  567. is_group = isinstance(id, str)
  568. try:
  569. await chess.move(id)
  570. if not is_group:
  571. await chess.move(id)
  572. except KeyError:
  573. return ["Нет активной игры."]
  574. except GameOver as e:
  575. return chess_game_over(chess, id, e)
  576. reply = chess_render(chess, id)
  577. if chess.is_check(id):
  578. reply.append("Шах!")
  579. return reply
  580. async def chess_board_handler(chess, id):
  581. try:
  582. reply = chess_render(chess, id)
  583. except KeyError:
  584. return ["Нет активной игры."]
  585. if chess.is_check(id):
  586. reply.append("Шах!")
  587. if isinstance(id, str):
  588. reply.append(f"Ход {'белых' if chess.turn(id) else 'чёрных'}.")
  589. return reply
  590. async def chess_moves_handler(chess, id):
  591. try:
  592. text = chess_game_stats(chess, id)
  593. except KeyError:
  594. return ["Нет активной игры."]
  595. if isinstance(id, str):
  596. text += f"\nХод {'белых' if chess.turn(id) else 'чёрных'}."
  597. return [text]
  598. async def chess_anim_handler(chess, id, *args):
  599. count = None
  600. if len(args) >= 1:
  601. count = args[0]
  602. try:
  603. count = int(count)
  604. if count < 2:
  605. raise ValueError
  606. except Exception:
  607. return ["Некорректное значение аргумента."]
  608. try:
  609. frames = chess.animate(id, count=count, shallow=isinstance(id, str))
  610. except KeyError:
  611. return ["Нет активной игры."]
  612. if not frames:
  613. return ["Ходов ещё не сделано."]
  614. frames = list(map(lambda f: Image.open(BytesIO(svg2png(f))), frames))
  615. buffer = BytesIO()
  616. buffer.name = "board.gif"
  617. frames[0].save(fp=buffer, format="GIF", append_images=frames[1:], save_all=True, duration=500, loop=0)
  618. buffer.seek(0)
  619. return [buffer]
  620. CHESS_COMMANDS = {
  621. "start": (chess_start_handler, "Начать новую игру", 0),
  622. "from": (chess_from_handler, "Начать новую игру с доской в указанном состоянии", 1, True),
  623. "end": (chess_stop_handler, "Завершить игру", 0),
  624. "move": (chess_move_handler, "Сделать ход", 1),
  625. "undo": (chess_undo_handler, "Отменить ход", 0),
  626. "skip": (chess_skip_handler, "Пропустить ход", 0),
  627. "pass": (chess_pass_handler, "Сделать ход вместо вас", 0),
  628. "board": (chess_board_handler, "Показать доску", 0),
  629. "moves": (chess_moves_handler, "Показать историю ходов и состояние игры", 0),
  630. "create": (chess_start_handler, "Создать общую доску", 0),
  631. "createfrom": (chess_from_handler, "Создать общую доску в указанном состоянии", 1, True),
  632. "anim": (chess_anim_handler, "Создать анимацию последних N ходов", 0, True),
  633. }
  634. CHESS_ALIASES = {
  635. "s": "start",
  636. "f": "from",
  637. "m": "move",
  638. "b": "board",
  639. "u": "undo",
  640. "s": "skip",
  641. "p": "pass",
  642. "ms": "moves",
  643. "i": "moves",
  644. "c": "create",
  645. "cf": "createfrom",
  646. "a": "anim",
  647. }
  648. async def chess_handler(bot, event, command):
  649. if not bot.chess:
  650. await event.reply("Извините, данная функция недоступна....")
  651. return
  652. if command.argc < 1:
  653. buffer = "Доступные субкоманды:\n"
  654. for command in CHESS_COMMANDS:
  655. buffer += f"- {command} - {CHESS_COMMANDS[command][1]}.\n"
  656. await event.reply(buffer.strip())
  657. return
  658. name = command.args[0].lower()
  659. if name in CHESS_ALIASES:
  660. name = CHESS_ALIASES[name]
  661. elif name not in CHESS_COMMANDS:
  662. await event.reply(f"Неизвестная субкоманда: '{name}'.")
  663. return
  664. subcommand = CHESS_COMMANDS[name]
  665. if len(subcommand) == 4 and subcommand[3]:
  666. if command.argc < subcommand[2]:
  667. await event.reply(
  668. f"Некорректное количество аргументов для субкоманды '{name}': требуется как минимум {subcommand[2]}, а получено {command.argc - 1}."
  669. )
  670. return
  671. args = [" ".join(command.args[1:])]
  672. else:
  673. if command.argc - 1 != subcommand[2]:
  674. await event.reply(
  675. f"Некорректное количество аргументов для субкоманды '{name}': требуется {subcommand[2]}, а получено {command.argc - 1}."
  676. )
  677. return
  678. args = command.args[1:]
  679. peer_id = str(get_peer_id(event.peer_id))
  680. result = await subcommand[0](bot.chess, peer_id if name in ("create", "createfrom") or (peer_id in bot.chess.sessions and name not in ("start", "from")) else event.sender.id, *args)
  681. for reply in result:
  682. if isinstance(reply, BytesIO):
  683. await bot.send_file(
  684. event.peer_id, file=reply, reply_to=event, force_document=True
  685. )
  686. elif isinstance(reply, bytes):
  687. buffer = BytesIO()
  688. buffer.name = "board.png"
  689. buffer.write(reply)
  690. buffer.seek(0)
  691. await bot.send_file(
  692. event.peer_id, file=buffer, reply_to=event, force_document=False
  693. )
  694. else:
  695. await event.reply(reply)
  696. if name in ("move", "skip", "undo", "pass") and peer_id in bot.chess.sessions:
  697. await event.reply(f"Ход {'белых' if bot.chess.turn(peer_id) else 'чёрных'}.")
  698. _chess_handler = Handler(chess_handler, is_public=True)
  699. COMMANDS = {
  700. "newadmin": Handler(newadmin_handler, is_restricted=True),
  701. "deladmin": Handler(deladmin_handler, is_restricted=True),
  702. "newaction": Handler(newaction_handler, is_restricted=True),
  703. "delaction": Handler(delaction_handler, is_restricted=True),
  704. "addgif": Handler(addgif_handler, is_restricted=True),
  705. "addserver": Handler(addserver_handler, is_restricted=True),
  706. "delserver": Handler(delserver_handler, is_restricted=True),
  707. "allow": Handler(allow_handler, is_restricted=True),
  708. "disallow": Handler(disallow_handler, is_restricted=True),
  709. "markov": Handler(markov_handler, is_restricted=True),
  710. "save": Handler(save_handler),
  711. "bday": Handler(bday_handler),
  712. # "vpn": Handler(vpn_handler),
  713. "sylvy": Handler(sylvy_handler, is_public=True),
  714. "run": Handler(run_handler, is_public=True),
  715. "say": Handler(say_handler, is_public=True),
  716. "roll": Handler(roll_handler, is_public=True),
  717. "chess": _chess_handler, "c": _chess_handler,
  718. }