Added and updated repositories 2025-11-22 08:13:29

This commit is contained in:
github-actions[bot]
2025-11-22 08:13:29 +00:00
parent f6c356cbe2
commit 36fdafa7d7
14 changed files with 3641 additions and 135 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

927
ZetGoHack/nullmod/Chess.py Normal file
View File

@@ -0,0 +1,927 @@
__version__ = (2, 0, 0)
#░░░███░███░███░███░███
#░░░░░█░█░░░░█░░█░░░█░█
#░░░░█░░███░░█░░█░█░█░█
#░░░█░░░█░░░░█░░█░█░█░█
#░░░███░███░░█░░███░███
#H:Mods Team [💎]
# meta developer: @nullmod
# requires: python-chess
# packurl: https://github.com/ZetGoHack/TestingModules/raw/main/chess.yml
from .. import loader, utils
from ..inline.types import BotInlineCall, InlineCall, InlineMessage
import asyncio
import chess
import chess.pgn
import copy
import random as r
import time
from datetime import datetime, timezone
from telethon.tl.types import PeerUser, User, Message
from typing import TypedDict
class Timer:
def __init__(self, scnds):
self.starttime = scnds
self.timers = {"white": scnds, "black": scnds}
self.running = {"white": False, "black": False}
self.last_time = time.monotonic()
self.t = None
def minutes(self) -> int:
return self.starttime // 60
async def _count(self):
while True:
await asyncio.sleep(0.1)
now = time.monotonic()
elapsed = now - self.last_time
self.last_time = now
for color in ("white", "black"):
if self.running[color]:
self.timers[color] = max(0, self.timers[color] - elapsed)
async def start(self, from_color: str = "white"):
self.last_time = time.monotonic()
if from_color == "restore":
if self.running["white"]:
from_color = "white"
else:
from_color = "black"
await self._turn(from_color)
self.t = asyncio.create_task(self._count())
async def switch(self):
self.running["white"] = not self.running["white"]
self.running["black"] = not self.running["black"]
async def _turn(self, color):
now = time.monotonic()
e = now - self.last_time
self.last_time = now
for clr in ("white", "black"):
if self.running[clr]:
self.timers[clr] = max(0, self.timers[clr] - e)
self.running = {"white": color == "white", "black": color == "black"}
async def white_time(self):
return round(self.timers["white"], 0)
async def black_time(self):
return round(self.timers["black"], 0)
def restore(self, white_time: float, black_time: float, running: dict):
self.timers["white"] = white_time
self.timers["black"] = black_time
self.running = running
def backup(self) -> dict:
return {
"white_time": self.timers["white"],
"black_time": self.timers["black"],
"running": self.running
}
async def stop(self):
if self.t:
self.t.cancel()
self.running = {"white": False, "black": False}
class Player(TypedDict):
id: int
name: str
color: bool | None
class TimerDict(TypedDict):
timer: Timer
timer_loop: bool
timer_is_set: bool
message: InlineCall
class GameParams(TypedDict):
chosen_figure_coord: str
reason_of_ending: str
promotion_move: str
winner_color: bool | None
resigner_color: bool | None
draw_offerer: bool | None
class Game(TypedDict):
board: chess.Board
message: InlineCall
root_node: chess.pgn.Game
curr_node: chess.pgn.Game
state: str
reason: str
add_params: GameParams
class GameObj(TypedDict):
game_id: str
game: Game
sender: Player
opponent: Player
Timer: TimerDict
time: int
host_plays: bool
style: dict[str, str]
GamesDict = dict[str, GameObj]
@loader.tds
class Chess(loader.Module):
"""A reworked version of the Chess module"""
strings = {
"": "",
"name": "Chess",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"play_self",
False,
"Allows you to make moves without turn checks (also, you can play with yourself)",
validator=loader.validators.Boolean(),
)
)
async def client_ready(self):
self.styles = {
"figures-with-circles": {
"symbol": "[♔⚪] ",
"r": "♖⚫", "n": "♘⚫", "b": "♗⚫", "q": "♕⚫", "k": "♔⚫", "p": "♙⚫",
"R": "♖⚪", "N": "♘⚪", "B": "♗⚪", "Q": "♕⚪", "K": "♔⚪", "P": "♙⚪",
"move": "", "capture": "×", "promotion": "", "capture_promotion": "×↻",
},
"figures": {
"symbol": "[♔] ",
"r": "", "n": "", "b": "", "q": "𝗾", "k": "", "p": "",
"R": "", "N": "", "B": "", "Q": "𝗤", "K": "", "P": "",
"move": "", "capture": "×", "promotion": "", "capture_promotion": "×↻",
},
"letters": {
"symbol": "[𝗞] ",
"r": "𝗿", "n": "𝗻", "b": "𝗯", "q": "𝗾", "k": "𝗸", "p": "𝗽",
"R": "𝗥", "N": "𝗡", "B": "𝗕", "Q": "𝗤", "K": "𝗞", "P": "𝗣",
"move": "", "capture": "×", "promotion": "", "capture_promotion": "×↻",
},
"figures-with-cyr-letters": {
"symbol": "[♔Б] ",
"r": "♖Ч", "n": "♘Ч", "b": "♗Ч", "q": "♕Ч", "k": "♔Ч", "p": "♙Ч",
"R": "♖Б", "N": "♘Б", "B": "♗Б", "Q": "♕Б", "K": "♔Б", "P": "♙Б",
"move": "", "capture": "×", "promotion": "", "capture_promotion": "×↻",
},
"figures-with-latin-letters": {
"symbol": "[♔W] ",
"r": "♖B", "n": "♘B", "b": "♗B", "q": "♕B", "k": "♔B", "p": "♙B",
"R": "♖W", "N": "♘W", "B": "♗W", "Q": "♕W", "K": "♔W", "P": "♙W",
"move": "", "capture": "×", "promotion": "", "capture_promotion": "×↻",
},
"figures-with-comb-letters": {
"symbol": "[♔ⷱ] ",
"r": "♖ⷱ", "n": "♘ⷱ", "b": "♗ⷱ", "q": "♕ⷱ", "k": "♔ⷱ", "p": "♙ⷱ",
"R": "♖ⷠ", "N": "♘ⷠ", "B": "♗ⷠ", "Q": "♕ⷠ", "K": "♔ⷠ", "P": "♙ⷠ",
"move": "", "capture": "×", "promotion": "", "capture_promotion": "×↻",
},
}
self.coords = {
f"{col}{row}": "" for row in range(1, 9)
for col in "hgfedcba"
}
self.games: GamesDict = self.get("games_backup", {})
self.gsettings = {
"style": "figures-with-circles",
}
self.pgn = {
'Event': "Chess Play In Module",
'Site': "https://t.me/nullmod/",
'Date': "{date}",
'Round': "{game_id}",
'White': "{player}",
'Black': "{player}",
}
async def _check_player(self, call: InlineCall, game_id: str, only_opponent: bool = False, skip_turn_check: bool = False) -> bool:
if isinstance(call, (BotInlineCall, InlineCall, InlineMessage)):
game = self.games[game_id]
_from_id = call.from_user.id
if game.get("game", None) and game["game"]["state"] == "the_end":
await call.answer(self.strings["game_ended"], show_alert=True)
return
if _from_id != game["sender"]["id"]:
if _from_id != game["opponent"]["id"]:
await call.answer(self.strings["not_available"])
return False
if _from_id == game["sender"]["id"] and only_opponent and not self.config["play_self"]:
await call.answer(self.strings["not_you"])
return False
elif not self.config["play_self"] and game.get("game", None) and not skip_turn_check:
if game["sender"]["color"] == game["game"]["board"].turn and game["sender"]["id"] != _from_id:
await call.answer(self.strings["opp_move"])
return False
elif game["opponent"]["color"] == game["game"]["board"].turn and game["opponent"]["id"] != _from_id:
await call.answer(self.strings["opp_move"])
return False
return True
async def get_players(self, message: Message):
sender = {
"id": message.from_id.user_id if isinstance(message.peer_id, PeerUser) else message.sender.id,
"name": (await self.client.get_entity(message.from_id if isinstance(message.peer_id, PeerUser) else message.sender.id)).first_name
}
if message.is_reply:
r = await message.get_reply_message()
opponent = r.sender
if not isinstance(opponent, User):
await utils.answer(message, self.strings["not_a_user"])
return (None, None)
opp_id = opponent.id
opp_name = opponent.first_name
else:
args = utils.get_args(message)
if len(args)==0:
await utils.answer(message, self.strings["noargs"])
return (None, None)
opponent = args[0]
try:
if opponent.isdigit():
opp_id = int(opponent)
opponent = await self.client.get_entity(opp_id)
if not isinstance(opponent, User):
await utils.answer(message, self.strings["not_a_user"])
return (None, None)
opp_name = opponent.first_name
else:
opponent = await self.client.get_entity(opponent)
if not isinstance(opponent, User):
await utils.answer(message, self.strings["not_a_user"])
return (None, None)
opp_name = opponent.first_name
opp_id = opponent.id
except:
await utils.answer(message, self.strings["whosthat"])
return (None, None)
opponent = {
"id": opp_id,
"name": opp_name
}
return (sender, opponent)
async def _invite(self, call: InlineCall, game_id: str):
if not await self._check_player(call, game_id): return
game = self.games[game_id]
await utils.answer(
call,
self.strings["invite"].format(opponent=utils.escape_html(self.games[game_id]["opponent"]["name"])) + self.strings['settings_text'].format(
style=game['style'],
timer=self.strings['available'] if game['Timer']['available'] and not game['Timer']['timer']
else self.strings['timer'].format(game['Timer']['timer'].minutes()) if game['Timer']['timer']
else self.strings['not_available'],
color=self.strings['random'] if game['host_plays'] == 'r'
else self.strings['white'] if game['host_plays'] == True
else self.strings['black']
),
reply_markup = [
[
{
"text": self.strings["yes"],
"callback": self._init_game,
"args": (game_id,)
},
{
"text": self.strings["no"],
"callback": self._init_game,
"args": (game_id, "no")
}
],
[
{
"text": self.strings["settings"],
"callback": self.settings,
"args": (game_id,)
}
]
],
disable_security=True
)
async def settings(self, call: InlineCall, game_id: str):
if not await self._check_player(call, game_id): return
game = self.games[game_id]
reply_markup = []
if game["Timer"]["available"]:
reply_markup.append([
{"text": self.strings["time_btn"], "callback": self._settings, "args": (game_id, "t", )}
])
reply_markup.extend([
[
{"text": self.strings["color_btn"], "callback": self._settings, "args": (game_id, "c", )}
],
[
{"text": self.strings["style_btn"], "callback": self._settings, "args": (game_id, "s", )}
],
[
{"text": self.strings['back'], "callback": self._invite, "args": (game_id,)}
]
])
await utils.answer(
call,
self.strings['settings_text'].format(
style=game['style'],
timer=self.strings['available'] if game['Timer']['available'] and not game['Timer']['timer']
else self.strings['timer'].format(game['Timer']['timer'].minutes()) if game['Timer']['timer']
else self.strings['not_available'],
color=self.strings['random'] if game['host_plays'] == 'r'
else self.strings['white'] if game['host_plays'] == True
else self.strings['black']
),
reply_markup=reply_markup,
disable_security=True
)
async def _settings(self, call: InlineCall, game_id: str, ruleset: str | list):
reply_markup = []
text = "🍓"
if isinstance(ruleset, str):
if ruleset == "t":
text = ""
reply_markup.extend([
[
{"text": self.strings['blitz_text'], "action": "answer", "message": self.strings['blitz_message']}
],
[
{"text": self.strings['timer'].format(3), "callback":self._settings, "args": (game_id, ['Timer', 3])},
{"text": self.strings['timer'].format(5), "callback":self._settings, "args": (game_id, ['Timer', 5])},
],
[
{"text": self.strings['rapid_text'], "action": "answer", "message": self.strings['rapid_message']}
],
[
{"text": self.strings['timer'].format(10), "callback":self._settings, "args": (game_id, ['Timer', 10])},
{"text": self.strings['timer'].format(15), "callback":self._settings, "args": (game_id, ['Timer', 15])},
{"text": self.strings['timer'].format(30), "callback":self._settings, "args": (game_id, ['Timer', 30])},
{"text": self.strings['timer'].format(60), "callback":self._settings, "args": (game_id, ['Timer', 60])}
],
[
{"text": self.strings['no_clock_text'], "callback":self._settings, "args": (game_id, ['Timer', True])}
]
])
elif ruleset == "c":
text = "♟️"
reply_markup.extend([
[
{"text": self.strings['white'], "callback":self._settings, "args": (game_id, ['host_plays', True])},
{"text": self.strings['black'], "callback":self._settings, "args": (game_id, ['host_plays', True] )}
],
[
{"text": self.strings['random'], "callback":self._settings, "args": (game_id, ['host_plays', 'r'])}
]
])
elif ruleset == "s":
text = "✏️"
reply_markup.extend([
[{"text": st["symbol"] + self.strings[name], "callback":self._settings, "args": (game_id, ["style", name])}]
for name, st in self.styles.items()
])
reply_markup.append(
[
{"text": self.strings['back'], "callback": self.settings, "args": (game_id,)}
]
)
await utils.answer(call, text, reply_markup=reply_markup, disable_security=True)
else:
await call.answer("")
if ruleset[0] == "style":
self.set('style', ruleset[1])
if ruleset[0] == "Timer" and isinstance(ruleset[1], int):
self.games[game_id]['Timer']['timer'] = Timer(ruleset[1]*60)
else:
self.games[game_id][ruleset[0]] = ruleset[1]
await self.settings(call, game_id)
@loader.command(ru_doc="[reply/username/id] - предложить человеку сыграть партию")
async def chess(self, message: Message):
"""[reply/username/id] - propose a person to play a game"""
sender, opponent = await self.get_players(message)
if not sender or not opponent: return
if sender['id'] == opponent['id'] and not self.config["play_self"]:
await utils.answer(message, self.strings["playing_with_yourself?"])
return
if self.games:
past_game = next(reversed(self.games.values()))
if not past_game.get("game", None):
self.games.pop(past_game['game_id'], None)
if not self.games:
game_id = str(1)
else:
game_id = str(max(map(int, self.games.keys())) + 1)
self.games[game_id] = GameObj(
game_id = game_id,
sender = sender,
opponent = opponent,
Timer = {"available": True if isinstance(message.peer_id, PeerUser) else False, "timer": None, "timer_loop": False},
time = int(time.time()),
host_plays = "r",
style = self.gsettings['style']
)
await self._invite(message, game_id)
# @loader.command(ru_doc="посмотреть текущее состояние модуля и статистику своих партий")
# async def chesstats(self, message: Message):
# """view the current state of the module and statistics of your games"""
# total_games = len(self.get("games_backup", {}))
# await utils.answer(message, f"♟️ <b>{self.strings['name']}</b> ♟️\n\nTotal games played: <b>{total_games}</b>")
# TODO: добавить кнопки для просмотра состояния каждой партии; считать победы/поражения/ничьи и прочую бесполезную статистику; проверка на наличие исполняемого файла шахматного движка для возможности игры против ИИ; возможность экспорта партии в PGN; возможность продолжить сохранённую партию
############## Preparing all for game start... ##############
async def _init_game(self, call: InlineCall, game_id: str, ans="yes"):
if not await self._check_player(call, game_id=game_id, only_opponent=True): return
if ans == "no":
self.games.pop(game_id, None)
await utils.answer(call, self.strings["declined"])
return
game = self.games[game_id]
await utils.answer(call, self.strings["step1"])
await asyncio.sleep(0.8)
await utils.answer(call, self.strings["step2"])
game["style"] = self.styles[game["style"]]
await asyncio.sleep(0.8)
await utils.answer(call, self.strings["step3"])
if (turn := game.pop("host_plays")) == "r":
turn = r.choice([True, False])
game["sender"]["color"] = True if turn else False
game["opponent"]["color"] = False if turn else True
await asyncio.sleep(0.8)
await utils.answer(call, self.strings["step4"])
game["Timer"].pop("available", None)
await asyncio.sleep(0.8)
if isinstance(self.games[game_id]["Timer"]["timer"], Timer):
await utils.answer(call, self.strings["step4.T"])
await self._set_timer(call, game_id, call._units[call.unit_id]['chat'])
await asyncio.sleep(0.8)
return await utils.answer(call, self.strings["waiting_for_start"])
await self._start_game(call, game_id)
async def _set_timer(self, board_call: InlineCall, game_id: str, chat_id):
timer = self.games[game_id]["Timer"]["timer"]
self.games[game_id]["Timer"]["message"] = (
await self.inline.form(self.strings["timer_text"].format(
int(await timer.white_time()),
int(await timer.black_time()),
""
),
chat_id,
reply_markup = {"text": self.strings["start_timer"], "callback": self._start_timer, "args": (board_call, game_id,)},
disable_security = True,
)
)
@loader.loop(interval=1, autostart=True)
async def main_loop(self):
for game_id in self.games:
if not self.games[game_id].get("backup", False) and self.games[game_id]["Timer"]["timer_loop"] and not self.games[game_id]["Timer"]["timer_is_set"]:
async def timer_loop(game_id):
timer = self.games[game_id]["Timer"]["timer"]
await timer.start()
self.games[game_id]["Timer"]["timer_is_set"] = True
while self.games[game_id]["Timer"]["timer_loop"]:
if not all([await timer.white_time(), await timer.black_time()]):
self.games[game_id]["Timer"]["timer_loop"] = False
self.the_end(game_id, "time_is_up")
elif self.games[game_id]["game"]["state"] == "the_end":
self.games[game_id]["Timer"]["timer_loop"] = False
loser, winner = self._get_loser_and_winner(game_id)
await self.games[game_id]["Timer"]["message"].edit(self.strings["timer_text"].format(
int(await timer.white_time()),
int(await timer.black_time()),
"" if self.games[game_id]["game"]["state"] != "the_end"
else "⏹️ " + self.strings[self.games[game_id]["game"]["add_params"]["reason_of_ending"]].format(
loser, winner
)
),
)
await asyncio.sleep(1)
await timer.stop()
asyncio.create_task(timer_loop(game_id))
if self.games[game_id].get("game", None):
if not self.games[game_id].get("backup", False):
self.games[game_id]["game"]["message"].inline_manager._units[
self.games[game_id]["game"]["message"].unit_id
]["always_allow"] = True # для ругающегося на эту строку гпт - по неизвестно какой причине фреймворк в какое-то время попросту
# забывает про отключение его проверки. мне это нужно, чтобы сам модуль брал на себя ответсвенность
# проверки, кто может управлять доской, а до кого очередь ещё не дошла
games_backup = {}
games = self.games
for game_id, game in games.items():
if game.get("game", None):
game_copy = game
if not game.get("backup", None):
game_copy = {}
game_copy["backup"] = True
game_copy["game"] = {
k: v for k, v in game["game"].items()
if k not in ("message", "root_node", "curr_node", "board")
}
game_copy["game"]["node"] = str(game["game"]["root_node"])
if game.get("Timer", None) and game["Timer"].get("timer", None):
game_copy["Timer"] = game["Timer"]["timer"].backup()
for key, value in game.items():
if key not in ("game", "Timer"):
game_copy[key] = value
games_backup[game_id] = game_copy
self.set("games_backup", games_backup)
############## Starting game... ##############
async def _start_timer(self, call: InlineCall, board_call: InlineCall, game_id: str):
if not await self._check_player(call, game_id): return
timer = self.games[game_id]["Timer"]
timer["timer_loop"] = True
await self._start_game(board_call, game_id)
async def _start_game(self, call: InlineCall, game_id: str):
if not await self._check_player(call, game_id): return
game = self.games[game_id]
node = chess.pgn.Game()
pgn = copy.deepcopy(self.pgn)
pgn["Date"] = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
pgn["Round"] = str(game_id)
pgn["White"] = game["sender"]["name"] if game["sender"]["color"] else game["opponent"]["name"]
pgn["Black"] = game["opponent"]["name"] if game["sender"]["color"] else game["sender"]["name"]
pgn["Result"] = "*"
node.headers.update(pgn)
game["game"] = {
"board": chess.Board(),
"message": call,
"root_node": node,
"curr_node": node,
"state": "idle",
"add_params": {
"chosen_figure_coord": "",
"reason_of_ending": "",
"promotion_move": "",
"winner_color": None,
"resigner_color": None,
"draw_offerer": None,
}
}
await self.update_board(game_id)
def idle(self, game_id: str):
game = self.games[game_id]["game"]
game["state"] = "idle"
game["add_params"]["chosen_figure_coord"] = ""
game["add_params"]["promotion_move"] = ""
game["add_params"]["draw_offerer"] = None
def choose(self, game_id: str, coord: str):
game = self.games[game_id]["game"]
game["state"] = "in_choose"
game["add_params"]["chosen_figure_coord"] = coord
game["add_params"]["promotion_move"] = ""
def promotion(self, game_id: str, move: str):
game = self.games[game_id]["game"]
game["state"] = "in_promotion"
game["add_params"]["chosen_figure_coord"] = ""
game["add_params"]["promotion_move"] = move
def the_end(self, game_id: str, reason: str, winner: bool = None):
game = self.games[game_id]["game"]
game["state"] = "the_end"
game["add_params"]["reason_of_ending"] = reason
game["add_params"]["winner_color"] = winner
game["add_params"]["chosen_figure_coord"] = ""
game["add_params"]["promotion_move"] = ""
game["root_node"].headers["Result"] = (
"1-0" if winner is True else
"0-1" if winner is False else
"1/2-1/2"
)
def _get_loser_and_winner(self, game_id: str) -> tuple[str, str]:
game = self.games[game_id]
if game["sender"]["color"] == self.games[game_id]["game"]["add_params"]["winner_color"]:
return (game["opponent"]["name"], game["sender"]["name"])
else:
return (game["sender"]["name"], game["opponent"]["name"])
def _get_piece_symbol(self, game_id: str, coord: str) -> str:
game = self.games[game_id]
piece = game["game"]["board"].piece_at(chess.parse_square(coord))
return game["style"][piece.symbol()] if piece else " "
def _get_move_symbol(self, game_id: str, move: str) -> str:
game = self.games[game_id]
if len(move) == 5:
return game["style"][
"capture_promotion" if (move := chess.Move.from_uci(move))
and game["game"]["board"].is_capture(move)
else "promotion"
]
else:
return game["style"][
"capture" if (move := chess.Move.from_uci(move))
and game["game"]["board"].is_capture(move)
else "move"
]
def _get_available_moves(self, game_id: str, coord: str) -> list[str]:
if not coord: return []
game = self.games[game_id]
coord = chess.parse_square(coord)
moves = [move.uci() for move in game["game"]["board"].legal_moves if move.from_square == coord]
return moves
def _get_board_dict(self, game_id: str) -> dict[str, str]:
game = self.games[game_id]
coords = copy.deepcopy(self.coords)
for coord in self.coords:
coords[coord] = self._get_piece_symbol(game_id, coord)
if game["game"]["state"] == "in_choose":
choosen_coord = game["game"]["add_params"]["chosen_figure_coord"]
for move in self._get_available_moves(game_id, choosen_coord):
coord = move[2:4]
coords[coord] = self._get_move_symbol(game_id, move)
return coords
def _get_reply_markup(self, game_id: str, promotion: bool = False, resign_confirm: bool = False, draw_confirm: bool = False) -> list[list[dict]]:
game = self.games[game_id]
is_end = game["game"]["state"] == "the_end"
reply_markup = utils.chunks(
[
{
"text": figure,
"callback": self.choose_coord,
"args": (game_id, coord),
}
for coord, figure in self._get_board_dict(game_id).items()
][::-1],
8
)
if promotion:
reply_markup.append(
[{"text": "⬇️↻⬇️", "action": "answer", "message": self.strings["choose_promotion"]}]
)
reply_markup.append(
[
{
"text": game["style"].get(piece, piece),
"callback": self.pawn_promotion,
"args": (game_id, piece),
} for piece in "qrnb"
]
)
elif resign_confirm:
reply_markup.extend(
[
[
{
"text": self.strings["resign_check"],
"data": "_there_is_nothing",
}
],
[
{
"text": self.strings["resign_yes"],
"callback": self.resign,
"args": (game_id, True),
},
{
"text": self.strings["resign_no"],
"callback": self._back_to_game,
"args": (game_id,),
},
]
]
)
elif draw_confirm:
reply_markup.extend(
[
[
{
"text": self.strings["draw_offer"].format(
self.strings["white"] if game["game"]["add_params"]["draw_offerer"]
else self.strings["black"]
),
"data": "_there_is_nothing",
}
],
[
{
"text": self.strings["draw_yes"],
"callback": self.draw,
"args": (game_id, True),
},
{
"text": self.strings["resign_no"],
"callback": self._back_to_game,
"args": (game_id,),
},
]
]
)
elif not is_end:
resign = [
{
"text": "🏳️",
"callback": self.resign,
"args": (game_id,),
},
{
"text": "🤝",
"callback": self.draw,
"args": (game_id,),
}
]
reply_markup.append(resign)
return reply_markup
async def update_board(self, game_id: str, promotion: bool = False, resign_confirm: bool = False, draw_confirm: bool = False):
game = self.games[game_id]
is_end = game["game"]["state"] == "the_end"
reason_of_ending = game["game"]["add_params"]["reason_of_ending"]
status = (
self.strings["check"] if game["game"]["board"].is_check() and not is_end
else self.strings[reason_of_ending] + "\n"
)
loser, winner = self._get_loser_and_winner(game_id)
reply_markup = self._get_reply_markup(game_id, promotion, resign_confirm, draw_confirm)
pgn = game["game"]["root_node"].accept(chess.pgn.StringExporter(columns=None, headers=False)).replace("*", "").rsplit(maxsplit=1)
if pgn:
pgn[-1] = f"<b>{pgn[-1]}</b>"
else:
pgn = ["<b>|</b>"]
last_moves = " ".join(pgn)
await utils.answer(
game["game"]["message"],
self.strings["board"].format(
game_id,
utils.escape_html(game["sender"]["name"] if game["sender"]["color"] else game["opponent"]["name"]),
utils.escape_html(game["opponent"]["name"] if game["sender"]["color"] else game["sender"]["name"]),
self.strings["white"] if game["game"]["board"].turn else self.strings["black"],
status.format(loser=loser, winner=winner),
last_moves[-32:],
),
reply_markup=reply_markup,
)
def make_move(self, game_id: str, move: str):
game = self.games[game_id]["game"]
move = chess.Move.from_uci(move)
game["board"].push(move)
game["curr_node"] = game["curr_node"].add_variation(move)
async def pawn_promotion(self, call: InlineCall, game_id: str, piece: str):
if not await self._check_player(call, game_id): return
game = self.games[game_id]["game"]
move = game["add_params"]["promotion_move"] + piece
self.make_move(game_id, move)
self.set_game_state(game_id)
return await self.update_board(game_id)
async def _back_to_game(self, _, game_id: str):
self.set_game_state(game_id)
await self.update_board(game_id)
async def resign(self, call: InlineCall, game_id: str, confirm: bool = False):
if not await self._check_player(call, game_id): return
game = self.games[game_id]
if not confirm:
game["game"]["add_params"]["resign_offerer"] = self._get_color_by_player(
game_id,
call.from_user.id
)
return await self.update_board(game_id, resign_confirm=True)
self.the_end(game_id, "resign", winner=not game["game"]["board"].turn)
await self.update_board(game_id)
async def draw(self, call: InlineCall, game_id: str, accept: bool = False):
if not await self._check_player(call, game_id, skip_turn_check=True): return
game = self.games[game_id]
if accept:
offerer_id = self._get_player_by_color(
game_id,
game["game"]["add_params"]["draw_offerer"]
)["id"]
if call.from_user.id == offerer_id:
await call.answer(self.strings["draw_not_you"])
return
self.the_end(game_id, "draw")
return await self.update_board(game_id)
game["game"]["add_params"]["draw_offerer"] = self._get_color_by_player(
game_id,
call.from_user.id
)
return await self.update_board(game_id, draw_confirm=True)
def set_game_state(self, game_id: str):
game = self.games[game_id]["game"]
board = game["board"]
self.idle(game_id)
if board.is_checkmate():
self.the_end(game_id, "checkmate", winner=not board.turn)
elif board.is_stalemate():
self.the_end(game_id, "stalemate")
elif board.is_insufficient_material():
self.the_end(game_id, "insufficient_material")
elif board.is_seventyfive_moves():
self.the_end(game_id, "seventyfive_moves")
elif board.is_fivefold_repetition():
self.the_end(game_id, "fivefold_repetition")
async def choose_coord(self, call: BotInlineCall, game_id: str, coord: str):
if not await self._check_player(call, game_id): return
game = self.games[game_id]["game"]
state = game["state"]
if state == "idle":
if self._get_available_moves(game_id, coord):
self.choose(game_id, coord)
else:
await call.answer(self.strings["no_moves"])
return await self.update_board(game_id)
elif state == "in_choose":
if coord == game["add_params"]["chosen_figure_coord"]:
self.idle(game_id)
return await self.update_board(game_id)
av_moves = self._get_available_moves(game_id, game["add_params"]["chosen_figure_coord"])
coord_matches = [move for move in av_moves if coord in move]
if len(coord_matches) == 1:
self.make_move(game_id, coord_matches[0])
self.set_game_state(game_id)
return await self.update_board(game_id)
elif len(coord_matches) > 1:
move = coord_matches[0][:4]
self.promotion(game_id, move)
return await self.update_board(game_id, promotion=True)
elif game["board"].piece_at(chess.parse_square(coord)):
self.choose(game_id, coord)
return await self.update_board(game_id)
else:
self.idle(game_id)
return await self.update_board(game_id)
elif state == "in_promotion":
return await call.answer(self.strings["can_not_move"])
elif state == "the_end":
return await call.answer(self.strings["game_ended"])
else:
await call.answer("ты игру сломал?")
self.idle(game_id)
return await self.update_board(game_id)
def _get_player_by_color(self, game_id: str, color: bool):
game = self.games[game_id]
return game["sender"] if game["sender"]["color"] == color else game["opponent"]
def _get_color_by_player(self, game_id: str, player_id: int):
game = self.games[game_id]
if game["sender"]["id"] == player_id:
return game["sender"]["color"]
elif game["opponent"]["id"] == player_id:
return game["opponent"]["color"]
return None

