diff --git a/bots/client_bot.py b/bots/client_bot.py index 845b714..ab77bde 100644 --- a/bots/client_bot.py +++ b/bots/client_bot.py @@ -104,12 +104,15 @@ def _pad_col(s: str, width: int) -> str: return s + ' ' * (width - cur) -def format_pairings(pairings_data: dict, tournament_name: str, lang: str = '') -> str: +def format_pairings(pairings_data: dict, tournament_name: str, lang: str = '', + player_name: str = '') -> str: locale = _get_locale(lang) pairings = pairings_data['pairings'] rnd = pairings_data['round'] - w_board, w_name, w_rating, w_pts, w_tb1 = 2, 17, 4, 4, 5 + # If highlighting a player, reduce name width to compensate for ✅ emoji (visually 2 chars wide) + w_name = 15 if player_name else 17 + w_board, w_rating, w_pts, w_tb1 = 2, 4, 4, 5 border = '+'.join('-' * w for w in [w_board, w_name, w_rating, w_pts, w_tb1]) header = (f'{_pad_col("#", w_board)}|{_pad_col(locale["player"], w_name)}|' @@ -121,23 +124,60 @@ def format_pairings(pairings_data: dict, tournament_name: str, lang: str = '') - footer = locale['footer_bbp'] if source == 'bbp_pairings_fide_2025' else \ locale['footer_fallback'] if source == 'swiss_algorithm_simplified' else '' + safe_tour = _md_escape(tournament_name) + title = f'📋 *{safe_tour}*\n*{locale["title"].format(rnd=rnd)}*' + return { - 'title': locale['title'].format(rnd=rnd), + 'title': title, 'rows': _build_table_rows(pairings, w_board, w_name, w_rating, w_pts, w_tb1, border, - locale['bye']), + locale['bye'], player_name), 'table_header': table_header, 'footer': footer, } def _build_table_rows(pairings: list, w_board: int, w_name: int, w_rating: int, - w_pts: int, w_tb1: int, border: str, bye_label: str = '— BYE —') -> list: + w_pts: int, w_tb1: int, border: str, bye_label: str = '— BYE —', + player_name: str = '') -> list: def _rating_str(p): return str(p.rating) if p.rating else '' def _tb1_str(p): return f"{p.tb[0]:.1f}" if p.tb else '' + # Find the board where the player plays (normalize names: strip commas, collapse spaces) + def _n(s: str) -> str: + return s.replace(',', '').replace(' ', ' ').lower() + + player_board = 0 + board_idx = 1 + player_norm = _n(player_name) if player_name else '' + for pairing in pairings: + if len(pairing) != 3: + continue + p1, p2, _color = pairing + if player_norm and (player_norm == _n(p1.name) or player_norm == _n(p2.name)): + player_board = board_idx + # Store the actual name from pairings for the ✅ marker + if player_norm == _n(p1.name): + player_name = p1.name + else: + player_name = p2.name + break + board_idx += 1 + + # If we have a player, show ±5 boards around them + if player_board > 0: + lo = max(1, player_board - 5) + hi = player_board + 5 + else: + lo, hi = 1, 999999 + + def _name_cell(name: str) -> str: + if player_name and name == player_name: + return f'✅ {name}' + return name + rows = [] board = 1 for pi, pairing in enumerate(pairings): @@ -145,10 +185,16 @@ def _build_table_rows(pairings: list, w_board: int, w_name: int, w_rating: int, continue p1, p2, color = pairing + if board < lo: + board += 1 + continue + if board > hi: + break + if color == 'bye': rows.append( f'{_pad_col(str(board), w_board)}|' - f'{_pad_col(p1.name, w_name)}|' + f'{_pad_col(_name_cell(p1.name), w_name)}|' f'{_pad_col(_rating_str(p1), w_rating)}|' f'{_pad_col(f"{p1.points:.1f}", w_pts)}|' f'{_pad_col(_tb1_str(p1), w_tb1)}' @@ -161,7 +207,7 @@ def _build_table_rows(pairings: list, w_board: int, w_name: int, w_rating: int, f'{_pad_col("", w_tb1)}' ) board += 1 - if pi + 1 < len(pairings): + if pi + 1 < len(pairings) and board <= hi: rows.append(border) continue @@ -172,22 +218,21 @@ def _build_table_rows(pairings: list, w_board: int, w_name: int, w_rating: int, rows.append( f'{_pad_col(str(board), w_board)}|' - f'{_pad_col(white.name, w_name)}|' + f'{_pad_col(_name_cell(white.name), w_name)}|' f'{_pad_col(_rating_str(white), w_rating)}|' f'{_pad_col(f"{white.points:.1f}", w_pts)}|' f'{_pad_col(_tb1_str(white), w_tb1)}' ) rows.append( f'{_pad_col("", w_board)}|' - f'{_pad_col(black.name, w_name)}|' + f'{_pad_col(_name_cell(black.name), w_name)}|' f'{_pad_col(_rating_str(black), w_rating)}|' f'{_pad_col(f"{black.points:.1f}", w_pts)}|' f'{_pad_col(_tb1_str(black), w_tb1)}' ) board += 1 - # Separator between pairs - if pi + 1 < len(pairings): + if pi + 1 < len(pairings) and board <= hi: rows.append(border) return rows @@ -342,7 +387,7 @@ async def _lookup_and_confirm(update: Update, context, fide_id: int): safe_tour = _md_escape(t['name']) lines.append(f' • {safe_tour}{dates}') lines.append('\nЯ буду присылать результаты и жеребьёвку\.\n' - '/myplayers — список подписок') + '/myplayers \\- список подписок') await update.message.reply_text('\n'.join(lines), parse_mode=ParseMode.MARKDOWN_V2) else: @@ -607,7 +652,20 @@ async def handle_url(update: Update, context: ContextTypes.DEFAULT_TYPE): return lang_code = user.language_code or '' - fmt = format_pairings(result, tournament['name'], lang_code) + + # If user has a subscription for this tournament, highlight their player + player_name = '' + try: + subs = tracker.get_user_subs(user.id) + for s in subs: + sub_tour = s.get('tournament_name', '') + if sub_tour and sub_tour == tournament.get('name', ''): + player_name = s.get('player_name', '') + break + except Exception: + pass + + fmt = format_pairings(result, tournament['name'], lang_code, player_name) chunks = _render_chunks(fmt) try: diff --git a/bots/tracker.py b/bots/tracker.py index 2a33fdb..230ab4e 100644 --- a/bots/tracker.py +++ b/bots/tracker.py @@ -728,7 +728,8 @@ async def check_all_subscriptions(context): if current_rd > sub['last_round_done'] and current_rd < tournament.get('num_rounds', 0): try: result = calculate_next_round(tournament) - fmt = format_pairings(result, tournament.get('name', ''), sub['lang']) + fmt = format_pairings(result, tournament.get('name', ''), sub['lang'], + sub['player_name']) chunks = _render_chunks(fmt) for chunk in chunks: try: