|
@@ -0,0 +1,163 @@
|
|
|
|
|
+#!/usr/bin/env node
|
|
|
|
|
+/**
|
|
|
|
|
+ * 下载 Epic 免费游戏图片到本地
|
|
|
|
|
+ * 运行方式: node scripts/download-epic-images.mjs
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+import fs from 'fs'
|
|
|
|
|
+import path from 'path'
|
|
|
|
|
+import https from 'https'
|
|
|
|
|
+import http from 'http'
|
|
|
|
|
+import { fileURLToPath } from 'url'
|
|
|
|
|
+
|
|
|
|
|
+const __filename = fileURLToPath(import.meta.url)
|
|
|
|
|
+const __dirname = path.dirname(__filename)
|
|
|
|
|
+
|
|
|
|
|
+const EPIC_API_URL = 'https://uapis.cn/api/v1/game/epic-free'
|
|
|
|
|
+const OUTPUT_DIR = path.join(__dirname, '../public/epic-images')
|
|
|
|
|
+const MANIFEST_PATH = path.join(OUTPUT_DIR, 'manifest.json')
|
|
|
|
|
+
|
|
|
|
|
+// 发起 HTTPS GET 请求获取 JSON
|
|
|
|
|
+function fetchJson(url) {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ https.get(url, (response) => {
|
|
|
|
|
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
|
|
|
+ fetchJson(response.headers.location).then(resolve).catch(reject)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (response.statusCode !== 200) {
|
|
|
|
|
+ reject(new Error(`HTTP ${response.statusCode}`))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let data = ''
|
|
|
|
|
+ response.on('data', chunk => data += chunk)
|
|
|
|
|
+ response.on('end', () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ resolve(JSON.parse(data))
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ reject(e)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }).on('error', reject)
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 确保输出目录存在
|
|
|
|
|
+function ensureDir(dir) {
|
|
|
|
|
+ if (!fs.existsSync(dir)) {
|
|
|
|
|
+ fs.mkdirSync(dir, { recursive: true })
|
|
|
|
|
+ console.log(`📁 Created directory: ${dir}`)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 下载文件
|
|
|
|
|
+function downloadFile(url, destPath) {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ const protocol = url.startsWith('https') ? https : http
|
|
|
|
|
+
|
|
|
|
|
+ const request = protocol.get(url, (response) => {
|
|
|
|
|
+ // 处理重定向
|
|
|
|
|
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
|
|
|
+ downloadFile(response.headers.location, destPath).then(resolve).catch(reject)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (response.statusCode !== 200) {
|
|
|
|
|
+ reject(new Error(`Failed to download ${url}: ${response.statusCode}`))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const fileStream = fs.createWriteStream(destPath)
|
|
|
|
|
+ response.pipe(fileStream)
|
|
|
|
|
+
|
|
|
|
|
+ fileStream.on('finish', () => {
|
|
|
|
|
+ fileStream.close()
|
|
|
|
|
+ resolve()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ fileStream.on('error', (err) => {
|
|
|
|
|
+ fs.unlink(destPath, () => { }) // 删除失败的文件
|
|
|
|
|
+ reject(err)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ request.on('error', reject)
|
|
|
|
|
+ request.setTimeout(30000, () => {
|
|
|
|
|
+ request.destroy()
|
|
|
|
|
+ reject(new Error(`Timeout downloading ${url}`))
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取文件扩展名
|
|
|
|
|
+function getExtension(url) {
|
|
|
|
|
+ const match = url.match(/\.(\w+)(?:\?|$)/)
|
|
|
|
|
+ return match ? `.${match[1]}` : '.jpg'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 生成安全的文件名
|
|
|
|
|
+function safeFileName(id, url) {
|
|
|
|
|
+ const ext = getExtension(url)
|
|
|
|
|
+ return `${id}${ext}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 主函数
|
|
|
|
|
+async function main() {
|
|
|
|
|
+ console.log('🎮 开始下载 Epic 免费游戏图片...\n')
|
|
|
|
|
+
|
|
|
|
|
+ // 获取 Epic API 数据
|
|
|
|
|
+ console.log('📡 正在获取 Epic API 数据...')
|
|
|
|
|
+ const result = await fetchJson(EPIC_API_URL)
|
|
|
|
|
+
|
|
|
|
|
+ if (!result.data || !Array.isArray(result.data)) {
|
|
|
|
|
+ console.error('❌ API 返回数据格式错误')
|
|
|
|
|
+ process.exit(1)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 过滤掉神秘游戏
|
|
|
|
|
+ const games = result.data.filter(game => !game.title.includes('神秘游戏'))
|
|
|
|
|
+ console.log(`📋 找到 ${games.length} 个游戏\n`)
|
|
|
|
|
+
|
|
|
|
|
+ // 确保输出目录存在
|
|
|
|
|
+ ensureDir(OUTPUT_DIR)
|
|
|
|
|
+
|
|
|
|
|
+ // 下载每个游戏的封面图片
|
|
|
|
|
+ const manifest = {}
|
|
|
|
|
+
|
|
|
|
|
+ for (const game of games) {
|
|
|
|
|
+ if (!game.cover) {
|
|
|
|
|
+ console.log(`⚠️ 跳过 ${game.title}: 无封面图片`)
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const fileName = safeFileName(game.id, game.cover)
|
|
|
|
|
+ const destPath = path.join(OUTPUT_DIR, fileName)
|
|
|
|
|
+
|
|
|
|
|
+ // 检查文件是否已存在
|
|
|
|
|
+ if (fs.existsSync(destPath)) {
|
|
|
|
|
+ console.log(`✅ 已存在: ${game.title} (${fileName})`)
|
|
|
|
|
+ manifest[game.id] = `/epic-images/${fileName}`
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ console.log(`⬇️ 下载中: ${game.title}...`)
|
|
|
|
|
+ await downloadFile(game.cover, destPath)
|
|
|
|
|
+ console.log(`✅ 完成: ${game.title} (${fileName})`)
|
|
|
|
|
+ manifest[game.id] = `/epic-images/${fileName}`
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`❌ 失败: ${game.title} - ${error.message}`)
|
|
|
|
|
+ // 失败时保留原始 URL
|
|
|
|
|
+ manifest[game.id] = game.cover
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 保存 manifest 文件
|
|
|
|
|
+ fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2))
|
|
|
|
|
+ console.log(`\n📄 Manifest 已保存到: ${MANIFEST_PATH}`)
|
|
|
|
|
+ console.log('🎉 完成!')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+main().catch(console.error)
|