View File

@@ -0,0 +1,435 @@
__version__ = (1,1,7)
#░░░███░███░███░███░███
#░░░░░█░█░░░░█░░█░░░█░█
#░░░░█░░███░░█░░█░█░█░█
#░░░█░░░█░░░░█░░█░█░█░█
#░░░███░███░░█░░███░███
# H:Mods Team [💎]
# meta developer: @nullmod
# - main - #
from .. import loader, utils
# - func - #
import asyncio
import logging
import time
import re
# - func(tl) - #
from telethon.tl.functions.chatlists import CheckChatlistInviteRequest, JoinChatlistInviteRequest, LeaveChatlistRequest
from telethon.tl.functions.messages import ImportChatInviteRequest, CheckChatInviteRequest
from telethon.tl.functions.channels import JoinChannelRequest, LeaveChannelRequest
from telethon.tl.functions.contacts import BlockRequest, UnblockRequest
# - types - #
from telethon.tl.types import InputChatlistDialogFilter, UpdateDialogFilter
# - errors - #
from telethon.errors import YouBlockedUserError, InviteRequestSentError
# - end - #
logger = logging.getLogger(__name__)
@loader.tds
class HaremManager(loader.Module):
"""Module for harem bots: Gif Harem, Waifu Harem, Horny Harem"""
strings = {
"name": "HaremManager"
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"ignore-chats",
[],
"Список чатов, где модуль НЕ будет ловить вайфу. Указывайте ID чатов в виде 123456789",
validator=loader.validators.Series(
validator=loader.validators.Integer(),
)
),
loader.ConfigValue(
"whitelist-chats",
[],
"Список чатов, где модуль будет ловить вайфу. Указывайте ID чатов в виде 123456789. Если что-то указано, то модуль будет ловить вайфу только в этих чатах",
validator=loader.validators.Series(
validator=loader.validators.Integer(),
)
),
loader.ConfigValue(
"interval-horny",
4,
"Интервал между автобонусом",
validator=loader.validators.Float(2.0)
),
loader.ConfigValue(
"interval-waifu",
4,
"Интервал между автобонусом",
validator=loader.validators.Float(2.0)
),
loader.ConfigValue(
"interval-gif",
4,
"Интервал между автобонусом",
validator=loader.validators.Float(2.0)
),
)
async def client_ready(self):
self.harems = {
"horny": "@Horny_GaremBot",
"waifu": "@garem_chatbot",
"gif": "@GIFgarem_bot",
}
self.harems_ids = {
"horny": 7896566560,
"waifu": 6704842953,
"gif": 7084965046,
}
temp_values = [
"config",
"ab-horny",
"catch-horny",
"out-horny",
"ab-waifu",
"catch-waifu",
"out-waifu",
"ab-gif",
"catch-gif",
"out-gif"
]
if not self.get("config", None):
for value in temp_values:
self.set(value, False if value not in "config" else True)
@loader.loop(interval=1, autostart=True)
async def loop(self):
for bot in self.harems:
if self.get(f"ab-{bot}", None):
if (not self.get(f"ab-t-{bot}") or (time.time() - self.get(f"ab-t-{bot}")) >= int(3600*self.config[f"interval-{bot}"])):
await self._autobonus(self.harems[bot], bot)
@loader.watcher("only_messages")
async def watcher(self, message):
"""Watcher"""
chatid = int(str(message.chat_id).replace("-100", ""))
for bot in self.harems:
if bot == "waifu": continue
if message.sender_id == self.harems_ids[bot] and self.get(f"catch-{bot}", None):
if self.config["whitelist-chats"]:
if chatid not in self.config["whitelist-chats"]:
return
elif chatid in self.config["ignore-chats"]:
return
if (not self.get(f"catcher_time-{bot}") or int(time.time()) - int(self.get(f"catcher_time-{bot}")) > 14400):
if "заблудилась" in message.text.lower():
try:
await message.click()
await asyncio.sleep(5)
msgs = await message.client.get_messages(chatid, limit=10)
for msg in msgs:
if msg.mentioned and "забрали" in msg.text and msg.sender_id == self.harems_ids[bot]:
if self.get(f"out-{bot}", None):
match = re.search(r", Вы забрали (.+?)\. Вайфу", msg.text)
waifu = match.group(1)
caption = f"{waifu} в вашем гареме! <emoji document_id=5395592707580127159>😎</emoji>"
await self.client.send_file(self.harems[bot], caption=caption, file=message.media)
self.set(f"catcher_time-{bot}", int(time.time()))
except Exception as e:
logger.error(f"Ошибка при ловле вайфу для {bot}(не критично): {e}")
def _main_markup(self):
return [
[
{
"text": "[✔️] Horny Harem" if self.get("ab-horny") else "[❌] Horny Harem",
"callback": self.callback_handler,
"args": ("horny",)
},
{
"text": "[✔️] Waifu Harem" if self.get("ab-waifu") else "[❌] Waifu Harem",
"callback": self.callback_handler,
"args": ("waifu",)
},
],
[
{
"text": "[✔️] Gif Harem" if self.get("ab-gif") else "[❌] Gif Harem",
"callback": self.callback_handler,
"args": ("gif",)
}
],
[
{
"text": "🔻 Закрыть",
"action": "close",
}
]
]
def _menu_markup(self, bot):
markup = [[],[]]
markup[0].append({
"text": "[✔️] Автобонус" if self.get(f"ab-{bot}", None) else "[❌] Автобонус",
"callback": self.callback_handler,
"args": (f"ab-{bot}",)
})
if "waifu" not in bot:
markup[0].append({
"text": "[✔️] Автоловля" if self.get(f"catch-{bot}", None) else "[❌] Автоловля",
"callback": self.callback_handler,
"args": (f"catch-{bot}",)
})
markup[1].append({
"text": "[✔️] Вывод от ловца" if self.get(f"out-{bot}", None) else "[❌] Вывод от ловца",
"callback": self.callback_handler,
"args": (f"out-{bot}",)
})
markup.append([
{
"text":"🔁 Перезапустить автобонус",
"callback": self.callback_handler,
"args": (f"restart-{bot}",)
},
])
markup.append([
{
"text":"↩️ Назад",
"callback":self.callback_handler,
"args": ("back",)
}
])
return markup
async def _set_menu(self, message):
await utils.answer(
message,
f"❤️ Выберите бота для управления\n\n✅ <i>- означает, что автобонус включён</i>\
\n\nБольше настроек в конфиге модуля(<code>{self.get_prefix()}config HaremManager</code>)",
reply_markup=self._main_markup()
)
async def callback_handler(self, call, data):
if data == "back":
await self._set_menu(call)
return
elif data.startswith("restart-"):
bot = data.split("-")[-1]
await call.answer(f"Перезапуск бонуса для {self.harems[bot]}...")
await self._autobonus(self.harems[bot], bot)
return
elif data.startswith("ab-"):
bot = data.split("-")[-1]
self.set(data, not self.get(data, None))
await utils.answer(call, f"Меню <code>{self.harems[bot]}</code>", reply_markup=self._menu_markup(bot))
elif data.startswith("catch-"):
bot = data.split("-")[-1]
self.set(data, not self.get(data, None))
await utils.answer(call, f"Меню <code>{self.harems[bot]}</code>", reply_markup=self._menu_markup(bot))
elif data.startswith("out-"):
bot = data.split("-")[-1]
self.set(data, not self.get(data, None))
await utils.answer(call, f"Меню <code>{self.harems[bot]}</code>", reply_markup=self._menu_markup(bot))
else:
bot = data
await utils.answer(call, f"Меню <code>{self.harems[bot]}</code>", reply_markup=self._menu_markup(bot))
async def _autobonus(self, id, bot):
wait_boost = False
async with self._client.conversation(id) as conv:
try:
await conv.send_message("/bonus")
except YouBlockedUserError:
await self.client(UnblockRequest(id))
await conv.send_message("/bonus")
r = None
try:
r = await conv.get_response(timeout=5*60)
except:
tryings = 5
while tryings > 0:
tryings -= 1
try:
await conv.send_message("/bonus")
r = await conv.get_response(5*60)
break
except:
pass
if r is None:
logger.warning("Ответ от бота не получен. Вероятно, он снова лёг\n\nПерезапустите автобонус, когда бот очнётся")
self.set(f"ab-{bot}", False)
return
self.set(f"ab-t-{bot}", int(time.time()))
if "Доступен бонус за подписки" in r.text:
await conv.send_message("/start ad_bonus")
r = await conv.get_response()
if "проверка пройдена" not in r.text:
to_leave, to_block, folders, chats_in_folders = [], [], [], []
wait_boost = False
if r.reply_markup:
a = r.buttons
for i in a:
for button in i: # каждая кнопка...
if button.url:
alr = False # "уже зашёл"
if "addlist/" in button.url: # добавление папок
slug = button.url.split("addlist/")[-1]
peers = await self.client(CheckChatlistInviteRequest(slug=slug))
if peers:
peers = peers.peers
try:
a = await self.client(JoinChatlistInviteRequest(slug=slug, peers=peers))
chats_in_folders.append(peers) # для выхода
for update in a.updates:
if isinstance(update, UpdateDialogFilter):
folders.append(InputChatlistDialogFilter(filter_id=update.id)) # для удаления папки
except: pass
continue
if "t.me/boost" in button.url: # бустить не обязательно
wait_boost = True
continue
if not bool(re.match(r"^https?:\/\/t\.me\/[^\/]+\/?$", button.url)): # дополнительные вложения отметаем
continue
if "t.me/+" in button.url: # приватные чаты
try:
a = await self.client(CheckChatInviteRequest(button.url.split("+")[-1]))
if not hasattr(a, "request_needed") or not a.request_needed: # получить айди приватного чата/канала с приглашениями без входа невозможно
pass
else:
url = button.url.split("?")[0] if "?" in button.url else button.url
try:
await self.client(ImportChatInviteRequest(button.url.split("+")[-1]))
except InviteRequestSentError: pass
await asyncio.sleep(3)
try:
entity = await self.client.get_entity(url)
except ValueError:
try:
await asyncio.sleep(15)
entity = await self.client.get_entity(url)
except:
continue
except:
pass
alr = True
except: continue
url = button.url.split("?")[0] if "?" in button.url else button.url
if not alr:
try:
entity = await self.client.get_entity(url)
except:
entity = (await self.client(ImportChatInviteRequest(button.url.split("+")[-1]))).chats[0] #gotten class Updates
alr = True
if hasattr(entity, "broadcast"):
if not alr:
await self.client(JoinChannelRequest(button.url))
to_leave.append(entity.id)
else:
to_leave.append(entity.chat.id) if hasattr(entity,"chat") else to_leave.append(entity.id) if hasattr(entity,"id") else None
elif hasattr(entity, "bot"):
username = entity.username if entity.username is not None else entity.usernames[0].username
try:
await self.client(UnblockRequest(username))
except: print("блин")
await self.client.send_message(entity, "/start")
to_block.append(username)
flyer_messages = await self.client.get_messages(id, limit=1)
if wait_boost:
await asyncio.sleep(150)
for m in flyer_messages:
await asyncio.sleep(5)
await m.click(-1)
await asyncio.sleep(5)
for folder, chats in zip(folders, chats_in_folders):
await self.client(LeaveChatlistRequest(peers=chats, chatlist=folder))
for bot in to_block:
await self.client(BlockRequest(bot))
await self.client.delete_dialog(bot)
for channel in to_leave:
try:
await self.client(LeaveChannelRequest(channel))
except Exception as e:
pass
count = 0
if not self.get(f"last_lout-{bot}") or int(time.time()) - self.get(f"last_lout-{bot}") > 43200:
while count <= 3: # на всякий случай 4 попытки. Бот может забагаться и не выдать завершающий ответ
await conv.send_message("/lout")
r = await conv.get_response()
if r.reply_markup:
pattern = self._parse(r)
clicks = self._solution(pattern)
for i in range(len(clicks)):
if clicks[i] == 1:
await r.click(i)
self.set(f"last_lout-{bot}", int(time.time()))
count += 1
else:
break
def _parse(self, r):
a = r.buttons
pattern = []
for i in a:
for m in i:
t = m.text
if t == "🌚":
pattern.append(0)
elif t == "🌞":
pattern.append(1)
else:
pass
return pattern
def _solution(self, pole):
n = len(pole)
for num in range(2**n):
binary_string = bin(num)[2:].zfill(n)
presses = [int(char) for char in binary_string]
temp = pole[:]
for i in range(n):
if presses[i]:
temp[i] ^= 1
if i % 3 > 0: temp[i - 1] ^= 1
if i % 3 < 2: temp[i + 1] ^= 1
if i >= 3: temp[i - 3] ^= 1
if i < 6: temp[i + 3] ^= 1
if sum(temp) == 0:
return presses
return None
@loader.command()
async def Harems(self, message):
"""Открыть меню управления"""
await self._set_menu(message)
@loader.command()
async def lightsout(self, message):
"""[ответ на соо с полем] Автоматически решает Lights Out"""
if message.is_reply:
r = await message.get_reply_message()
if r.reply_markup:
pattern = self._parse(r)
else:
await utils.answer(message, "<emoji document_id=5299030091735525430>❗️</emoji> Не вижу поля игры. Это точно то сообщение?")
return
else:
await utils.answer(message, "<emoji document_id=5299030091735525430>❗️</emoji> Пропиши команду в ответ на игру.")
return
if pattern:
await utils.answer(message, "<emoji document_id=5472146462362048818>💡</emoji>")
clicks = self._solution(pattern)
if not clicks:
await utils.answer(message, "Иди код трейси гений.")
return #*смачный пинок кодеру под зад.*
for i in range(len(clicks)):
if clicks[i] == 1:
await r.click(i)
await utils.answer(message, "<emoji document_id=5395592707580127159>😎</emoji> Готово.")
else:
await utils.answer(message, "<emoji document_id=5299030091735525430>❗️</emoji> Ты ответил не на поле игры.")
return

