download-epic-images.mjs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. #!/usr/bin/env node
  2. /**
  3. * 下载 Epic 免费游戏图片到本地
  4. * 运行方式: node scripts/download-epic-images.mjs
  5. */
  6. import fs from 'fs'
  7. import path from 'path'
  8. import https from 'https'
  9. import http from 'http'
  10. import { fileURLToPath } from 'url'
  11. const __filename = fileURLToPath(import.meta.url)
  12. const __dirname = path.dirname(__filename)
  13. const EPIC_API_URL = 'https://uapis.cn/api/v1/game/epic-free'
  14. const OUTPUT_DIR = path.join(__dirname, '../public/epic-images')
  15. const MANIFEST_PATH = path.join(OUTPUT_DIR, 'manifest.json')
  16. // 发起 HTTPS GET 请求获取 JSON
  17. function fetchJson(url) {
  18. return new Promise((resolve, reject) => {
  19. https.get(url, (response) => {
  20. if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
  21. fetchJson(response.headers.location).then(resolve).catch(reject)
  22. return
  23. }
  24. if (response.statusCode !== 200) {
  25. reject(new Error(`HTTP ${response.statusCode}`))
  26. return
  27. }
  28. let data = ''
  29. response.on('data', chunk => data += chunk)
  30. response.on('end', () => {
  31. try {
  32. resolve(JSON.parse(data))
  33. } catch (e) {
  34. reject(e)
  35. }
  36. })
  37. }).on('error', reject)
  38. })
  39. }
  40. // 确保输出目录存在
  41. function ensureDir(dir) {
  42. if (!fs.existsSync(dir)) {
  43. fs.mkdirSync(dir, { recursive: true })
  44. console.log(`📁 Created directory: ${dir}`)
  45. }
  46. }
  47. // 下载文件
  48. function downloadFile(url, destPath) {
  49. return new Promise((resolve, reject) => {
  50. const protocol = url.startsWith('https') ? https : http
  51. const request = protocol.get(url, (response) => {
  52. // 处理重定向
  53. if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
  54. downloadFile(response.headers.location, destPath).then(resolve).catch(reject)
  55. return
  56. }
  57. if (response.statusCode !== 200) {
  58. reject(new Error(`Failed to download ${url}: ${response.statusCode}`))
  59. return
  60. }
  61. const fileStream = fs.createWriteStream(destPath)
  62. response.pipe(fileStream)
  63. fileStream.on('finish', () => {
  64. fileStream.close()
  65. resolve()
  66. })
  67. fileStream.on('error', (err) => {
  68. fs.unlink(destPath, () => { }) // 删除失败的文件
  69. reject(err)
  70. })
  71. })
  72. request.on('error', reject)
  73. request.setTimeout(30000, () => {
  74. request.destroy()
  75. reject(new Error(`Timeout downloading ${url}`))
  76. })
  77. })
  78. }
  79. // 获取文件扩展名
  80. function getExtension(url) {
  81. const match = url.match(/\.(\w+)(?:\?|$)/)
  82. return match ? `.${match[1]}` : '.jpg'
  83. }
  84. // 生成安全的文件名
  85. function safeFileName(id, url) {
  86. const ext = getExtension(url)
  87. return `${id}${ext}`
  88. }
  89. // 主函数
  90. async function main() {
  91. console.log('🎮 开始下载 Epic 免费游戏图片...\n')
  92. // 获取 Epic API 数据
  93. console.log('📡 正在获取 Epic API 数据...')
  94. const result = await fetchJson(EPIC_API_URL)
  95. if (!result.data || !Array.isArray(result.data)) {
  96. console.error('❌ API 返回数据格式错误')
  97. process.exit(1)
  98. }
  99. // 过滤掉神秘游戏
  100. const games = result.data.filter(game => !game.title.includes('神秘游戏'))
  101. console.log(`📋 找到 ${games.length} 个游戏\n`)
  102. // 确保输出目录存在
  103. ensureDir(OUTPUT_DIR)
  104. // 下载每个游戏的封面图片
  105. const manifest = {}
  106. for (const game of games) {
  107. if (!game.cover) {
  108. console.log(`⚠️ 跳过 ${game.title}: 无封面图片`)
  109. continue
  110. }
  111. const fileName = safeFileName(game.id, game.cover)
  112. const destPath = path.join(OUTPUT_DIR, fileName)
  113. // 检查文件是否已存在
  114. if (fs.existsSync(destPath)) {
  115. console.log(`✅ 已存在: ${game.title} (${fileName})`)
  116. manifest[game.id] = `/epic-images/${fileName}`
  117. continue
  118. }
  119. try {
  120. console.log(`⬇️ 下载中: ${game.title}...`)
  121. await downloadFile(game.cover, destPath)
  122. console.log(`✅ 完成: ${game.title} (${fileName})`)
  123. manifest[game.id] = `/epic-images/${fileName}`
  124. } catch (error) {
  125. console.error(`❌ 失败: ${game.title} - ${error.message}`)
  126. // 失败时保留原始 URL
  127. manifest[game.id] = game.cover
  128. }
  129. }
  130. // 保存 manifest 文件
  131. fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2))
  132. console.log(`\n📄 Manifest 已保存到: ${MANIFEST_PATH}`)
  133. console.log('🎉 完成!')
  134. }
  135. main().catch(console.error)