diff --git a/.gitignore b/.gitignore index 45e8ef6..e2dcccf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ __pycache__/ *.egg-info/ dist/ *.json -archive/ \ No newline at end of file +archive/ +*.bplist \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0d37b83..3fb1089 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,4 +34,5 @@ Homepage = "https://git.satstack.dev/blee/beatsaber-playlist-tool" # https://setuptools.pypa.io/en/latest/userguide/entry_point.html [project.scripts] replay_ranked_ss = "saberlist.scoresaber:replay_ranked" +ranked_leaderboards_ss = "saberlist.scoresaber:ranked_leaderboards" #replay_ranked_bl = "saberlist.beatleader:replay_ranked" \ No newline at end of file diff --git a/src/saberlist/scoresaber.py b/src/saberlist/scoresaber.py index 7a2e73b..087fdad 100644 --- a/src/saberlist/scoresaber.py +++ b/src/saberlist/scoresaber.py @@ -1,7 +1,7 @@ import json import asyncio from pyscoresaber import ScoreSaberAPI, ScoreSort -import requests + def filter_and_sort_scores_by_stars(scores, min_stars=0.1, max_stars=float('inf')): # Exclude scores outside the specified star range @@ -10,6 +10,35 @@ def filter_and_sort_scores_by_stars(scores, min_stars=0.1, max_stars=float('inf' sorted_scores = sorted(filtered_scores, key=lambda x: x.leaderboard.stars) return sorted_scores +# TODO: combine with scores_to_playlist() +def leaderboards_to_playlist(leaderboards: list, playlist_title: str, playlist_author: str = "SaberList Tool") -> str: + playlist = { + "playlistTitle": playlist_title, + "playlistAuthor": playlist_author, + "songs": [] + } + + for leaderboard in leaderboards: + song_entry = { + "hash": leaderboard.song_hash, + "songName": leaderboard.song_name, + "difficulties": [ + { + "name": leaderboard.difficulty.difficulty.name.lower(), + "characteristic": leaderboard.difficulty.game_mode.name.lower() + } + ], + "levelAuthorName": leaderboard.level_author_name, + "songSubName": leaderboard.song_sub_name, + "songAuthorName": leaderboard.song_author_name + } + playlist["songs"].append(song_entry) + + playlist_json = json.dumps(playlist, indent=4) + with open(f"{playlist_title}.bplist", 'w') as file: + file.write(playlist_json) + return playlist_json + def scores_to_playlist(scores, playlist_title, playlist_author = "SaberList Tool"): playlist = { "playlistTitle": playlist_title, @@ -33,7 +62,7 @@ def scores_to_playlist(scores, playlist_title, playlist_author = "SaberList Tool playlist_json = json.dumps(playlist, indent=4) - with open(f"{playlist_title}.json", 'w') as file: + with open(f"{playlist_title}.bplist", 'w') as file: file.write(playlist_json) return playlist_json @@ -67,3 +96,59 @@ def replay_ranked(): asyncio.run(async_replay_ranked()) except Exception as e: print(f"An error occurred: {e}") + +def ranked_leaderboards(): + + default_min_stars = 6.0 + min_stars = float(input(f"Enter the minimum starlevel to include on the playlist (Default: {default_min_stars}): ") or default_min_stars) + default_max_stars = min_stars + 0.10 + max_stars = float(input(f"Enter the maximum starlevel to include on the playlist (Default: {default_max_stars}): ") or default_max_stars) + default_title = f"SS Leaderboard {min_stars}★" + playlist_title = input(f"Enter the filename for the playlist (Default: {default_title}): ") or default_title + + try: + leaderboards = asyncio.run(async_leaderboards(min_stars, max_stars)) + except Exception as e: + print(f"An error occurred: {e}") + + leaderboards_to_playlist(leaderboards, playlist_title) + +async def async_leaderboards(min_stars: float, max_stars: float): + params = { + "category": 3, # sort by scores + "sort": 1, # sort ascending + "max_star": max_stars, + "min_star": min_stars, + "qualified": 0, # False + "ranked": 1 # True + } + scoresaber = ExtendedScoreSaberAPI() + await scoresaber.start() + all_leaderboards = [] + try: + async for leaderboard_page in scoresaber.leaderboards_all(**params): + all_leaderboards.extend(leaderboard_page) + except Exception as e: + print(f"An error occurred: {e}") + finally: + await scoresaber.close() + return all_leaderboards + +from math import ceil +from typing import AsyncIterable, List + +from pyscoresaber.models import LeaderboardInfo +class ExtendedScoreSaberAPI(ScoreSaberAPI): + async def leaderboards_all(self, **params) -> AsyncIterable[List[LeaderboardInfo]]: + page = 1 + max_page = -1 + while page < max_page or max_page == -1: + try: + leaderboards = await self.leaderboards(**params, page=page) + if max_page == -1: + max_page = ceil(leaderboards.metadata.total / leaderboards.metadata.items_per_page) + yield leaderboards.leaderboards + page += 1 + except Exception as e: + print(f"An error occurred while fetching page {page}: {e}") + break