View File

@@ -0,0 +1,82 @@
__version__ = (1,1,1)
#░░░███░███░███░███░███
#░░░░░█░█░░░░█░░█░░░█░█
#░░░░█░░███░░█░░█░█░█░█
#░░░█░░░█░░░░█░░█░█░█░█
#░░░███░███░░█░░███░███
# Team: 'H:Mods'
# meta developer: @nullmod
from .. import loader, utils
import re
from datetime import datetime, timedelta, timezone
@loader.tds
class SchedulePlus(loader.Module):
"""Планирование периодичных сообщений"""
strings = {"name": "SchedulePlus",
"no_args": "<emoji document_id=5019523782004441717>❌</emoji> Invalid arguments",
"too_many": "<emoji document_id=5019523782004441717>❌</emoji> Maximum number of scheduled messages is 100.",
"scheduled": "<emoji document_id=5062291541624619917>✈️</emoji> Messages will be scheduled"}
strings_ru = {"name": "SchedulePlus",
"no_args": "<emoji document_id=5019523782004441717>❌</emoji> Неверные аргументы",
"too_many": "<emoji document_id=5019523782004441717>❌</emoji> Максимальное число отложенных сообщений - 100.",
"scheduled": "<emoji document_id=5062291541624619917>✈️</emoji> Сообщения будут запланированы"}
@loader.command()
async def sch(self, message):
"""Используй .sch <периодичность в секундах> <количество отправок> <текст/содержимое из ответа>
Проф. режим: .sch 15 3 test{x=1;x*2}/{y=0;y+1}
Запланирует три сообщения: test2/1, test4/2, test8/3"""
args = utils.get_args_raw(message.text).split(' ', 2)
resp = (await message.get_reply_message()) if len(args) < 3 and message.is_reply else message
if not resp or not args[0].isdigit() or not args[1].isdigit():
return await utils.answer(message, self.strings["no_args"])
interval, count, text = int(args[0]), int(args[1]), args[2] if len(args) > 2 else resp.text
if count > 100:
return await utils.answer(message, self.strings["too_many"])
chat_id = message.chat_id
reply_message_id = resp.reply_to.reply_to_msg_id if resp.reply_to else None
await utils.answer(message, self.strings["scheduled"])
variables = {}
for i in range(count):
send_time = datetime.now(timezone.utc) + timedelta(seconds=interval * i)
formatted_text = self.process_text(text, variables)
await self.client.send_message(chat_id, formatted_text, file=resp.media, schedule=send_time, reply_to=reply_message_id)
def process_text(self, text, variables):
"""Process text okay?"""
def replace_match(match):
return self.eval_expr(match.group(1), variables)
return re.sub(r"\{(.*?)\}", replace_match, text)
def eval_expr(self, expr, variables):
"""eval()"""
parts = expr.split(";")
last_value = None
var_name = None
for part in parts:
part = part.strip()
if "=" in part and part.count("=") == 1:
var, value = part.split("=")
var = var.strip()
if var not in variables:
variables[var] = eval(value, {"__builtins__": {}}, variables)
last_value = variables[var]
var_name = var
else:
last_value = eval(part, {"__builtins__": {}}, variables)
if var_name is not None:
variables[var_name] = last_value
return str(last_value)

View File

@@ -0,0 +1,3 @@
Chess
HaremManager
SchedulePlus