Create msdls_v3.py
This commit is contained in:
parent
64d9557dcd
commit
1f9b3493c0
1 changed files with 108 additions and 0 deletions
108
msdls_v3.py
Normal file
108
msdls_v3.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
import time
|
||||
import requests
|
||||
|
||||
# New v3 JSON Endpoints
|
||||
BASE_URL = "https://www.microsoft.com/software-download-connector/api"
|
||||
PROFILE = "606624d44113"
|
||||
LOCALE = "en-US"
|
||||
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
|
||||
def setup_session():
|
||||
"""Initializes a session with Microsoft tracking servers."""
|
||||
session_id = str(uuid.uuid4())
|
||||
return session_id
|
||||
|
||||
def get_product(product_id, session_id):
|
||||
"""Fetches product/SKU info for a given ID."""
|
||||
url = f"{BASE_URL}/getskuinformationbyproductedition"
|
||||
params = {
|
||||
"profile": PROFILE,
|
||||
"productEditionId": product_id,
|
||||
"SKU": "undefined",
|
||||
"friendlyFileName": "undefined",
|
||||
"Locale": LOCALE,
|
||||
"sessionID": session_id
|
||||
}
|
||||
headers = {
|
||||
"User-Agent": UA,
|
||||
"Accept": "application/json",
|
||||
"Referer": "https://www.microsoft.com/en-us/software-download/windows11"
|
||||
}
|
||||
|
||||
try:
|
||||
r = requests.get(url, params=params, headers=headers, timeout=10)
|
||||
if not r.ok: return None
|
||||
|
||||
# Handle Microsoft's double-encoded JSON
|
||||
raw_text = r.text
|
||||
try:
|
||||
# First pass: decode the wrapper JSON
|
||||
data = json.loads(raw_text)
|
||||
# Second pass: if the value is a string, it's the real JSON
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
return data
|
||||
except:
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error(f"Error fetching ID {product_id}: {e}")
|
||||
return None
|
||||
|
||||
def scan_id(product_id):
|
||||
"""Checks if a product ID is valid and active."""
|
||||
session_id = setup_session()
|
||||
data = get_product(product_id, session_id)
|
||||
|
||||
if not data or "Skus" not in data or len(data["Skus"]) == 0:
|
||||
return None
|
||||
|
||||
if "Errors" in data and data["Errors"] and len(data["Errors"]) > 0:
|
||||
return None
|
||||
|
||||
# MS usually puts the release name in the first SKU
|
||||
skus = data.get("Skus", [])
|
||||
if skus:
|
||||
# Check multiple possible name keys
|
||||
s = skus[0]
|
||||
# ReleaseName is often "Windows 10 Version 22H2 (Updated Oct 2025)"
|
||||
name = s.get("EditionName") or s.get("ReleaseName") or s.get("FriendlyName")
|
||||
if name:
|
||||
return name
|
||||
|
||||
return f"Windows Product {product_id}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Scan for new Windows Product IDs (v3 JSON API)")
|
||||
parser.add_argument("--first", required=True, type=int, help="First ID to check")
|
||||
parser.add_argument("--last", required=True, type=int, help="Last ID to check")
|
||||
parser.add_argument("--write", help="Output JSON file")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
||||
products = {}
|
||||
|
||||
logging.info(f"Scanning range {args.first} to {args.last}...")
|
||||
|
||||
for i in range(args.first, args.last + 1):
|
||||
name = scan_id(i)
|
||||
if name:
|
||||
logging.info(f"✅ FOUND: [{i}] {name}")
|
||||
products[str(i)] = name
|
||||
else:
|
||||
# Only log skips for very small ranges to keep output clean
|
||||
if (args.last - args.first) < 20:
|
||||
logging.info(f"❌ SKIP: [{i}] Not found")
|
||||
|
||||
# Be gentle to the API
|
||||
time.sleep(0.5)
|
||||
|
||||
if args.write:
|
||||
with open(args.write, 'w', encoding='utf-8') as f:
|
||||
json.dump(products, f, indent=4)
|
||||
logging.info(f"\nSaved {len(products)} products to {args.write}")
|
||||
|
||||
logging.info("Done.")
|
||||
Loading…
Add table
Reference in a new issue