From 91d87ed7ef14d269e11d2167a9cb1afda4ab7ce7 Mon Sep 17 00:00:00 2001 From: amd64fox <62529699+amd64fox@users.noreply.github.com> Date: Mon, 27 Oct 2025 21:16:35 +0300 Subject: [PATCH] Delete LoaderSpot.py --- LoaderSpot.py | 309 -------------------------------------------------- 1 file changed, 309 deletions(-) delete mode 100644 LoaderSpot.py diff --git a/LoaderSpot.py b/LoaderSpot.py deleted file mode 100644 index 04e2c78..0000000 --- a/LoaderSpot.py +++ /dev/null @@ -1,309 +0,0 @@ -import re -import json -import aiohttp -import asyncio -from tqdm import tqdm -from typing import Optional, Tuple, List, Dict -from dataclasses import dataclass -from enum import Enum - - -class Platform(Enum): - WIN_X86 = "Win-x86" - WIN_X64 = "Win-x64" - WIN_ARM64 = "Win-arm64" - MACOS_INTEL = "macOS-intel" - MACOS_ARM64 = "macOS-arm64" - - -@dataclass -class SpotifyVersion: - version: str - start_number: int - end_number: int - - -def extract_base_version(version: str) -> str: - parts = version.split('.') - if len(parts) >= 3: - return f"{parts[0]}.{parts[1]}.{parts[2]}" - return version - - -def should_use_win_x86(version: str) -> bool: - base_version = extract_base_version(version) - try: - # Split by dot and compare parts - parts = base_version.split('.') - version_tuple = (int(parts[0]), int(parts[1]), int(parts[2])) - - # Compare with version 1.2.53 using tuple comparison - return version_tuple <= (1, 2, 53) - except (ValueError, IndexError): - # If there's any error in parsing, default to include - return True - - -class UrlGenerator: - BASE_URL = "https://upgrade.scdn.co/upgrade/client/" - PLATFORM_PATHS = { - Platform.WIN_X86: "win32-x86/spotify_installer-{version}-{number}.exe", - Platform.WIN_X64: "win32-x86_64/spotify_installer-{version}-{number}.exe", - Platform.WIN_ARM64: "win32-arm64/spotify_installer-{version}-{number}.exe", - Platform.MACOS_INTEL: "osx-x86_64/spotify-autoupdate-{version}-{number}.tbz", - Platform.MACOS_ARM64: "osx-arm64/spotify-autoupdate-{version}-{number}.tbz", - } - - @staticmethod - def generate_url(platform: Platform, version: str, number: int) -> str: - return f"{UrlGenerator.BASE_URL}{UrlGenerator.PLATFORM_PATHS[platform].format(version=version, number=number)}" - - -async def check_url( - session: aiohttp.ClientSession, url: str, platform: Platform -) -> Optional[Tuple[str, Platform]]: - try: - async with session.head(url) as response: - if response.status == 200: - return url, platform - except aiohttp.ClientError: - pass - return None - - -async def fetch_versions_json(session: aiohttp.ClientSession) -> dict: - async with session.get( - "https://raw.githubusercontent.com/amd64fox/LoaderSpot/refs/heads/main/versions.json" - ) as response: - if response.status == 200: - try: - return json.loads(await response.text()) - except json.JSONDecodeError: - return {} - return {} - - -async def submit_to_google_form(session: aiohttp.ClientSession, version: str, comment: str = "from LoaderSpot") -> None: - form_url = "https://docs.google.com/forms/u/0/d/e/1FAIpQLSdqIxSjqt2PcjBlQzhvwqc4QckfWuq5qqWsrdpoTidQHsPGpw/formResponse" - data = {"entry.1104502920": version, "entry.1319854718": comment} - - try: - await session.post(form_url, data=data) - except Exception: - pass - - -async def check_version_and_submit(session: aiohttp.ClientSession, version: str) -> None: - try: - versions_json = await fetch_versions_json(session) - version_exists = any( - ver_data.get("fullversion") == version - for ver_data in versions_json.values() - ) - - if not version_exists: - await submit_to_google_form(session, version) - - except Exception: - pass - - -def validate_version(version: str) -> bool: - return bool(re.match(r"^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$", version)) - - -def validate_number(number: str) -> bool: - return number.isdigit() - - -def validate_range(start: int, end: int) -> bool: - return end >= start and (end - start) <= 20000 - - -def get_valid_input(prompt: str, validator: callable, error_message: str) -> str: - while True: - value = input(prompt) - if validator(value): - return value - print(error_message) - - -def get_version_input() -> str: - while True: - version = input("Spotify version, for example 1.1.68.632.g2b11de83: ") - if validate_version(version): - return version - print("Invalid version format") - - -def calculate_total_urls( - start_number: int, end_number: int, selected_platforms: List[Platform] -) -> int: - return (end_number - start_number + 1) * len(selected_platforms) - - -async def search_installers( - session: aiohttp.ClientSession, version_info: SpotifyVersion, selected_platforms: List[Platform] -) -> Dict[Platform, List[str]]: - tasks = [] - total_urls = calculate_total_urls( - version_info.start_number, version_info.end_number, selected_platforms - ) - - for platform in selected_platforms: - for number in range(version_info.start_number, version_info.end_number + 1): - url = UrlGenerator.generate_url(platform, version_info.version, number) - tasks.append(check_url(session, url, platform)) - - results: Dict[Platform, List[str]] = {platform: [] for platform in Platform} - - avg_speed = [0, 0] - - custom_bar_format = "{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}" - - with tqdm(total=total_urls, desc="Checking URLs", - bar_format=custom_bar_format + ", ? urls/sec]") as pbar: - - def update_speed(rate): - nonlocal custom_bar_format - speed_str = f", {int(rate)} urls/sec]" - pbar.bar_format = custom_bar_format + speed_str - - for task in asyncio.as_completed(tasks): - if result := await task: - url, platform = result - results[platform].append(url) - - pbar.update(1) - - rate = pbar.format_dict.get('rate', 0) - if rate > 0: - avg_speed[0] += rate - avg_speed[1] += 1 - update_speed(rate) - - if avg_speed[1] > 0: - avg_rate = avg_speed[0] / avg_speed[1] - final_speed_str = f", {int(avg_rate)} urls/sec (avg)]" - pbar.bar_format = custom_bar_format + final_speed_str - pbar.refresh() - - return results - - -def display_results(results: Dict[Platform, List[str]]) -> None: - found_any = False - for platform in Platform: - if urls := results[platform]: - found_any = True - print(f"\n{platform.value}:") - for url in urls: - print(url) - - if not found_any: - print("\nNothing found, consider increasing the search range") - - -def get_platform_choices(version_spoti: str = "") -> List[Platform]: - print("\nSelect the link type for the search:") - - # Filter out WIN_X86 platform for versions > 1.2.53 - available_platforms = list(Platform) - if version_spoti and not should_use_win_x86(version_spoti): - available_platforms = [p for p in Platform if p != Platform.WIN_X86] - - for i, platform in enumerate(available_platforms, 1): - print(f"[{i}] {platform.value}") - print(f"[{len(available_platforms) + 1}] All platforms") - - while True: - choices = input("Enter the number(s): ").strip().split(",") - - if not all(choice.strip().isdigit() and 1 <= int(choice.strip()) <= len(available_platforms) + 1 for choice in choices): - print(f"Invalid input. Please enter numbers between 1 and {len(available_platforms) + 1}") - continue - - if str(len(available_platforms) + 1) in choices: - return available_platforms - - selected = [] - for choice in choices: - platform_index = int(choice.strip()) - 1 - if 0 <= platform_index < len(available_platforms): - selected.append(available_platforms[platform_index]) - - if selected: - return selected - - print("Please select at least one valid platform") - - -def get_max_connections() -> int: - while True: - max_connections = input("Maximum number of concurrent connections (default 100): ").strip() - if not max_connections: - return 100 - if max_connections.isdigit() and int(max_connections) > 0: - return int(max_connections) - print("Please enter a valid positive number") - - -async def main(version_spoti: str = "") -> None: - # Increase connection limit - max_connections = get_max_connections() - connector = aiohttp.TCPConnector(limit=max_connections) - async with aiohttp.ClientSession(connector=connector) as session: - if not version_spoti: - version_spoti = get_version_input() - - version_check_task = asyncio.create_task(check_version_and_submit(session, version_spoti)) - - start_number = int( - get_valid_input( - "Start search from: ", - lambda x: validate_number(x), - "Please enter a valid number", - ) - ) - end_number = int( - get_valid_input( - "End search at: ", - lambda x: validate_number(x) and validate_range(start_number, int(x)), - f"Please enter a valid number that is at least {start_number} and no more than {start_number + 20000}", - ) - ) - - version_info = SpotifyVersion(version_spoti, start_number, end_number) - selected_platforms = get_platform_choices(version_spoti) - - print("\nSearching...\n") - results = await search_installers(session, version_info, selected_platforms) - display_results(results) - - try: - await asyncio.wait_for(version_check_task, timeout=1.0) - except asyncio.TimeoutError: - pass - - print("\nChoose an option:") - print("[1] Perform the search with a new version") - print("[2] Perform the search again with the same version") - print("[3] Exit") - - choice = input("Enter the number: ") - - if choice == "1": - print("\n") - await main() - elif choice == "2": - print("\n") - print(f"Search version: {version_spoti}") - await main(version_spoti) - elif choice == "3": - return - else: - print("Invalid choice. Exiting the program.") - - -if __name__ == "__main__": - asyncio.run(main())