diff --git a/backend/main.go b/backend/main.go index 39955b7..a17b2b6 100644 --- a/backend/main.go +++ b/backend/main.go @@ -53,7 +53,7 @@ func getReferer(productId string) string { // Replicate Fido session tracking func setupSession() (string, error) { sessionID := uuid.New().String() - client := &http.Client{Timeout: 3 * time.Second} + client := &http.Client{Timeout: 10 * time.Second} // Step 1: Register session req1, _ := http.NewRequest("GET", fmt.Sprintf("https://vlscppe.microsoft.com/tags?org_id=%s&session_id=%s", ORG_ID, sessionID), nil) diff --git a/frontend/public/data/products.json b/frontend/public/data/products.json index 9d929d8..fd7a303 100644 --- a/frontend/public/data/products.json +++ b/frontend/public/data/products.json @@ -1,110 +1,207 @@ { - "48": { - "name": "Windows 8.1 Single Language (9600.17415)", - "badge": "LEGACY", - "archs": ["x64", "x86"], - "related": ["52"] - }, - "52": { - "name": "Windows 8.1 (9600.17415)", - "badge": "LEGACY", - "archs": ["x64", "x86"], - "related": ["48", "2618"] - }, - "2378": { - "name": "Windows 10 22H2 Home China (19045.2006)", - "badge": "EOL SOON", - "archs": ["x64"], - "related": ["2618"] - }, - "2618": { - "name": "Windows 10 22H2 v1 (19045.2965)", - "badge": "EOL SOON", - "archs": ["x64", "x86"], - "related": ["3113", "3262", "52"] - }, - "3113": { - "name": "Windows 11 24H2 (26100.1742)", - "badge": "STABLE", - "archs": ["x64"], - "related": ["3262", "3131", "2618"] - }, - "3114": { - "name": "Windows 11 24H2 Home China (26100.1742)", - "badge": "STABLE", - "archs": ["x64"], - "related": ["3113", "3115"] - }, - "3115": { - "name": "Windows 11 24H2 Pro China (26100.1742)", - "badge": "STABLE", - "archs": ["x64"], - "related": ["3113", "3114"] - }, - "3131": { - "name": "Windows 11 Arm64 24H2 (26100.1742)", - "badge": "STABLE", - "archs": ["ARM64"], - "related": ["3265", "3113"] - }, - "3132": { - "name": "Windows 11 Arm64 24H2 Home China (26100.1742)", - "badge": "STABLE", - "archs": ["ARM64"], - "related": ["3131", "3133"] - }, - "3133": { - "name": "Windows 11 Arm64 24H2 Pro China (26100.1742)", - "badge": "STABLE", - "archs": ["ARM64"], - "related": ["3131", "3132"] - }, - "3262": { - "name": "Windows 11 25H2 (26200.6584)", - "badge": "LATEST", - "archs": ["x64"], - "related": ["3265", "3321", "3113"] - }, - "3263": { - "name": "Windows 11 25H2 Home China (26200.6584)", - "badge": "LATEST", - "archs": ["x64"], - "related": ["3262", "3264"] - }, - "3264": { - "name": "Windows 11 25H2 Pro China (26200.6584)", - "badge": "LATEST", - "archs": ["x64"], - "related": ["3262", "3263"] - }, - "3265": { - "name": "Windows 11 Arm64 25H2 (26200.6584)", - "badge": "LATEST", - "archs": ["ARM64"], - "related": ["3262", "3321", "3131"] - }, - "3266": { - "name": "Windows 11 Arm64 25H2 Home China (26200.6584)", - "badge": "LATEST", - "archs": ["ARM64"], - "related": ["3265", "3267"] - }, - "3267": { - "name": "Windows 11 Arm64 25H2 Pro China (26200.6584)", - "badge": "LATEST", - "archs": ["ARM64"], - "related": ["3265", "3266"] - }, - "3321": { - "name": "Windows 11 25H2 (Updated Oct)", - "badge": "LATEST", - "archs": ["x64"], - "related": ["3262", "3324", "3113"] - }, - "3324": { - "name": "Windows 11 Arm64 25H2 (Updated Oct)", - "badge": "LATEST", - "archs": ["ARM64"], - "related": ["3321", "3262", "3265"] - } + "48": { + "name": "Windows 8.1 Single Language (9600.17415)", + "badge": "LEGACY", + "archs": [ + "x64", + "x86" + ], + "related": [ + "52" + ] + }, + "52": { + "name": "Windows 8.1 (9600.17415)", + "badge": "LEGACY", + "archs": [ + "x64", + "x86" + ], + "related": [ + "48", + "2618" + ] + }, + "2378": { + "name": "Windows 10 22H2 Home China (19045.2006)", + "badge": "EOL SOON", + "archs": [ + "x64" + ], + "related": [ + "2618" + ] + }, + "2618": { + "name": "Windows 10 22H2 v1 (19045.2965)", + "badge": "EOL SOON", + "archs": [ + "x64", + "x86" + ], + "related": [ + "3113", + "3262", + "52" + ] + }, + "3113": { + "name": "Windows 11 24H2 (26100.1742)", + "badge": "STABLE", + "archs": [ + "x64" + ], + "related": [ + "3262", + "3131", + "2618" + ] + }, + "3114": { + "name": "Windows 11 24H2 Home China (26100.1742)", + "badge": "STABLE", + "archs": [ + "x64" + ], + "related": [ + "3113", + "3115" + ] + }, + "3115": { + "name": "Windows 11 24H2 Pro China (26100.1742)", + "badge": "STABLE", + "archs": [ + "x64" + ], + "related": [ + "3113", + "3114" + ] + }, + "3131": { + "name": "Windows 11 Arm64 24H2 (26100.1742)", + "badge": "STABLE", + "archs": [ + "ARM64" + ], + "related": [ + "3265", + "3113" + ] + }, + "3132": { + "name": "Windows 11 Arm64 24H2 Home China (26100.1742)", + "badge": "STABLE", + "archs": [ + "ARM64" + ], + "related": [ + "3131", + "3133" + ] + }, + "3133": { + "name": "Windows 11 Arm64 24H2 Pro China (26100.1742)", + "badge": "STABLE", + "archs": [ + "ARM64" + ], + "related": [ + "3131", + "3132" + ] + }, + "3262": { + "name": "Windows 11 25H2 (26200.6584)", + "badge": "LATEST", + "archs": [ + "x64" + ], + "related": [ + "3265", + "3321", + "3113" + ] + }, + "3263": { + "name": "Windows 11 25H2 Home China (26200.6584)", + "badge": "LATEST", + "archs": [ + "x64" + ], + "related": [ + "3262", + "3264" + ] + }, + "3264": { + "name": "Windows 11 25H2 Pro China (26200.6584)", + "badge": "LATEST", + "archs": [ + "x64" + ], + "related": [ + "3262", + "3263" + ] + }, + "3265": { + "name": "Windows 11 Arm64 25H2 (26200.6584)", + "badge": "LATEST", + "archs": [ + "ARM64" + ], + "related": [ + "3262", + "3321", + "3131" + ] + }, + "3266": { + "name": "Windows 11 Arm64 25H2 Home China (26200.6584)", + "badge": "LATEST", + "archs": [ + "ARM64" + ], + "related": [ + "3265", + "3267" + ] + }, + "3267": { + "name": "Windows 11 Arm64 25H2 Pro China (26200.6584)", + "badge": "LATEST", + "archs": [ + "ARM64" + ], + "related": [ + "3265", + "3266" + ] + }, + "3321": { + "name": "Windows 11 25H2 (Updated Oct)", + "badge": "LATEST", + "archs": [ + "x64" + ], + "related": [ + "3262", + "3324", + "3113" + ] + }, + "3324": { + "name": "Windows 11 Arm64 25H2 (Updated Oct)", + "badge": "LATEST", + "archs": [ + "ARM64" + ], + "related": [ + "3321", + "3262", + "3265" + ] + } } \ No newline at end of file diff --git a/frontend/src/components/StatsBar.tsx b/frontend/src/components/StatsBar.tsx index 22db93b..b256dd8 100644 --- a/frontend/src/components/StatsBar.tsx +++ b/frontend/src/components/StatsBar.tsx @@ -12,7 +12,10 @@ export default function StatsBar() { useEffect(() => { fetch('/data/products.json') .then(r => r.json()) - .then(data => setTotalReleases(Object.keys(data).length.toString())) + .then((data: Record) => { + const activeCount = Object.values(data).filter(p => p.active !== false).length + setTotalReleases(activeCount.toString()) + }) .catch(() => {}) }, []) diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index deeecfd..e33142b 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -69,7 +69,10 @@ export default function HomePage() { fetch('/data/products.json') .then(r => r.json()) - .then(data => setTotalReleases(Object.keys(data).length)) + .then((data: Record) => { + const activeCount = Object.values(data).filter(p => p.active !== false).length + setTotalReleases(activeCount) + }) .catch(() => {}) }, []) diff --git a/frontend/src/pages/ProductDetailPage.tsx b/frontend/src/pages/ProductDetailPage.tsx index 533b335..f16001f 100644 --- a/frontend/src/pages/ProductDetailPage.tsx +++ b/frontend/src/pages/ProductDetailPage.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import { useParams, useNavigate, Link } from 'react-router-dom' import { motion, AnimatePresence } from 'motion/react' import * as Select from '@radix-ui/react-select' -import { ArrowLeft, ChevronDown, Download, AlertTriangle, Check, ExternalLink } from 'lucide-react' +import { ArrowLeft, ChevronDown, Download, AlertTriangle, Check, ExternalLink, WifiOff } from 'lucide-react' import { toast } from 'sonner' import type { Sku, DownloadOption } from '../types' import SystemRequirements from '../components/SystemRequirements' @@ -16,6 +16,7 @@ interface ProductCatalogEntry { badge: string archs: string[] related: string[] + active?: boolean } function WinLogo({ className }: { className?: string }) { @@ -54,8 +55,9 @@ export default function ProductDetailPage() { const [copiedUri, setCopiedUri] = useState(null) const [isNotFound, setIsNotFound] = useState(false) const [isValidated, setIsValidated] = useState(false) + const [hasCatalogError, setHasCatalogError] = useState(false) - const [meta, setMeta] = useState<{ badge: string; archs: string[] }>({ badge: '', archs: [] }) + const [meta, setMeta] = useState<{ badge: string; archs: string[]; active: boolean }>({ badge: '', archs: [], active: true }) const [related, setRelated] = useState<{ id: string; label: string }[]>([]) // Load product name + build string @@ -63,12 +65,16 @@ export default function ProductDetailPage() { setBuildStr('') setIsNotFound(false) setIsValidated(false) + setHasCatalogError(false) setProductName('') - setMeta({ badge: '', archs: [] }) + setMeta({ badge: '', archs: [], active: true }) setRelated([]) fetch('/data/products.json') - .then(r => r.json()) + .then(r => { + if (!r.ok) throw new Error('Failed to fetch catalog') + return r.json() + }) .then((data: Record) => { const product = data[productId!] @@ -83,7 +89,7 @@ export default function ProductDetailPage() { const name = product.name setProductName(name) - setMeta({ badge: product.badge || '', archs: product.archs || [] }) + setMeta({ badge: product.badge || '', archs: product.archs || [], active: product.active !== false }) setRelated((product.related || []).map(rId => ({ id: rId, @@ -104,8 +110,8 @@ export default function ProductDetailPage() { if (match) setBuildStr(match[1]) }) .catch(() => { - setIsNotFound(true) - setProductName('Error Loading Product') + setHasCatalogError(true) + setProductName('Error Loading Catalog') }) }, [productId]) @@ -211,7 +217,18 @@ export default function ProductDetailPage() { {/* Main card */}
- {isNotFound ? ( + {hasCatalogError ? ( +
+ +

Network Error

+

+ Failed to load the product catalog. Please check your internet connection or try refreshing the page. +

+ +
+ ) : isNotFound ? (

Product Not Found

@@ -227,6 +244,17 @@ export default function ProductDetailPage() {
+ ) : !meta.active ? ( +
+ +

Product Discontinued

+

+ This release is no longer hosted on Microsoft's official CDN and cannot be downloaded. +

+ + Browse other releases + +
) : isLoadingLangs ? (
@@ -403,13 +431,13 @@ export default function ProductDetailPage() {
- {!isNotFound && ( + {(!isNotFound && !hasCatalogError) && ( <> {/* System requirements */} {/* aria2 tip */} - + {meta.active && } {/* Related releases */} {related.length > 0 && ( diff --git a/frontend/src/pages/ProductsPage.tsx b/frontend/src/pages/ProductsPage.tsx index 09a70c5..76f16e8 100644 --- a/frontend/src/pages/ProductsPage.tsx +++ b/frontend/src/pages/ProductsPage.tsx @@ -22,8 +22,11 @@ export default function ProductsPage() { fetch('/data/products.json') .then(r => r.json()) - .then((data: Record) => { - setProducts(Object.entries(data).map(([id, product]) => ({ id, name: product.name }))) + .then((data: Record) => { + const activeProducts = Object.entries(data) + .filter(([_, product]) => product.active !== false) + .map(([id, product]) => ({ id, name: product.name })) + setProducts(activeProducts) }) .finally(() => setIsLoading(false)) }, []) diff --git a/msdls_v3.py b/msdls_v3.py index 70b054e..5a9fe71 100644 --- a/msdls_v3.py +++ b/msdls_v3.py @@ -96,6 +96,7 @@ if __name__ == "__main__": # Only log skips for very small ranges to keep output clean if (args.last - args.first) < 20: logging.info(f"❌ SKIP: [{i}] Not found") + products[str(i)] = False # Be gentle to the API time.sleep(0.5) @@ -113,23 +114,41 @@ if __name__ == "__main__": # Merge new products, preserving existing extra metadata if present for pid, name in products.items(): if pid in catalog: + if name is False: + if isinstance(catalog[pid], dict): + catalog[pid]["active"] = False + else: + catalog[pid] = { + "name": catalog[pid], + "badge": "", + "archs": [], + "related": [], + "active": False + } + continue + # Update name but keep other fields like badge, archs, etc. if isinstance(catalog[pid], dict): catalog[pid]["name"] = name + catalog[pid]["active"] = True else: # Upgrade from old flat format catalog[pid] = { "name": name, "badge": "", "archs": [], - "related": [] + "related": [], + "active": True } else: + if name is False: + continue # Do not add new dead entries to the catalog catalog[pid] = { "name": name, "badge": "", "archs": [], - "related": [] + "related": [], + "active": True } with open(args.write, 'w', encoding='utf-8') as f: