Update LoaderSpot.py
- added sending unknown versions to the form - minor corrections
This commit is contained in:
parent
61b9865e6f
commit
7ef4fabd64
1 changed files with 201 additions and 123 deletions
324
LoaderSpot.py
324
LoaderSpot.py
|
|
@ -1,152 +1,229 @@
|
|||
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
|
||||
import threading
|
||||
|
||||
|
||||
async def check_url(session, url, platform_name):
|
||||
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
|
||||
|
||||
|
||||
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.get(url) as response:
|
||||
if response.status == 200:
|
||||
if platform_name:
|
||||
return response.url, platform_name
|
||||
else:
|
||||
return response.url
|
||||
return str(response.url), platform
|
||||
except aiohttp.ClientError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
async def main(version_spoti=""):
|
||||
if not version_spoti:
|
||||
while not re.match(r"^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$", version_spoti):
|
||||
version_spoti = input("Spotify version, for example 1.1.68.632.g2b11de83: ")
|
||||
async def fetch_versions_json() -> dict:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
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 {}
|
||||
|
||||
start_number = ""
|
||||
while not start_number.isdigit() or not 0 <= int(start_number) <= 20000:
|
||||
start_number = input("Start search from: ")
|
||||
|
||||
before_enter = ""
|
||||
while not before_enter.isdigit() or not 1 <= int(before_enter) <= 20000:
|
||||
before_enter = input("End search at: ")
|
||||
|
||||
numbers = int(start_number)
|
||||
find_url = []
|
||||
|
||||
win32 = "https://upgrade.scdn.co/upgrade/client/win32-x86/spotify_installer-{version_spoti}-{numbers}.exe"
|
||||
win64 = "https://upgrade.scdn.co/upgrade/client/win32-x86_64/spotify_installer-{version_spoti}-{numbers}.exe"
|
||||
win_arm64 = "https://upgrade.scdn.co/upgrade/client/win32-arm64/spotify_installer-{version_spoti}-{numbers}.exe"
|
||||
osx = "https://upgrade.scdn.co/upgrade/client/osx-x86_64/spotify-autoupdate-{version_spoti}-{numbers}.tbz"
|
||||
osx_arm64 = "https://upgrade.scdn.co/upgrade/client/osx-arm64/spotify-autoupdate-{version_spoti}-{numbers}.tbz"
|
||||
|
||||
print("\nSelect the link type for the search:")
|
||||
print("[1] WIN32")
|
||||
print("[2] WIN64")
|
||||
print("[3] WIN-ARM64")
|
||||
print("[4] OSX")
|
||||
print("[5] OSX-ARM64")
|
||||
print("[6] All platforms")
|
||||
|
||||
choices = []
|
||||
while not choices:
|
||||
choice = input("Enter the number(s): ")
|
||||
choice_list = choice.split(",")
|
||||
for c in choice_list:
|
||||
c = c.strip()
|
||||
if c == "1":
|
||||
choices.append("1")
|
||||
elif c == "2":
|
||||
choices.append("2")
|
||||
elif c == "3":
|
||||
choices.append("3")
|
||||
elif c == "4":
|
||||
choices.append("4")
|
||||
elif c == "5":
|
||||
choices.append("5")
|
||||
elif c == "6":
|
||||
choices = ["1", "2", "3", "4", "5"]
|
||||
break
|
||||
else:
|
||||
print("Value should be in the range from 1 to 6")
|
||||
|
||||
print("\nSearching...\n")
|
||||
async def submit_to_google_form(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}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
await session.post(form_url, data=data)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def check_version_and_submit(version: str) -> None:
|
||||
try:
|
||||
versions_json = await fetch_versions_json()
|
||||
version_exists = any(
|
||||
ver_data.get("fullversion") == version
|
||||
for ver_data in versions_json.values()
|
||||
)
|
||||
|
||||
if not version_exists:
|
||||
await submit_to_google_form(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, min_val: int, max_val: int) -> bool:
|
||||
return number.isdigit() and min_val <= int(number) <= max_val
|
||||
|
||||
|
||||
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(
|
||||
version_info: SpotifyVersion, selected_platforms: List[Platform]
|
||||
) -> Dict[Platform, List[str]]:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
tasks = []
|
||||
total_urls = calculate_total_urls(
|
||||
version_info.start_number, version_info.end_number, selected_platforms
|
||||
)
|
||||
|
||||
if "6" in choices: # Updated condition to include option 6
|
||||
url_templates = [win32, win64, win_arm64, osx, osx_arm64]
|
||||
platform_names = ["WIN32", "WIN64", "WIN-ARM64", "OSX", "OSX-ARM64"]
|
||||
for url_template, platform_name in zip(url_templates, platform_names):
|
||||
numbers = int(start_number)
|
||||
while numbers <= int(before_enter):
|
||||
url = url_template.format(
|
||||
version_spoti=version_spoti, numbers=numbers
|
||||
)
|
||||
tasks.append(check_url(session, url, platform_name))
|
||||
numbers += 1
|
||||
else:
|
||||
for choice in choices:
|
||||
if choice == "1":
|
||||
url_template = win32
|
||||
platform_name = "WIN32"
|
||||
elif choice == "2":
|
||||
url_template = win64
|
||||
platform_name = "WIN64"
|
||||
elif choice == "3":
|
||||
url_template = win_arm64
|
||||
platform_name = "WIN-ARM64"
|
||||
elif choice == "4":
|
||||
url_template = osx
|
||||
platform_name = "OSX"
|
||||
elif choice == "5":
|
||||
url_template = osx_arm64
|
||||
platform_name = "OSX-ARM64"
|
||||
numbers = int(start_number)
|
||||
while numbers <= int(before_enter):
|
||||
url = url_template.format(version_spoti=version_spoti, numbers=numbers)
|
||||
tasks.append(check_url(session, url, platform_name))
|
||||
numbers += 1
|
||||
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 = []
|
||||
results: Dict[Platform, List[str]] = {platform: [] for platform in Platform}
|
||||
|
||||
with tqdm(total=len(tasks)) as pbar:
|
||||
with tqdm(total=total_urls) as pbar:
|
||||
for task in asyncio.as_completed(tasks):
|
||||
result = await task
|
||||
if result is not None:
|
||||
find_url.append(result)
|
||||
if result := await task:
|
||||
url, platform = result
|
||||
results[platform].append(url)
|
||||
pbar.update(1)
|
||||
|
||||
print("\nSearch completed:\n")
|
||||
if find_url:
|
||||
platform_urls = {}
|
||||
for item in find_url:
|
||||
if isinstance(item, tuple):
|
||||
url, platform_name = item
|
||||
if platform_name not in platform_urls:
|
||||
platform_urls[platform_name] = []
|
||||
platform_urls[platform_name].append(url)
|
||||
else:
|
||||
if "Unknown" not in platform_urls:
|
||||
platform_urls["Unknown"] = []
|
||||
platform_urls["Unknown"].append(item)
|
||||
return results
|
||||
|
||||
sorting_order = ["WIN32", "WIN64", "WIN-ARM64", "OSX", "OSX-ARM64"]
|
||||
|
||||
for platform_name in sorting_order:
|
||||
if platform_name in platform_urls:
|
||||
print(f"{platform_name}:")
|
||||
for url in platform_urls[platform_name]:
|
||||
print(url)
|
||||
print()
|
||||
else:
|
||||
print("Nothing found, consider increasing the search range\n")
|
||||
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)
|
||||
|
||||
choice = input("Choose an option:\n"
|
||||
"[1] Perform the search with a new version\n"
|
||||
"[2] Perform the search again with the same version\n"
|
||||
"[3] Exit\n"
|
||||
"Enter the number: ")
|
||||
if not found_any:
|
||||
print("\nNothing found, consider increasing the search range")
|
||||
|
||||
|
||||
def get_platform_choices() -> List[Platform]:
|
||||
print("\nSelect the link type for the search:")
|
||||
for i, platform in enumerate(Platform, 1):
|
||||
print(f"[{i}] {platform.value}")
|
||||
print("[6] All platforms")
|
||||
|
||||
while True:
|
||||
choices = input("Enter the number(s): ").strip().split(",")
|
||||
|
||||
if not all(choice.strip() in "123456" for choice in choices):
|
||||
print("Invalid input. Please enter numbers between 1 and 6")
|
||||
continue
|
||||
|
||||
if "6" in choices:
|
||||
return list(Platform)
|
||||
|
||||
selected = []
|
||||
for choice in choices:
|
||||
platform_index = int(choice.strip()) - 1
|
||||
if 0 <= platform_index < len(Platform):
|
||||
selected.append(list(Platform)[platform_index])
|
||||
|
||||
if selected:
|
||||
return selected
|
||||
|
||||
print("Please select at least one valid platform")
|
||||
|
||||
|
||||
async def main(version_spoti: str = "") -> None:
|
||||
if not version_spoti:
|
||||
version_spoti = get_version_input()
|
||||
|
||||
version_check_thread = threading.Thread(
|
||||
target=lambda: asyncio.run(check_version_and_submit(version_spoti)), daemon=True
|
||||
)
|
||||
version_check_thread.start()
|
||||
|
||||
start_number = int(
|
||||
get_valid_input(
|
||||
"Start search from: ",
|
||||
lambda x: validate_number(x, 0, 20000),
|
||||
"Number should be between 0 and 20000",
|
||||
)
|
||||
)
|
||||
end_number = int(
|
||||
get_valid_input(
|
||||
"End search at: ",
|
||||
lambda x: validate_number(x, start_number, 20000),
|
||||
f"Number should be between {start_number} and 20000",
|
||||
)
|
||||
)
|
||||
|
||||
version_info = SpotifyVersion(version_spoti, start_number, end_number)
|
||||
selected_platforms = get_platform_choices()
|
||||
|
||||
print("\nSearching...\n")
|
||||
results = await search_installers(version_info, selected_platforms)
|
||||
display_results(results)
|
||||
|
||||
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")
|
||||
|
|
@ -158,7 +235,8 @@ async def main(version_spoti=""):
|
|||
elif choice == "3":
|
||||
return
|
||||
else:
|
||||
print("Invalid choice. Exiting the code.")
|
||||
print("Invalid choice. Exiting the program.")
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
|
|||
Loading…
Reference in a new issue