| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- #!/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)
|