Created
May 7, 2026 07:27
-
-
Save ShayBox/699bbd9e4afb12204952747fd017fc90 to your computer and use it in GitHub Desktop.
Osu Most Played Downloader | Some vibe coded shit that abuses multiple APIs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import requests | |
| import time | |
| import os | |
| import re | |
| from tqdm import tqdm | |
| # Try APIs in order. Use {} for beatmapset_id formatting. | |
| DOWNLOAD_APIS = [ | |
| "https://api.nerinyan.moe/d/{}", | |
| "https://catboy.best/d/{}", | |
| ] | |
| def sanitize_filename(name: str) -> str: | |
| # Remove characters invalid in Windows filenames: \ / : * ? " < > | | |
| return re.sub(r'[\\/*?:"<>|]', '', name) | |
| def prompt_yes_no(prompt: str, default: str = "n") -> bool: | |
| while True: | |
| choice = input(f"{prompt} (y/n) [default: {default}]: ").strip().lower() | |
| if choice == '': | |
| choice = default | |
| if choice in ['y', 'n']: | |
| return choice == 'y' | |
| print("Please enter 'y' or 'n'.") | |
| def build_params(options): | |
| # API query parameters | |
| params = {} | |
| for key in ['nohitsound', 'nostoryboard', 'nobg', 'novideo']: | |
| params[key] = 'true' if options.get(key, False) else 'false' | |
| return params | |
| def download_from_url(url, filename, headers, params): | |
| try: | |
| with requests.get(url, headers=headers, params=params, stream=True, timeout=30) as response: | |
| if response.status_code == 404: | |
| return False, "404 Not Found" | |
| if response.status_code != 200: | |
| return False, f"HTTP {response.status_code}" | |
| total_size = int(response.headers.get('content-length', 0)) | |
| with open(filename, "wb") as f, tqdm( | |
| total=total_size, | |
| unit='B', | |
| unit_scale=True, | |
| unit_divisor=1024, | |
| desc=filename, | |
| ascii=True | |
| ) as bar: | |
| for chunk in response.iter_content(chunk_size=8192): | |
| if chunk: | |
| f.write(chunk) | |
| bar.update(len(chunk)) | |
| return True, None | |
| except Exception as e: | |
| return False, str(e) | |
| def download_single_beatmap(beatmapset_id, song_title, options): | |
| headers = { | |
| "Accept": "application/x-osu-beatmap-archive", | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " | |
| "(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 OPR/108.0.0.0", | |
| } | |
| params = build_params(options) | |
| filename = f"{beatmapset_id} - {sanitize_filename(song_title)}.osz" | |
| # Skip if file already exists | |
| if os.path.exists(filename): | |
| print(f"Skipping {filename} (already exists)") | |
| return True | |
| last_error = None | |
| for api_template in DOWNLOAD_APIS: | |
| url = api_template.format(beatmapset_id) | |
| print(f"Trying: {url}") | |
| success, error = download_from_url(url, filename, headers, params) | |
| if success: | |
| print(f"Downloaded: {filename}") | |
| return True | |
| last_error = error | |
| print(f"Failed with {url}: {error}") | |
| # If this API 404s, try the next one | |
| # If it fails for another reason, still try the next one | |
| time.sleep(1) | |
| print(f"All APIs failed for {beatmapset_id} - {song_title}: {last_error}") | |
| return False | |
| def download_beatmaps(beatmaps: list[dict], options): | |
| failed_downloads = [] | |
| total = len(beatmaps) | |
| for i, beatmap in enumerate(beatmaps, 1): | |
| beatmapset = beatmap.get("beatmapset") or beatmap.get("beatmap") | |
| if not beatmapset: | |
| print(f"Skipping beatmap with missing beatmapset data at index {i}") | |
| failed_downloads.append(None) | |
| continue | |
| beatmapset_id = beatmapset.get("id") or beatmapset.get("beatmapset_id") | |
| song_title = beatmapset.get("title", "Unknown Title") | |
| if not beatmapset_id: | |
| print(f"Skipping beatmap with missing id at index {i}") | |
| failed_downloads.append(None) | |
| continue | |
| print(f"Downloading ({i}/{total}): {beatmapset_id} - {song_title}") | |
| success = download_single_beatmap(beatmapset_id, song_title, options) | |
| if not success: | |
| failed_downloads.append(beatmapset_id) | |
| time.sleep(1) # Rate limit delay | |
| if failed_downloads: | |
| with open("failed_downloads.txt", "w") as f: | |
| for fail in failed_downloads: | |
| if fail: | |
| f.write(str(fail) + "\n") | |
| print("Failed downloads saved to failed_downloads.txt") | |
| def retrieve_most_played_beatmaps(user_id: str, limit: int, offset: int = 0) -> list[dict]: | |
| beatmaps = [] | |
| per_page = 10 | |
| for current_offset in range(offset, offset + limit, per_page): | |
| try: | |
| url = f"https://osu.ppy.sh/users/{user_id}/beatmapsets/most_played?limit={per_page}&offset={current_offset}" | |
| response = requests.get(url, timeout=30) | |
| if response.status_code == 429: | |
| print(f"Rate limited at offset {current_offset}. Waiting 10 seconds...") | |
| time.sleep(10) | |
| continue | |
| elif response.status_code != 200: | |
| print(f"Skipping offset {current_offset} (status code: {response.status_code})") | |
| continue | |
| data = response.json() | |
| if not isinstance(data, list): | |
| print(f"Unexpected data structure at offset {current_offset}, skipping") | |
| continue | |
| beatmaps.extend(data) | |
| print(f"Fetched {len(data)} beatmaps at offset {current_offset}") | |
| time.sleep(1) | |
| except Exception as e: | |
| print(f"Error fetching beatmaps at offset {current_offset}: {e}") | |
| return beatmaps | |
| def main(): | |
| print("osu! Beatmap Downloader by API") | |
| user_id = input("Enter your osu! user ID: ").strip() | |
| while not user_id.isdigit(): | |
| user_id = input("Invalid input. Please enter numeric osu! user ID: ").strip() | |
| try: | |
| limit = int(input("How many beatmaps to download? (e.g. 100): ").strip()) | |
| except: | |
| limit = 10 | |
| print("Invalid input. Defaulting to 10 beatmaps.") | |
| try: | |
| offset = int(input("Start from offset? (0 for beginning): ").strip()) | |
| except: | |
| offset = 0 | |
| print("Invalid input. Starting from offset 0.") | |
| print("\nSelect download options (y/n):") | |
| options = {} | |
| options['nohitsound'] = prompt_yes_no("NoHitsound", "n") | |
| options['nostoryboard'] = prompt_yes_no("NoStoryboard", "n") | |
| options['nobg'] = prompt_yes_no("noBg", "n") | |
| options['novideo'] = prompt_yes_no("noVideo", "y") | |
| beatmaps = retrieve_most_played_beatmaps(user_id, limit, offset) | |
| if not beatmaps: | |
| print("No beatmaps found. Exiting.") | |
| return | |
| download_beatmaps(beatmaps, options) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment