diff --git a/backend/main.go b/backend/main.go
index a17b2b6..39955b7 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: 10 * time.Second}
+ client := &http.Client{Timeout: 3 * 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 ee8ad0f..9d929d8 100644
--- a/frontend/public/data/products.json
+++ b/frontend/public/data/products.json
@@ -1,18 +1,110 @@
{
- "48": "Windows 8.1 Single Language (9600.17415)",
- "52": "Windows 8.1 (9600.17415)",
- "2378": "Windows 10 22H2 Home China (19045.2006)",
- "2618": "Windows 10 22H2 v1 (19045.2965)",
- "3113": "Windows 11 24H2 (26100.1742)",
- "3114": "Windows 11 24H2 Home China (26100.1742)",
- "3115": "Windows 11 24H2 Pro China (26100.1742)",
- "3131": "Windows 11 Arm64 24H2 (26100.1742)",
- "3132": "Windows 11 Arm64 24H2 Home China (26100.1742)",
- "3133": "Windows 11 Arm64 24H2 Pro China (26100.1742)",
- "3262": "Windows 11 25H2 (26200.6584)",
- "3263": "Windows 11 25H2 Home China (26200.6584)",
- "3264": "Windows 11 25H2 Pro China (26200.6584)",
- "3265": "Windows 11 Arm64 25H2 (26200.6584)",
- "3266": "Windows 11 Arm64 25H2 Home China (26200.6584)",
- "3267": "Windows 11 Arm64 25H2 Pro China (26200.6584)"
+ "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/App.tsx b/frontend/src/App.tsx
index c5c3dfb..ea818fd 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -12,9 +12,9 @@ import ScrollToTop from './components/ScrollToTop'
export default function App() {
return (
-
+
-
+
} />
} />
diff --git a/frontend/src/components/StatsBar.tsx b/frontend/src/components/StatsBar.tsx
index 52c41fb..22db93b 100644
--- a/frontend/src/components/StatsBar.tsx
+++ b/frontend/src/components/StatsBar.tsx
@@ -1,17 +1,27 @@
import { motion } from 'motion/react'
+import { useState, useEffect } from 'react'
interface Stat {
value: string
label: string
}
-const stats: Stat[] = [
- { value: '17', label: 'releases available' },
- { value: '38', label: 'languages' },
- { value: '0', label: 'intermediaries' },
-]
-
export default function StatsBar() {
+ const [totalReleases, setTotalReleases] = useState('17')
+
+ useEffect(() => {
+ fetch('/data/products.json')
+ .then(r => r.json())
+ .then(data => setTotalReleases(Object.keys(data).length.toString()))
+ .catch(() => {})
+ }, [])
+
+ const stats: Stat[] = [
+ { value: totalReleases, label: 'releases available' },
+ { value: '38', label: 'languages' },
+ { value: '0', label: 'intermediaries' },
+ ]
+
return (
{
document.title = "Windows ISO Downloader | Official Microsoft Images"
@@ -65,6 +66,11 @@ export default function HomePage() {
if (metaDesc) {
metaDesc.setAttribute('content', "Download official Microsoft Windows ISO files. Fast, free, no registration required.")
}
+
+ fetch('/data/products.json')
+ .then(r => r.json())
+ .then(data => setTotalReleases(Object.keys(data).length))
+ .catch(() => {})
}, [])
return (
@@ -147,7 +153,7 @@ export default function HomePage() {
to="/products"
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl border border-white/8 bg-white/4 text-sm text-zinc-400 hover:text-white hover:border-white/15 hover:bg-white/7 transition-all duration-200"
>
- Browse all {featured.length > 0 ? '16' : ''} releases
+ Browse all {totalReleases} releases
diff --git a/frontend/src/pages/ProductDetailPage.tsx b/frontend/src/pages/ProductDetailPage.tsx
index 41a1b80..533b335 100644
--- a/frontend/src/pages/ProductDetailPage.tsx
+++ b/frontend/src/pages/ProductDetailPage.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
-import { useParams, useNavigate } from 'react-router-dom'
+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'
@@ -11,61 +11,11 @@ import RelatedReleases from '../components/RelatedReleases'
const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:3002'
-// Map product ID → metadata
-const PRODUCT_META: Record = {
- '3262': { badge: 'LATEST', archs: ['x64'] },
- '3265': { badge: 'LATEST', archs: ['ARM64'] },
- '3321': { badge: 'LATEST', archs: ['x64'] },
- '3324': { badge: 'LATEST', archs: ['ARM64'] },
- '3113': { badge: 'STABLE', archs: ['x64'] },
- '3131': { badge: 'STABLE', archs: ['ARM64'] },
- '2618': { badge: 'EOL SOON', archs: ['x64', 'x86'] },
- '2378': { badge: 'EOL SOON', archs: ['x64'] },
- '52': { badge: 'LEGACY', archs: ['x64', 'x86'] },
- '48': { badge: 'LEGACY', archs: ['x64', 'x86'] },
-}
-
-// Sibling release groups for "Also available"
-const RELATED_GROUPS: Record = {
- '3262': [
- { id: '3265', label: 'Win 11 25H2 ARM64' },
- { id: '3321', label: 'Win 11 25H2 (updated)' },
- { id: '3113', label: 'Win 11 24H2' },
- ],
- '3265': [
- { id: '3262', label: 'Win 11 25H2 x64' },
- { id: '3321', label: 'Win 11 25H2 (updated)' },
- { id: '3131', label: 'Win 11 24H2 ARM64' },
- ],
- '3321': [
- { id: '3262', label: 'Win 11 25H2' },
- { id: '3324', label: 'Win 11 25H2 ARM64' },
- { id: '3113', label: 'Win 11 24H2' },
- ],
- '3324': [
- { id: '3321', label: 'Win 11 25H2 x64' },
- { id: '3262', label: 'Win 11 25H2' },
- { id: '3265', label: 'Win 11 25H2 ARM64 (alt)' },
- ],
- '3113': [
- { id: '3262', label: 'Win 11 25H2' },
- { id: '3131', label: 'Win 11 24H2 ARM64' },
- { id: '2618', label: 'Win 10 22H2' },
- ],
- '3131': [
- { id: '3265', label: 'Win 11 25H2 ARM64' },
- { id: '3113', label: 'Win 11 24H2 x64' },
- ],
- '2618': [
- { id: '3113', label: 'Win 11 24H2' },
- { id: '3262', label: 'Win 11 25H2' },
- { id: '52', label: 'Win 8.1' },
- ],
-
- '52': [
- { id: '48', label: 'Win 8.1 Single Language' },
- { id: '2618', label: 'Win 10 22H2' },
- ],
+interface ProductCatalogEntry {
+ name: string
+ badge: string
+ archs: string[]
+ related: string[]
}
function WinLogo({ className }: { className?: string }) {
@@ -102,17 +52,45 @@ export default function ProductDetailPage() {
const [isFetching, setIsFetching] = useState(false)
const [error, setError] = useState(null)
const [copiedUri, setCopiedUri] = useState(null)
-
- const meta = PRODUCT_META[productId!] ?? { badge: '', archs: ['x64'] }
- const related = RELATED_GROUPS[productId!] ?? []
+ const [isNotFound, setIsNotFound] = useState(false)
+ const [isValidated, setIsValidated] = useState(false)
+
+ const [meta, setMeta] = useState<{ badge: string; archs: string[] }>({ badge: '', archs: [] })
+ const [related, setRelated] = useState<{ id: string; label: string }[]>([])
// Load product name + build string
useEffect(() => {
+ setBuildStr('')
+ setIsNotFound(false)
+ setIsValidated(false)
+ setProductName('')
+ setMeta({ badge: '', archs: [] })
+ setRelated([])
+
fetch('/data/products.json')
.then(r => r.json())
- .then((data: Record) => {
- const name = data[productId!] ?? `Product ${productId}`
+ .then((data: Record) => {
+ const product = data[productId!]
+
+ if (!product || !product.name) {
+ setIsNotFound(true)
+ setProductName('Product Not Found')
+ document.title = 'Product Not Found | Windows ISO Downloader'
+ const descMeta = document.querySelector('meta[name="description"]')
+ if (descMeta) descMeta.setAttribute('content', 'The requested product could not be found.')
+ return
+ }
+
+ const name = product.name
setProductName(name)
+ setMeta({ badge: product.badge || '', archs: product.archs || [] })
+
+ setRelated((product.related || []).map(rId => ({
+ id: rId,
+ label: data[rId]?.name || `Product ${rId}`
+ })))
+
+ setIsValidated(true)
// Dynamic SEO injection
document.title = `${name} ISO Download | Windows ISO Downloader`
@@ -125,12 +103,15 @@ export default function ProductDetailPage() {
const match = name.match(/\(([^)]+)\)/)
if (match) setBuildStr(match[1])
})
- .catch(() => setProductName(`Product ${productId}`))
+ .catch(() => {
+ setIsNotFound(true)
+ setProductName('Error Loading Product')
+ })
}, [productId])
// Fetch languages on mount
useEffect(() => {
- if (!productId) return
+ if (!productId || !isValidated) return
setIsLoadingLangs(true)
setError(null)
setDownloadLinks([])
@@ -151,7 +132,7 @@ export default function ProductDetailPage() {
})
.catch(e => setError(e.message))
.finally(() => setIsLoadingLangs(false))
- }, [productId])
+ }, [productId, isValidated])
async function handleGetLinks() {
if (!selectedSku || !productId) return
@@ -230,11 +211,37 @@ export default function ProductDetailPage() {
{/* Main card */}
- {isLoadingLangs ? (
-
-
-
-
+ {isNotFound ? (
+
+
+
Product Not Found
+
+ The product ID you requested ({productId}) does not exist or has been removed from the catalog.
+
+
+
+ Home
+
+
+ Browse all
+
+
+
+ ) : isLoadingLangs ? (
+
+
+
+ Connecting to Microsoft CDN...
+
+
) : error && !languages.length ? (
@@ -396,15 +403,19 @@ export default function ProductDetailPage() {
- {/* System requirements */}
-
+ {!isNotFound && (
+ <>
+ {/* System requirements */}
+
- {/* aria2 tip */}
-
+ {/* aria2 tip */}
+
- {/* Related releases */}
- {related.length > 0 && (
-
+ {/* Related releases */}
+ {related.length > 0 && (
+
+ )}
+ >
)}
diff --git a/frontend/src/pages/ProductsPage.tsx b/frontend/src/pages/ProductsPage.tsx
index a56aee8..09a70c5 100644
--- a/frontend/src/pages/ProductsPage.tsx
+++ b/frontend/src/pages/ProductsPage.tsx
@@ -22,8 +22,8 @@ export default function ProductsPage() {
fetch('/data/products.json')
.then(r => r.json())
- .then((data: Record
) => {
- setProducts(Object.entries(data).map(([id, name]) => ({ id, name })))
+ .then((data: Record) => {
+ setProducts(Object.entries(data).map(([id, product]) => ({ id, name: product.name })))
})
.finally(() => setIsLoading(false))
}, [])
diff --git a/msdls_v3.py b/msdls_v3.py
index 4270923..70b054e 100644
--- a/msdls_v3.py
+++ b/msdls_v3.py
@@ -101,8 +101,39 @@ if __name__ == "__main__":
time.sleep(0.5)
if args.write:
+ import os
+ catalog = {}
+ if os.path.exists(args.write):
+ try:
+ with open(args.write, 'r', encoding='utf-8') as f:
+ catalog = json.load(f)
+ except Exception as e:
+ logging.error(f"Failed to read existing catalog: {e}")
+
+ # Merge new products, preserving existing extra metadata if present
+ for pid, name in products.items():
+ if pid in catalog:
+ # Update name but keep other fields like badge, archs, etc.
+ if isinstance(catalog[pid], dict):
+ catalog[pid]["name"] = name
+ else:
+ # Upgrade from old flat format
+ catalog[pid] = {
+ "name": name,
+ "badge": "",
+ "archs": [],
+ "related": []
+ }
+ else:
+ catalog[pid] = {
+ "name": name,
+ "badge": "",
+ "archs": [],
+ "related": []
+ }
+
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}")
+ json.dump(catalog, f, indent=4)
+ logging.info(f"\nSaved {len(catalog)} products to {args.write} (merged with existing)")
logging.info("Done.")