Update LoaderSpot.py

- added sending unknown versions to the form
- minor corrections
This commit is contained in:
amd64fox 2024-11-09 13:58:30 +03:00 committed by GitHub
parent 61b9865e6f
commit 7ef4fabd64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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())