From d18c5016ce724b4b21188efbc2c39279fc3c740d Mon Sep 17 00:00:00 2001 From: vrubel Date: Sat, 20 Jun 2026 22:09:48 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20rescan=5Fexisting=5Ftournaments=20?= =?UTF-8?q?=E2=80=94=20detect=20new=20players=20in=20cached=20tournaments?= =?UTF-8?q?=20every=202h?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bots/client_bot.py | 2 ++ bots/tracker.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/bots/client_bot.py b/bots/client_bot.py index 351284a..58326d6 100644 --- a/bots/client_bot.py +++ b/bots/client_bot.py @@ -721,6 +721,8 @@ def main(): tracker.check_all_subscriptions, interval=300, first=10) app.job_queue.run_repeating( tracker.rescan_new_tournaments, interval=3600, first=60) + app.job_queue.run_repeating( + tracker.rescan_existing_tournaments, interval=7200, first=300) app.job_queue.run_repeating( tracker.warmup_cache, interval=21600, first=10) diff --git a/bots/tracker.py b/bots/tracker.py index fbaaf8e..cbcab6c 100644 --- a/bots/tracker.py +++ b/bots/tracker.py @@ -371,6 +371,16 @@ def _fetch_and_parse_art0(tnr: int) -> dict | None: return parsed +def _refetch_art0(tnr: int) -> dict | None: + """Always fetch art=0 via HTTP, update cache. Returns parsed dict or None.""" + html = _fetch_art0(tnr) + if html is None: + return None + parsed = _parse_art0_page(html) + _save_tnr_cache(tnr, parsed) + return parsed + + # ═══ Tournament scanner (art=0 pages) ═══ ART0_URL = 'https://chess-results.com/tnr{tnr}.aspx?lan=11&art=0&turdet=YES' @@ -684,6 +694,69 @@ def _warmup_cache_sync(): set_tnr_state('warmup_tnr', 'done') +async def rescan_existing_tournaments(context): + """Every 2h: refetch art=0 for all cached TNRs, detect new players.""" + import time + from bots.client_bot import _md_escape as _md_esc + + conn = _get_conn() + cached_tnrs = [r[0] for r in conn.execute('SELECT tnr FROM tnr_cache ORDER BY tnr DESC').fetchall()] + conn.close() + if not cached_tnrs: + return + + # Collect subscribed FIDE IDs and their users + conn = _get_conn() + sub_rows = conn.execute( + 'SELECT DISTINCT user_id, fide_id, lang FROM subscriptions WHERE active = 1' + ).fetchall() + conn.close() + if not sub_rows: + return + fide_to_users = {} + for user_id, fide_id, lang in sub_rows: + fide_to_users.setdefault(fide_id, []).append((user_id, lang)) + + for tnr in cached_tnrs: + old_data = _get_cached_tnr(tnr) + old_fids = set(old_data['players'].keys()) if old_data else set() + + new_data = _refetch_art0(tnr) + if new_data is None: + continue # tournament deleted or unreachable + new_fids = set(new_data['players'].keys()) + + # New players that appeared since last cached snapshot + appeared = new_fids - old_fids + for fid in appeared: + if fid not in fide_to_users: + continue + player_info = new_data['players'][fid] + url = f'https://chess-results.com/tnr{tnr}.aspx?lan=11' + for user_id, lang in fide_to_users[fid]: + player = {'fide_id': fid, 'name': player_info['name'], + 'rating': player_info.get('rating', 0), 'fed': ''} + add_subscription( + user_id, player, url, new_data['name'] or f'Tournament {tnr}', + player_info['sno'], 0, 0.0, lang, + new_data.get('start_date', ''), new_data.get('end_date', '')) + try: + safe_name = _md_esc(player_info['name']) + safe_tour = _md_esc(new_data['name'] or f'Tournament {tnr}') + dates = '' + if new_data.get('start_date') and new_data.get('end_date'): + dates = f'\\({new_data["start_date"]} — {new_data["end_date"]}\\)' + await context.bot.send_message( + user_id, + f'🆕 *{safe_name}* появился в турнире\!\n' + f'📅 *{safe_tour}*{dates}\n' + 'Автоматически подписал — буду отслеживать\.', + parse_mode='MarkdownV2') + except Exception: + pass + time.sleep(0.1) + + async def check_all_subscriptions(context): from swiss_calc.parser import fetch_tournament from swiss_calc.swiss import calculate_next_round