Spaces:
Sleeping
Sleeping
Commit
·
a25bdd2
1
Parent(s):
3d08d61
new fetch lichess puzzle
Browse files- core/chess_api.py +84 -30
core/chess_api.py
CHANGED
|
@@ -54,41 +54,95 @@ def get_user_games_from_chess_com(username):
|
|
| 54 |
|
| 55 |
|
| 56 |
def fetch_lichess_puzzles(error_types, user_rating=1500, count=5):
|
| 57 |
-
"""
|
| 58 |
-
Fetches relevant Lichess puzzles based on error types and user rating.
|
| 59 |
-
error_types: list of strings (e.g. ['hangingPiece', 'blunder'])
|
| 60 |
-
user_rating: int, target puzzle rating
|
| 61 |
-
count: int, number of puzzles to fetch
|
| 62 |
-
"""
|
| 63 |
puzzles = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
try:
|
| 65 |
-
|
| 66 |
-
# We'll use the public puzzle endpoint and filter locally
|
| 67 |
-
url = f"https://lichess.org/api/puzzle"
|
| 68 |
-
headers = {'Accept': 'application/x-ndjson'}
|
| 69 |
-
response = requests.get(url, headers=headers, timeout=10)
|
| 70 |
-
if response.status_code != 200:
|
| 71 |
-
logger.error("Failed to fetch puzzles from Lichess")
|
| 72 |
-
return puzzles
|
| 73 |
-
|
| 74 |
-
lines = response.text.strip().split('\n')
|
| 75 |
-
for line in lines:
|
| 76 |
if len(puzzles) >= count:
|
| 77 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
try:
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
except Exception as e:
|
| 93 |
logger.error(f"Error fetching puzzles: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
return puzzles
|
|
|
|
| 54 |
|
| 55 |
|
| 56 |
def fetch_lichess_puzzles(error_types, user_rating=1500, count=5):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
puzzles = []
|
| 58 |
+
|
| 59 |
+
# Map Uzbek error categories to Lichess puzzle themes
|
| 60 |
+
theme_mapping = {
|
| 61 |
+
"Qo'pol xatolar": ["mate", "mateIn1", "mateIn2", "hangingPiece"],
|
| 62 |
+
"Kichik xatolar": ["advantage", "crushing", "attackingF2F7"],
|
| 63 |
+
"Himoyasiz qoldirish": ["hangingPiece", "pin", "skewer", "discoveredAttack"],
|
| 64 |
+
"Debyut xatolari": ["opening", "middlegame"],
|
| 65 |
+
"O'rta o'yin xatolari": ["middlegame", "fork", "defensiveMove"],
|
| 66 |
+
"Endshpil xatolari": ["endgame", "advancedPawn", "promotion"]
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
# Convert error types to Lichess themes
|
| 70 |
+
lichess_themes = []
|
| 71 |
+
for error_type in error_types:
|
| 72 |
+
if error_type in theme_mapping:
|
| 73 |
+
lichess_themes.extend(theme_mapping[error_type])
|
| 74 |
+
|
| 75 |
+
lichess_themes = list(set(lichess_themes))
|
| 76 |
+
|
| 77 |
+
if not lichess_themes:
|
| 78 |
+
lichess_themes = ["tactics"]
|
| 79 |
+
|
| 80 |
try:
|
| 81 |
+
for theme in lichess_themes[:3]: # Limit to top 3 themes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
if len(puzzles) >= count:
|
| 83 |
break
|
| 84 |
+
|
| 85 |
+
url = f"https://lichess.org/api/puzzle/daily"
|
| 86 |
+
headers = {'Accept': 'application/json'}
|
| 87 |
+
|
| 88 |
+
params = {
|
| 89 |
+
'max': count,
|
| 90 |
+
'theme': theme
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
try:
|
| 94 |
+
response = requests.get(
|
| 95 |
+
"https://lichess.org/api/puzzle/activity",
|
| 96 |
+
headers=headers,
|
| 97 |
+
timeout=10
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
if response.status_code == 200:
|
| 101 |
+
data = response.json()
|
| 102 |
+
|
| 103 |
+
# Filter by rating range
|
| 104 |
+
for item in data.get('puzzles', []):
|
| 105 |
+
if len(puzzles) >= count:
|
| 106 |
+
break
|
| 107 |
+
|
| 108 |
+
puzzle_rating = item.get('puzzle', {}).get('rating', 1500)
|
| 109 |
+
|
| 110 |
+
if abs(puzzle_rating - user_rating) <= 300:
|
| 111 |
+
puzzle_id = item.get('puzzle', {}).get('id')
|
| 112 |
+
puzzle_themes = item.get('puzzle', {}).get('themes', [])
|
| 113 |
+
|
| 114 |
+
puzzles.append({
|
| 115 |
+
'id': puzzle_id,
|
| 116 |
+
'url': f"https://lichess.org/training/{puzzle_id}",
|
| 117 |
+
'theme': theme.title(),
|
| 118 |
+
'rating': puzzle_rating,
|
| 119 |
+
'themes': puzzle_themes
|
| 120 |
+
})
|
| 121 |
+
except:
|
| 122 |
+
pass
|
| 123 |
+
|
| 124 |
+
# If no puzzles found, provide generic training links
|
| 125 |
+
if not puzzles:
|
| 126 |
+
for i, theme in enumerate(lichess_themes[:count]):
|
| 127 |
+
puzzles.append({
|
| 128 |
+
'id': f'theme_{i}',
|
| 129 |
+
'url': f"https://lichess.org/training/{theme}",
|
| 130 |
+
'theme': theme.title(),
|
| 131 |
+
'rating': user_rating,
|
| 132 |
+
'themes': [theme]
|
| 133 |
+
})
|
| 134 |
+
|
| 135 |
except Exception as e:
|
| 136 |
logger.error(f"Error fetching puzzles: {str(e)}")
|
| 137 |
+
|
| 138 |
+
for i, error_type in enumerate(error_types[:count]):
|
| 139 |
+
theme = theme_mapping.get(error_type, ["tactics"])[0]
|
| 140 |
+
puzzles.append({
|
| 141 |
+
'id': f'fallback_{i}',
|
| 142 |
+
'url': f"https://lichess.org/training/{theme}",
|
| 143 |
+
'theme': error_type,
|
| 144 |
+
'rating': user_rating,
|
| 145 |
+
'themes': [theme]
|
| 146 |
+
})
|
| 147 |
+
|
| 148 |
return puzzles
|