From 62cdd750f863eba44c5e63379415720bcd2e897c Mon Sep 17 00:00:00 2001 From: vrubelroman Date: Fri, 3 Jul 2026 13:33:58 +0000 Subject: [PATCH] align /lastyear_or_1000games table into a single monospace code block Column-align per-mode rows (games/rating change/rating/W-L-D/accuracy) same as the other stats commands, instead of loose free-text lines. Strip the invisible variation selector from bullet/classical emoji so it doesn't throw off column padding. --- LichessClientTG_bot/formatters.py | 72 ++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/LichessClientTG_bot/formatters.py b/LichessClientTG_bot/formatters.py index 3a1e36f..3bf63ca 100644 --- a/LichessClientTG_bot/formatters.py +++ b/LichessClientTG_bot/formatters.py @@ -380,23 +380,23 @@ class StatsFormatter: period_end = data.get('period_end') stats = (data.get('data') or {}) # Title and subheader - escaped_username = StatsFormatter._escape_md(username) if games_count >= 1000: - header = f"šŸ“ˆ {escaped_username}: last 1000 rated games" + header = f"šŸ“ˆ {username}: last 1000 rated games" earliest_ts = data.get('earliest_game_ts') if isinstance(earliest_ts, int): earliest = datetime.fromtimestamp(earliest_ts).strftime("%d.%m.%Y") - header += f"\n\n\nStart of these 1000 games: {earliest}" + header += f"\n\nStart of these 1000 games: {earliest}" else: - header = f"šŸ“ˆ {escaped_username}: last year (rated), games: {games_count}" + header = f"šŸ“ˆ {username}: last year (rated), games: {games_count}" # Use earliest actual game date instead of naive 'year ago' earliest_ts = data.get('earliest_game_ts', period_start) if isinstance(earliest_ts, int) and isinstance(period_end, int): start_str = datetime.fromtimestamp(earliest_ts).strftime("%d.%m.%Y") end_str = datetime.fromtimestamp(period_end).strftime("%d.%m.%Y") - header += f"\n\n\nPeriod: {start_str}–{end_str}" - # Body per mode - lines = [] + header += f"\n\nPeriod: {start_str}–{end_str}" + + # Collect per-mode rows + rows = [] for mode in ["bullet", "blitz", "rapid", "classical", "correspondence"]: mode_stats = stats.get(mode) if not mode_stats: @@ -404,28 +404,50 @@ class StatsFormatter: games_played = mode_stats.get('games_played', 0) if games_played == 0: continue - emoji = StatsFormatter._get_game_type_emoji(mode) + # Strip the variation selector some emoji include (e.g. bullet/classical) — + # it's invisible but counts as an extra character, which would throw off + # the column padding below even though it doesn't add any visual width. + emoji = StatsFormatter._get_game_type_emoji(mode).replace('ļø', '') wins = mode_stats.get('wins', 0) losses = mode_stats.get('losses', 0) draws = mode_stats.get('draws', 0) rating_change = mode_stats.get('rating_change', 0) - rating_change_str = StatsFormatter._format_rating_change(rating_change) rating = mode_stats.get('rating') - rating_str = rating if rating is not None else "—" - line = f"{emoji} {mode.title()}: {games_played} Ī” {rating_change_str} R {rating_str} āœ… {wins} āŒ {losses} šŸ¤ {draws}" + accuracy_str = "-" if mode in ("blitz", "rapid", "classical"): - line += f" šŸŽÆ {StatsFormatter._format_accuracy(mode_stats.get('accuracy'))}" + accuracy_str = StatsFormatter._format_accuracy(mode_stats.get('accuracy')) + rows.append(( + f"{emoji} {mode.title()}", + str(games_played), + StatsFormatter._format_rating_change(rating_change), + str(rating) if rating is not None else "—", + str(wins), + str(losses), + str(draws), + accuracy_str, + )) + + if not rows: + return f"{header}\n\nšŸ“­ No data" + + label_w = max(len(r[0]) for r in rows) + games_w = max(len(r[1]) for r in rows) + change_w = max(len(r[2]) for r in rows) + rating_w = max(len(r[3]) for r in rows) + wins_w = max(len(r[4]) for r in rows) + losses_w = max(len(r[5]) for r in rows) + draws_w = max(len(r[6]) for r in rows) + acc_w = max((len(r[7]) for r in rows), default=0) + + lines = [] + for label, games, change, rating, wins, losses, draws, acc in rows: + line = ( + f"{label:<{label_w}} {games:>{games_w}} {change:<{change_w}} " + f"R{rating:>{rating_w}} āœ…{wins:>{wins_w}} āŒ{losses:>{losses_w}} šŸ¤{draws:>{draws_w}}" + ) + if acc_w: + line += f" šŸŽÆ{acc:>{acc_w}}" lines.append(line) - # Join lines with newlines between each mode - # Between regular modes: one empty line (\n\n) - # Before last mode: two empty lines (\n\n\n) - if len(lines) == 0: - body = "" - elif len(lines) == 1: - body = lines[0] - else: - # All modes except last joined with one empty line - body = "\n\n".join(lines[:-1]) - # Add two empty lines before last mode - body += "\n\n\n" + lines[-1] - return f"{header}\n\n{body}" + + body = "\n".join(lines) + return f"{header}\n\n```\n{body}\n```"