commands.py 30 KB

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