fetch achievement data from self api server

This commit is contained in:
HolographicHat
2022-04-08 23:56:20 +08:00
parent dd582437bc
commit bea596b906
4 changed files with 46 additions and 4046 deletions

BIN
2MB.bin

Binary file not shown.

View File

@@ -13,20 +13,6 @@
![alt](https://upload-bbs.mihoyo.com/upload/2022/04/06/165631158/e540a5a6d50cd5fdee19665435548e00_514247033566841954.jpg)
### Windows7
系统变量添加名为```NODE_SKIP_PLATFORM_CHECK```的变量并将值设为```1```
### 自定义代理(可选)
配置文件内添加proxy字段详细请参看[Axios-请求配置](https://axios-http.com/zh/docs/req_config)
```json
{
"path": [],
"offlineResource": false,
"customCDN": "",
"proxy": {
"protocol": "http",
"host": "127.0.0.1",
"port": 7890
}
}
```
## 下载地址
[releases/latest](https://github.com/HolographicHat/genshin-achievement-export/releases/latest)

File diff suppressed because it is too large Load Diff

116
utils.js
View File

@@ -11,7 +11,6 @@ const { promisify } = require("util")
const { createHash } = require("crypto")
const path = require("path")
const messages = path.join(__dirname, "./proto/Messages.proto")
let axios = require("axios")
const encodeProto = (object, name) => protobuf.load(messages).then(r => {
const msgType = r.lookupType(name)
@@ -37,11 +36,15 @@ const initConfig = async () => {
const configFileName = "./config.json"
if (fs.existsSync(configFileName)) {
conf = JSON.parse(fs.readFileSync(configFileName, "utf-8"))
if (conf.offlineResource !== undefined || conf.customCDN !== undefined || conf.proxy !== undefined) {
conf.proxy = undefined
conf.customCDN = undefined
conf.offlineResource = undefined
fs.writeFileSync(configFileName, JSON.stringify(conf, null, 2))
}
} else {
conf = {
path: [],
offlineResource: false,
customCDN: ""
path: []
}
const p = path.dirname(native.selectGameExecutable())
await checkPath(p).catch(reason => {
@@ -51,11 +54,6 @@ const initConfig = async () => {
conf.path.push(p)
fs.writeFileSync(configFileName, JSON.stringify(conf, null, 2))
}
if (conf.proxy !== undefined) {
axios = axios.create({
proxy: conf.proxy
})
}
if (conf.path.length === 1) {
await checkPath(conf.path[0]).catch(reason => {
console.log(reason)
@@ -129,73 +127,51 @@ const md5 = str => {
return h.digest("hex")
}
let cdnUrlFormat = undefined
/*
* Structure:
* UInt16 = 0x4321: magic num
* UInt8 = 0x2 : version
* Char[32] : data md5
* ... : compressed data
*/
String.prototype.format = function() {
const args = arguments;
return this.replace(/{(\d+)}/g, (match, number) => typeof args[number] != "undefined" ? args[number] : match)
}
const checkCDN = async () => {
const controller = new AbortController()
const a = new Promise(resolve => axios.get("https://cdn.jsdelivr.net/gh/HolographicHat/genshin-achievement-export@master/2MB.bin", {
signal: controller.signal
}).then(_ => resolve(["jsdelivr", "https://cdn.jsdelivr.net/gh/{0}@master/{1}"])))
const b = new Promise(resolve => axios.get("https://raw.githubusercontent.com/HolographicHat/genshin-achievement-export/master/2MB.bin", {
signal: controller.signal
}).then(_ => resolve(["githubusercontent", "https://raw.githubusercontent.com/{0}/master/{1}"])))
const c = new Promise(resolve => axios.get("https://ghproxy.net/https://raw.githubusercontent.com/HolographicHat/genshin-achievement-export/master/2MB.bin", {
signal: controller.signal
}).then(_ => resolve(["ghproxy", "https://ghproxy.net/https://raw.githubusercontent.com/{0}/master/{1}"])))
const d = new Promise(resolve => {
const s = conf === undefined ? "" : conf.customCDN.trim() // 🤔
if (s.length === 0) throw "empty"
axios.get(s.format("HolographicHat/genshin-achievement-export", "2MB.bin"), {
signal: controller.signal
}).then(_ => resolve(["customCDN", s]))
})
await Promise.any([a, b, c, d]).then(([name, url]) => {
debug(`Fastest: ${name}`)
controller.abort()
cdnUrlFormat = url
}).catch()
if (cdnUrlFormat === undefined) {
throw "网络错误,请检查配置文件/网络后重试 (11-3)"
}
}
const loadCache = async (fp, repo = "Dimbreath/GenshinData") => {
log(`预检资源: ${cdnUrlFormat.format(repo, fp)}`)
const loadCache = async (fp = "latest-data") => {
log(`正在加载资源: ${fp}`)
const startAt = Date.now()
fs.mkdirSync("./cache", { recursive: true })
const localPath = `./cache/${md5(fp)}`
if (conf.offlineResource) {
const fd = brotliDecompressSync(fs.readFileSync(localPath))
return JSON.parse(fd.subarray(1 + fd.readUInt8()).toString())
// remove old cache file
fs.readdir("./cache/", (err, files) => {
if (err === null) files.forEach(s => {
const fn = `./cache/${s}`
if (!fn.endsWith(".ch")) fs.rm(fn,_ => {})
})
})
const localPath = `./cache/${md5(fp)}.ch`
const headers = {
"x-content-hash": "0".repeat(32)
}
const header = {}
let fd = Buffer.alloc(0)
if (fs.existsSync(localPath)) {
fd = brotliDecompressSync(fs.readFileSync(localPath))
const etagLength = fd.readUInt8()
header["If-None-Match"] = fd.subarray(1, 1 + etagLength).toString()
fd = fs.readFileSync(localPath)
if (fd.readUInt16BE(0) === 0x4321 && fd.readUInt8(2) === 2) {
headers["x-content-hash"] = fd.subarray(3, 35).toString("utf-8")
}
const headResponse = await axios.head(cdnUrlFormat.format(repo, fp), {
headers: header,
validateStatus: _ => true
})
if (headResponse.status === 304) {
log("%s 命中缓存", fp)
const etagLength = fd.readUInt8()
return JSON.parse(fd.subarray(1 + etagLength).toString())
}
const resp = await cloud.get(`/${fp}`, "application/json", headers, "arraybuffer")
if (resp.status === 304) {
log(`完成 (Cache, ${Date.now() - startAt}ms)`)
return JSON.parse(brotliDecompressSync(fd.subarray(35)).toString())
} else {
log("下载所需资源, 请稍后...")
const response = await axios.get(cdnUrlFormat.format(repo, fp))
const etag = response.headers.etag
const str = JSON.stringify(response.data)
const comp = brotliCompressSync(Buffer.concat([Buffer.of(etag.length), Buffer.from(etag), Buffer.from(str)]))
fs.writeFileSync(localPath, comp)
log("下载完成")
return response.data
const compressedData = resp.data
const decompressedData = brotliDecompressSync(compressedData)
const buf = Buffer.allocUnsafe(compressedData.length + 35)
buf.writeUInt16BE(0x4321, 0)
buf.writeUInt8(0x2, 2)
buf.fill(md5(decompressedData), 3)
buf.fill(compressedData, 35)
fs.writeFileSync(localPath, buf)
log(`完成 (Network, ${Date.now() - startAt}ms)`)
return JSON.parse(decompressedData)
}
}
@@ -302,5 +278,5 @@ class KPacket {
module.exports = {
log, encodeProto, decodeProto, initConfig, splitPacket, upload, brotliCompressSync, brotliDecompressSync,
setupHost, loadCache, debug, checkCDN, checkUpdate, KPacket, cdnUrlFormat, checkGameIsRunning, checkPortIsUsing
setupHost, loadCache, debug, checkUpdate, KPacket, checkGameIsRunning, checkPortIsUsing
}