authenticate outgoing lichess.org requests to fix silent 404s on games/user
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 12s

Lichess started rejecting anonymous requests to games/user and
user/activity with a 404 (confirmed live: the same request with a valid
bearer token got a normal 429 instead), which our code silently read as
"user has no games" for every single tracked player, making periodic
checks report zero activity across the board. Add LICHESS_APP_TOKEN and
send it as a Bearer token on these two calls; it just needs to be any
valid token; it does not need to belong to the tracked player.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
vrubelroman 2026-07-25 23:01:35 +00:00
parent 08f97d413a
commit 7ef5875f58
4 changed files with 27 additions and 3 deletions

View file

@ -4,3 +4,9 @@ COMPOSE_PROJECT_NAME=lichess
# Telegram Bot Tokens (Test) # Telegram Bot Tokens (Test)
TELEGRAM_BOT_TOKEN=7903295042:AAGBO2k8pfBDy4RoLRFsknwE7z0N-thAPI8 TELEGRAM_BOT_TOKEN=7903295042:AAGBO2k8pfBDy4RoLRFsknwE7z0N-thAPI8
ADMINPANEL_TELEGRAM_BOT_TOKEN=8588876086:AAHoZncfhTCbul1BblpvnZMzvz7jAYVFmcw ADMINPANEL_TELEGRAM_BOT_TOKEN=8588876086:AAHoZncfhTCbul1BblpvnZMzvz7jAYVFmcw
# Lichess personal API token used to authenticate outgoing requests to
# lichess.org (games/user, user/activity). Lichess started rejecting
# anonymous requests to these endpoints with a 404; any valid token works,
# it does not need to belong to a tracked player.
LICHESS_APP_TOKEN=lip_xxxxxxxxxxxxxxxxxxxx

View file

@ -14,6 +14,7 @@ Lichess Statistics API - Клиент для работы с Lichess API
""" """
import httpx import httpx
import os
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
@ -44,6 +45,15 @@ class LichessClient:
self.base_url = "https://lichess.org/api" # Базовый URL Lichess API self.base_url = "https://lichess.org/api" # Базовый URL Lichess API
self.client = httpx.AsyncClient(timeout=30.0) # HTTP клиент с таймаутом self.client = httpx.AsyncClient(timeout=30.0) # HTTP клиент с таймаутом
self.rate_limiter = get_rate_limiter() self.rate_limiter = get_rate_limiter()
# Lichess начал возвращать 404 (вместо честного 401/429) на анонимные
# запросы к games/user и user/activity — тот же валидный токен, отправленный
# для другого юзера, сразу превращает 404 в 429, то есть эндпоинт жив, просто
# больше не пускает анонимных. Токен не должен принадлежать конкретному
# отслеживаемому игроку — он только авторизует запрос, к его данным доступа
# не даёт (партии и так публичные).
self.app_token = os.getenv("LICHESS_APP_TOKEN")
if not self.app_token:
logger.warning("LICHESS_APP_TOKEN не задан — анонимные запросы к games/user и user/activity будут получать 404 от Lichess")
async def get_user_activity(self, username: str) -> Optional[List[Dict[str, Any]]]: async def get_user_activity(self, username: str) -> Optional[List[Dict[str, Any]]]:
""" """
@ -68,8 +78,10 @@ class LichessClient:
url = f"{self.base_url}/user/{username}/activity" url = f"{self.base_url}/user/{username}/activity"
logger.info(f"🔍 Making request to Lichess API: {url}") logger.info(f"🔍 Making request to Lichess API: {url}")
headers = {'Authorization': f'Bearer {self.app_token}'} if self.app_token else {}
# Выполняем HTTP GET запрос # Выполняем HTTP GET запрос
response = await self.client.get(url) response = await self.client.get(url, headers=headers)
logger.info(f"🔍 Lichess API response status: {response.status_code} for {username}") logger.info(f"🔍 Lichess API response status: {response.status_code} for {username}")
response.raise_for_status() # Проверяем статус ответа response.raise_for_status() # Проверяем статус ответа
@ -135,6 +147,8 @@ class LichessClient:
headers = { headers = {
'Accept': 'application/x-ndjson' # Запрашиваем NDJSON формат 'Accept': 'application/x-ndjson' # Запрашиваем NDJSON формат
} }
if self.app_token:
headers['Authorization'] = f'Bearer {self.app_token}'
logger.info(f"Запрос игр для {username} с {since_ms} по {until_ms}") logger.info(f"Запрос игр для {username} с {since_ms} по {until_ms}")

View file

@ -4,6 +4,8 @@ services:
container_name: lichess-api container_name: lichess-api
ports: ports:
- "8002:8000" - "8002:8000"
env_file:
- .env
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
restart: always restart: always

View file

@ -6,6 +6,8 @@ services:
container_name: lichess-api container_name: lichess-api
ports: ports:
- "8002:8000" - "8002:8000"
env_file:
- .env
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
volumes: volumes: