commands.py 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  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. def chess_game_stats(chess, id):
  483. if not chess.has_moves(id):
  484. return "Ходов ещё не сделано."
  485. text = f"Последние два хода: {chess.moves(id, 2)}\nВсе ходы [{chess.moves_count(id)}]: {chess.moves(id)}"
  486. if chess.is_checkmate(id):
  487. text = f"Мат!\n{text}"
  488. elif chess.is_check(id):
  489. text = f"Шах!\n{text}"
  490. return text
  491. def chess_game_over(chess, id, e):
  492. return [
  493. chess_render(chess, id, False),
  494. f"Конец игры: {str(e)}",
  495. chess_game_stats(chess, id)
  496. ]
  497. async def chess_start_handler(chess, id):
  498. await chess.begin(id)
  499. return chess_render(chess, id)
  500. async def chess_from_handler(chess, id, moves):
  501. try:
  502. await chess.begin(id, moves, strict=not isinstance(id, str))
  503. except GameOver as e:
  504. return chess_game_over(chess, id, e)
  505. except IllegalMove as e:
  506. chess.end(id)
  507. move = str(e)
  508. if move:
  509. return [f"Некорректный ход: {move}"]
  510. return ["Некорректная последовательность ходов."]
  511. reply = chess_render(chess, id)
  512. if chess.is_check(id):
  513. reply.append("Шах!")
  514. if isinstance(id, str):
  515. reply.append(f"Ход {'белых' if chess.turn(id) else 'чёрных'}.")
  516. return reply
  517. async def chess_stop_handler(chess, id):
  518. try:
  519. stats = chess_game_stats(chess, id)
  520. except KeyError:
  521. return ["Нет активной игры."]
  522. chess.end(id)
  523. return ["Игра завершена.", stats]
  524. async def chess_move_handler(chess, id, move):
  525. is_group = isinstance(id, str)
  526. try:
  527. await chess.move(id, move)
  528. if not is_group:
  529. await chess.move(id)
  530. except KeyError:
  531. return ["Нет активной игры."]
  532. except GameOver as e:
  533. return chess_game_over(chess, id, e)
  534. except IllegalMove:
  535. return ["Некорректный ход."]
  536. reply = chess_render(chess, id)
  537. if chess.is_check(id):
  538. reply.append("Шах!")
  539. return reply
  540. async def chess_undo_handler(chess, id):
  541. is_group = isinstance(id, str)
  542. try:
  543. chess.undo(id)
  544. if not is_group:
  545. chess.undo(id)
  546. except KeyError:
  547. return ["Нет активной игры."]
  548. except IndexError:
  549. return ["Нечего отменять."]
  550. except GameOver as e:
  551. return chess_game_over(chess, id, e)
  552. return ["Последний ход отменён.", svg2png(chess.svg(id))]
  553. async def chess_skip_handler(chess, id):
  554. is_group = isinstance(id, str)
  555. try:
  556. await chess.skip(id)
  557. if not is_group:
  558. await chess.move(id)
  559. except KeyError:
  560. return ["Нет активной игры."]
  561. except GameOver as e:
  562. return chess_game_over(chess, id, e)
  563. reply = chess_render(chess, id)
  564. if chess.is_check(id):
  565. reply.append("Шах!")
  566. return reply
  567. async def chess_pass_handler(chess, id):
  568. is_group = isinstance(id, str)
  569. try:
  570. await chess.move(id)
  571. if not is_group:
  572. await chess.move(id)
  573. except KeyError:
  574. return ["Нет активной игры."]
  575. except GameOver as e:
  576. return chess_game_over(chess, id, e)
  577. reply = chess_render(chess, id)
  578. if chess.is_check(id):
  579. reply.append("Шах!")
  580. return reply
  581. async def chess_board_handler(chess, id):
  582. try:
  583. e = chess.game_over(id)
  584. if e is not None:
  585. return chess_game_over(chess, id, e)
  586. reply = chess_render(chess, id)
  587. except KeyError:
  588. return ["Нет активной игры."]
  589. if chess.is_check(id):
  590. reply.append("Шах!")
  591. if isinstance(id, str):
  592. reply.append(f"Ход {'белых' if chess.turn(id) else 'чёрных'}.")
  593. return reply
  594. async def chess_moves_handler(chess, id):
  595. try:
  596. e = chess.game_over(id)
  597. if e is not None:
  598. return chess_game_over(chess, id, e)
  599. text = chess_game_stats(chess, id)
  600. except KeyError:
  601. return ["Нет активной игры."]
  602. if isinstance(id, str):
  603. text += f"\nХод {'белых' if chess.turn(id) else 'чёрных'}."
  604. return [text]
  605. async def chess_anim_handler(chess, id, arg):
  606. count = None
  607. if arg.strip():
  608. try:
  609. count = int(arg)
  610. if count < 2:
  611. raise ValueError
  612. except Exception:
  613. return ["Некорректное значение аргумента."]
  614. try:
  615. frames = chess.animate(id, count=count, shallow=isinstance(id, str))
  616. except KeyError:
  617. return ["Нет активной игры."]
  618. if not frames:
  619. return ["Ходов ещё не сделано."]
  620. frames = list(map(lambda f: Image.open(BytesIO(svg2png(f))), frames))
  621. buffer = BytesIO()
  622. buffer.name = "board.gif"
  623. frames[0].save(fp=buffer, format="GIF", append_images=frames[1:], save_all=True, duration=500, loop=0)
  624. buffer.seek(0)
  625. return [buffer]
  626. CHESS_COMMANDS = {
  627. "start": (chess_start_handler, "Начать новую игру", 0),
  628. "from": (chess_from_handler, "Начать новую игру с доской в указанном состоянии", 1, True),
  629. "end": (chess_stop_handler, "Завершить игру", 0),
  630. "move": (chess_move_handler, "Сделать ход", 1),
  631. "undo": (chess_undo_handler, "Отменить ход", 0),
  632. "skip": (chess_skip_handler, "Пропустить ход", 0),
  633. "pass": (chess_pass_handler, "Сделать ход вместо вас", 0),
  634. "board": (chess_board_handler, "Показать доску", 0),
  635. "moves": (chess_moves_handler, "Показать историю ходов и состояние игры", 0),
  636. "create": (chess_start_handler, "Создать общую доску", 0),
  637. "createfrom": (chess_from_handler, "Создать общую доску в указанном состоянии", 1, True),
  638. "anim": (chess_anim_handler, "Создать анимацию последних N ходов", 0, True),
  639. }
  640. CHESS_ALIASES = {
  641. "s": "start",
  642. "f": "from",
  643. "m": "move",
  644. "b": "board",
  645. "u": "undo",
  646. "s": "skip",
  647. "p": "pass",
  648. "ms": "moves",
  649. "i": "moves",
  650. "c": "create",
  651. "cf": "createfrom",
  652. "a": "anim",
  653. }
  654. async def chess_handler(bot, event, command):
  655. if not bot.chess:
  656. await event.reply("Извините, данная функция недоступна....")
  657. return
  658. if command.argc < 1:
  659. buffer = "Доступные субкоманды:\n"
  660. for command in CHESS_COMMANDS:
  661. buffer += f"- {command} - {CHESS_COMMANDS[command][1]}.\n"
  662. await event.reply(buffer.strip())
  663. return
  664. name = command.args[0].lower()
  665. if name in CHESS_ALIASES:
  666. name = CHESS_ALIASES[name]
  667. elif name not in CHESS_COMMANDS:
  668. await event.reply(f"Неизвестная субкоманда: '{name}'.")
  669. return
  670. subcommand = CHESS_COMMANDS[name]
  671. if len(subcommand) == 4 and subcommand[3]:
  672. if command.argc < subcommand[2]:
  673. await event.reply(
  674. f"Некорректное количество аргументов для субкоманды '{name}': требуется как минимум {subcommand[2]}, а получено {command.argc - 1}."
  675. )
  676. return
  677. args = [" ".join(command.args[1:])]
  678. else:
  679. if command.argc - 1 != subcommand[2]:
  680. await event.reply(
  681. f"Некорректное количество аргументов для субкоманды '{name}': требуется {subcommand[2]}, а получено {command.argc - 1}."
  682. )
  683. return
  684. args = command.args[1:]
  685. peer_id = str(get_peer_id(event.peer_id))
  686. 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)
  687. for reply in result:
  688. if isinstance(reply, BytesIO):
  689. await bot.send_file(
  690. event.peer_id, file=reply, reply_to=event, force_document=True
  691. )
  692. elif isinstance(reply, bytes):
  693. buffer = BytesIO()
  694. buffer.name = "board.png"
  695. buffer.write(reply)
  696. buffer.seek(0)
  697. await bot.send_file(
  698. event.peer_id, file=buffer, reply_to=event, force_document=False
  699. )
  700. else:
  701. await event.reply(reply)
  702. if name in ("move", "skip", "undo", "pass") and peer_id in bot.chess.sessions:
  703. await event.reply(f"Ход {'белых' if bot.chess.turn(peer_id) else 'чёрных'}.")
  704. _chess_handler = Handler(chess_handler, is_public=True)
  705. COMMANDS = {
  706. "newadmin": Handler(newadmin_handler, is_restricted=True),
  707. "deladmin": Handler(deladmin_handler, is_restricted=True),
  708. "newaction": Handler(newaction_handler, is_restricted=True),
  709. "delaction": Handler(delaction_handler, is_restricted=True),
  710. "addgif": Handler(addgif_handler, is_restricted=True),
  711. "addserver": Handler(addserver_handler, is_restricted=True),
  712. "delserver": Handler(delserver_handler, is_restricted=True),
  713. "allow": Handler(allow_handler, is_restricted=True),
  714. "disallow": Handler(disallow_handler, is_restricted=True),
  715. "markov": Handler(markov_handler, is_restricted=True),
  716. "save": Handler(save_handler),
  717. "bday": Handler(bday_handler),
  718. # "vpn": Handler(vpn_handler),
  719. "sylvy": Handler(sylvy_handler, is_public=True),
  720. "run": Handler(run_handler, is_public=True),
  721. "say": Handler(say_handler, is_public=True),
  722. "roll": Handler(roll_handler, is_public=True),
  723. "chess": _chess_handler, "c": _chess_handler,
  724. }