Compare commits

..

18 Commits

Author SHA1 Message Date
66188231bc chore: bump version to 1.0.6 2024-07-22 21:59:23 +08:00
5624af3fb2 fix(build): fix icon 2024-07-22 21:58:17 +08:00
3c848a97f8 docs(readme): update project name in Chinese title 2024-07-21 23:25:56 +08:00
6ddc29af5a docs(readme): update readme links 2024-07-21 23:13:30 +08:00
e83fe42268 fix(utils): update hash keys 2024-07-21 21:42:19 +08:00
f9e74b4fb8 chore: bump version to 1.0.5 2024-07-21 20:46:26 +08:00
84179ccc8d ci: fix yarn lock for actions 2024-07-21 20:46:05 +08:00
8b725053ce feat(update): re-implement auto update 2024-07-21 19:45:51 +08:00
bebb14b63d chore: bump version to 1.0.4 2024-07-21 14:30:40 +08:00
2adf56d062 fix(gachaDetail): reset countMio when get S-Rank 2024-07-21 14:26:14 +08:00
ee94ae36cd chore: bump version to 1.0.3 2024-07-21 13:33:58 +08:00
e575e46238 fix(getData): update API host for OS servers 2024-07-21 13:33:04 +08:00
3fac233471 fix(getData): game path for test not removed 2024-07-21 13:26:29 +08:00
d99af2cc26 chore: bump version to v1.0.2 2024-07-20 19:25:15 +08:00
681e664e32 feat: check url from cache
1. NEVER MODIFY THE `yarn.lock` YOU GET. YOU NEVER KNOW WHAT IT CRASHES
2. FUCK REGEX
2024-07-20 19:24:01 +08:00
5f9fc94709 chore: bump version to v1.0.1 2024-07-19 01:42:02 +08:00
d96bd9f355 fix: read url from log
Directly get url from `Player.log`

Closes #2
2024-07-19 01:37:21 +08:00
3ff2879041 add lanzou link 2024-07-17 17:16:31 +08:00
17 changed files with 2549 additions and 2024 deletions

51
.electron-vite/update.js Normal file
View File

@ -0,0 +1,51 @@
const fs = require('fs-extra')
const path = require('path')
const crypto = require('crypto')
const AdmZip = require('adm-zip')
const { version } = require('../package.json')
const hash = (data, type = 'sha256') => {
const hmac = crypto.createHmac(type, 'nap')
hmac.update(data)
return hmac.digest('hex')
}
const createZip = (filePath, dest) => {
const zip = new AdmZip()
zip.addLocalFolder(filePath)
zip.toBuffer()
zip.writeZip(dest)
}
const start = async () => {
copyAppZip()
const appPath = './build/win-ia32-unpacked/resources/app'
const name = 'app.zip'
const outputPath = path.resolve('./build/update/update/')
const zipPath = path.resolve(outputPath, name)
await fs.ensureDir(outputPath)
await fs.emptyDir(outputPath)
createZip(appPath, zipPath)
const buffer = await fs.readFile(zipPath)
const sha256 = hash(buffer)
const hashName = sha256.slice(7, 12)
await fs.copy(zipPath, path.resolve(outputPath, `${hashName}.zip`))
await fs.remove(zipPath)
await fs.outputJSON(path.join(outputPath, 'manifest.json'), {
active: true,
version,
from: '0.0.1',
name: `${hashName}.zip`,
hash: sha256
})
}
const copyAppZip = () => {
try {
const dir = path.resolve('./build')
const filePath = path.resolve(dir, `ZzzSignalSearchExport-${version}-ia32-win.zip`)
fs.copySync(filePath, path.join(dir, 'app.zip'))
} catch (e) {}
}
start()

55
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,55 @@
on:
workflow_dispatch:
push:
# Sequence of patterns matched against refs/tag
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Release
jobs:
build:
name: Release
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: Build App
run: |
yarn --frozen-lockfile
yarn build:win32
yarn build-update
- name: Create Release
if: success()
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ZzzSignalSearchExport ${{ github.ref }}
draft: false
prerelease: false
- name: Upload Release Asset
if: success()
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: ./build/app.zip
asset_name: ZzzSignalSearchExport.zip
asset_content_type: application/zip
- name: Deploy update
if: success()
uses: crazy-max/ghaction-github-pages@v2
with:
commit_message: Update app
build_dir: ./build/update
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}

View File

@ -1,6 +1,6 @@
# 绝区零记录导出工具
# 绝区零调频记录导出工具
中文 | [English](https://git.loliquq.cn/earthjasonlin/zzz-signal-search-export/blob/main/docs/README_EN.md)
中文 | [English](https://github.com/earthjasonlin/zzz-signal-search-export/blob/main/docs/README_EN.md)
这个项目由[star-rail-warp-export](https://github.com/biuuu/star-rail-warp-export/)修改而来,功能基本一致。
@ -14,7 +14,7 @@
## 使用说明
1. 下载工具后解压 - 下载地址: [GitHub](https://github.com/earthjasonlin/zzz-signal-search-export/releases/latest/download/ZzzSignalSearchExport.zip) / [123云盘](https://www.123pan.com/s/Vs9uVv-ShhE.html)
1. 下载工具后解压 - 下载地址: [GitHub](https://github.com/earthjasonlin/zzz-signal-search-export/releases/latest/download/ZzzSignalSearchExport.zip) / [123云盘](https://www.123pan.com/s/Vs9uVv-ShhE.html) / [蓝奏云(密码:zzzz](https://www.lanzouh.com/b00eewtvxa)
2. 打开游戏的跃迁详情页面
![详情页面](/docs/wish-history.jpg)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -1,6 +1,6 @@
# Zenless Zone Zero Signal Search History Exporter
[中文](https://git.loliquq.cn/earthjasonlin/zzz-signal-search-export/blob/main/README.md) | English
[中文](https://github.com/earthjasonlin/zzz-signal-search-export) | English
This project is modified from the [star-rail-warp-export](https://github.com/biuuu/star-rail-warp-export/) repository, and its functions are basically the same.

View File

@ -1,6 +1,6 @@
{
"name": "zzz-signal-search-export",
"version": "1.0.0",
"version": "1.0.6",
"main": "./dist/electron/main/main.js",
"author": "earthjasonlin <https://git.loliquq.cn/earthjasonlin>",
"homepage": "https://github.com/earthjasonlin/zzz-signal-search-export",
@ -16,6 +16,7 @@
"build:dir": "cross-env BUILD_TARGET=clean node .electron-vite/build.js && electron-builder --dir",
"build:clean": "cross-env BUILD_TARGET=onlyClean node .electron-vite/build.js",
"build:web": "cross-env BUILD_TARGET=web node .electron-vite/build.js",
"build-update": "node .electron-vite/update.js",
"dev:web": "cross-env TARGET=web node .electron-vite/dev-runner.js",
"start": "electron ./src/main/main.js",
"dep:upgrade": "yarn upgrade-interactive --latest",

View File

@ -87,6 +87,7 @@
"log.proxy.hint": "Using proxy mode [${ip}:${port}] to get URLplease reopen warp history inside the game client.",
"log.url.notFound2": "Unable to find URL, please make sure you already opened warp history inside the game client",
"log.url.incorrect": "Unable to get URL parameters",
"log.autoUpdate.success": "Auto update successfulplease restart the program",
"excel.header.time": "time",
"excel.header.name": "name",
"excel.header.type": "type",

View File

@ -86,6 +86,7 @@
"log.proxy.hint": "正在使用代理模式[${ip}:${port}]获取URL请重新打开游戏抽卡记录。",
"log.url.notFound2": "未找到URL请确认是否已打开游戏抽卡记录",
"log.url.incorrect": "获取URL参数失败",
"log.autoUpdate.success": "自动更新已完成,重启工具后生效",
"excel.header.time": "时间",
"excel.header.name": "名称",
"excel.header.type": "类别",

View File

@ -138,15 +138,20 @@ const readLog = async () => {
}
const promises = logPaths.map(async logpath => {
const logText = await fs.readFile(logpath, 'utf8')
const gamePathMch = logText.match(/\w:\/.*?\/StarRail_Data\//i)
const url = logText.match(/https:\/\/.*?\/info/g)
if (url) {
return getLatestUrl(url)
}
const gamePathMch = logText.match(/([A-Z]:\/.*?\/)(?=ZenlessZoneZero_Data)/i)
if (gamePathMch) {
const [cacheText, cacheFile] = await getCacheText(gamePathMch[0])
const urlMch = cacheText.match(/https[^?]+?\?[^?]+?&auth_appid=webview_gacha&.+?authkey=.+?&game_biz=hkrpg_.+?&plat_type=pc/g)
const[cacheText, cacheFile] = await getCacheText(gamePathMch[0]+"/ZenlessZoneZero_Data")
const urlMch = cacheText.match(/https.+?authkey=.+?end_id=/g)
if (urlMch) {
cacheFolder = cacheFile.replace(/Cache_Data[/\\]data_2$/, '')
return getLatestUrl(urlMch)
}
}
})
const result = await Promise.all(promises)
for (let url of result) {
@ -288,8 +293,8 @@ const fixAuthkey = (url) => {
const getQuerystring = (url) => {
const text = i18n.log
const { searchParams, host } = new URL(fixAuthkey(url))
if (host.includes('webstatic-sea') || host.includes('hkrpg-api-os') || host.includes('api-os-takumi') || host.includes('hoyoverse.com')) {
apiDomain = 'https://api-os-takumi.mihoyo.com'
if (host.includes('webstatic-sea') || host.includes('hoyoverse.com')) {
apiDomain = 'https://public-operation-nap-sg.hoyoverse.com'
} else {
apiDomain = 'https://public-operation-nap.mihoyo.com'
}

View File

@ -5,6 +5,7 @@ require('./getData')
require('./bridge')
require('./excel')
require('./SRGFJson')
const { getUpdateInfo } = require('./update/index')
const isDev = !app.isPackaged
let win = null
@ -53,6 +54,12 @@ if (!isFirstInstance) {
if (proxyStatus.started) {
disableProxy()
}
if (getUpdateInfo().status === 'moving') {
e.preventDefault()
setTimeout(() => {
app.quit()
}, 3000)
}
})
app.on('quit', () => {

View File

@ -0,0 +1,185 @@
// Copyright (c) 2014 Max Ogden and other contributors
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// https://github.com/maxogden/extract-zip
// eslint-disable-next-line node/no-unsupported-features/node-builtins
const { createWriteStream, promises: fs } = require('original-fs')
const getStream = require('get-stream')
const path = require('path')
const { promisify } = require('util')
const stream = require('stream')
const yauzl = require('yauzl')
const openZip = promisify(yauzl.open)
const pipeline = promisify(stream.pipeline)
class Extractor {
constructor (zipPath, opts) {
this.zipPath = zipPath
this.opts = opts
}
async extract () {
this.zipfile = await openZip(this.zipPath, { lazyEntries: true })
this.canceled = false
return new Promise((resolve, reject) => {
this.zipfile.on('error', err => {
this.canceled = true
reject(err)
})
this.zipfile.readEntry()
this.zipfile.on('close', () => {
if (!this.canceled) {
resolve()
}
})
this.zipfile.on('entry', async entry => {
/* istanbul ignore if */
if (this.canceled) {
return
}
if (entry.fileName.startsWith('__MACOSX/')) {
this.zipfile.readEntry()
return
}
const destDir = path.dirname(path.join(this.opts.dir, entry.fileName))
try {
await fs.mkdir(destDir, { recursive: true })
const canonicalDestDir = await fs.realpath(destDir)
const relativeDestDir = path.relative(this.opts.dir, canonicalDestDir)
if (relativeDestDir.split(path.sep).includes('..')) {
throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`)
}
await this.extractEntry(entry)
this.zipfile.readEntry()
} catch (err) {
this.canceled = true
this.zipfile.close()
reject(err)
}
})
})
}
async extractEntry (entry) {
/* istanbul ignore if */
if (this.canceled) {
return
}
if (this.opts.onEntry) {
this.opts.onEntry(entry, this.zipfile)
}
const dest = path.join(this.opts.dir, entry.fileName)
// convert external file attr int into a fs stat mode int
const mode = (entry.externalFileAttributes >> 16) & 0xFFFF
// check if it's a symlink or dir (using stat mode constants)
const IFMT = 61440
const IFDIR = 16384
const IFLNK = 40960
const symlink = (mode & IFMT) === IFLNK
let isDir = (mode & IFMT) === IFDIR
// Failsafe, borrowed from jsZip
if (!isDir && entry.fileName.endsWith('/')) {
isDir = true
}
// check for windows weird way of specifying a directory
// https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566
const madeBy = entry.versionMadeBy >> 8
if (!isDir) isDir = (madeBy === 0 && entry.externalFileAttributes === 16)
const procMode = this.getExtractedMode(mode, isDir) & 0o777
// always ensure folders are created
const destDir = isDir ? dest : path.dirname(dest)
const mkdirOptions = { recursive: true }
if (isDir) {
mkdirOptions.mode = procMode
}
await fs.mkdir(destDir, mkdirOptions)
if (isDir) return
const readStream = await promisify(this.zipfile.openReadStream.bind(this.zipfile))(entry)
if (symlink) {
const link = await getStream(readStream)
await fs.symlink(link, dest)
} else {
await pipeline(readStream, createWriteStream(dest, { mode: procMode }))
}
}
getExtractedMode (entryMode, isDir) {
let mode = entryMode
// Set defaults, if necessary
if (mode === 0) {
if (isDir) {
if (this.opts.defaultDirMode) {
mode = parseInt(this.opts.defaultDirMode, 10)
}
if (!mode) {
mode = 0o755
}
} else {
if (this.opts.defaultFileMode) {
mode = parseInt(this.opts.defaultFileMode, 10)
}
if (!mode) {
mode = 0o644
}
}
}
return mode
}
}
module.exports = async function (zipPath, opts) {
if (!path.isAbsolute(opts.dir)) {
throw new Error('Target directory is expected to be absolute')
}
await fs.mkdir(opts.dir, { recursive: true })
opts.dir = await fs.realpath(opts.dir)
return new Extractor(zipPath, opts).extract()
}

65
src/main/update/index.js Normal file
View File

@ -0,0 +1,65 @@
const { app } = require('electron')
const fetch = require('electron-fetch').default
const semver = require('semver')
const util = require('util')
const path = require('path')
const fs = require('fs-extra')
const extract = require('../module/extract-zip')
const { version } = require('../../../package.json')
const { hash, sendMsg } = require('../utils')
const config = require('../config')
const i18n = require('../i18n')
const streamPipeline = util.promisify(require('stream').pipeline)
async function download(url, filePath) {
const response = await fetch(url)
if (!response.ok) throw new Error(`unexpected response ${response.statusText}`)
await streamPipeline(response.body, fs.createWriteStream(filePath))
}
const updateInfo = {
status: 'init'
}
const isDev = !app.isPackaged
const appPath = isDev ? path.resolve(__dirname, '../../', 'update-dev/app'): app.getAppPath()
const updatePath = isDev ? path.resolve(__dirname, '../../', 'update-dev/download') : path.resolve(appPath, '..', '..', 'update')
const update = async () => {
if (isDev) return
try {
const url = 'https://earthjasonlin.github.io/zzz-signal-search-export/update'
const res = await fetch(`${url}/manifest.json?t=${Math.floor(Date.now() / (1000 * 60 * 10))}`)
const data = await res.json()
if (!data.active) return
if (semver.gt(data.version, version) && semver.gte(version, data.from)) {
await fs.emptyDir(updatePath)
const filePath = path.join(updatePath, data.name)
if (!config.autoUpdate) {
sendMsg(data.version, 'NEW_VERSION')
return
}
updateInfo.status = 'downloading'
await download(`${url}/${data.name}`, filePath)
const buffer = await fs.readFile(filePath)
const sha256 = hash(buffer)
if (sha256 !== data.hash) return
const appPathTemp = path.join(updatePath, 'app')
await extract(filePath, { dir: appPathTemp })
updateInfo.status = 'moving'
await fs.emptyDir(appPath)
await fs.copy(appPathTemp, appPath)
updateInfo.status = 'finished'
sendMsg(i18n.log.autoUpdate.success, 'UPDATE_HINT')
}
} catch (e) {
updateInfo.status = 'failed'
sendMsg(e, 'ERROR')
}
}
const getUpdateInfo = () => updateInfo
setTimeout(update, 1000)
exports.getUpdateInfo = getUpdateInfo

View File

@ -142,12 +142,12 @@ const readJSON = async (dataPath, name) => {
}
const hash = (data, type = 'sha256') => {
const hmac = crypto.createHmac(type, 'hk4e')
const hmac = crypto.createHmac(type, 'nap')
hmac.update(data)
return hmac.digest('hex')
}
const scryptKey = crypto.scryptSync(userPath, 'hk4e', 24)
const scryptKey = crypto.scryptSync(userPath, 'nap', 24)
const cipherAes = (data) => {
const algorithm = 'aes-192-cbc'
const iv = Buffer.alloc(16, 0)

View File

@ -327,6 +327,11 @@ onMounted(async () => {
console.error(err)
})
ipcRenderer.on('UPDATE_HINT', (event, message) => {
state.log = message
state.status = 'updated'
})
ipcRenderer.on('AUTHKEY_TIMEOUT', (event, message) => {
state.authkeyTimeout = message
})

View File

@ -23,6 +23,12 @@
<el-button type="primary" plain @click="state.showDataDialog = true">{{common.dataManage}}</el-button>
<p class="text-gray-400 text-xs m-1.5">{{text.dataManagerHint}}</p>
</el-form-item>
<el-form-item :label="text.autoUpdate">
<el-switch
@change="saveSetting"
v-model="settingForm.autoUpdate">
</el-switch>
</el-form-item>
<el-form-item :label="text.fetchFullHistory">
<el-switch
@change="saveSetting"
@ -92,6 +98,7 @@ const settingForm = reactive({
lang: 'zh-cn',
logType: 1,
proxyMode: true,
autoUpdate: true,
fetchFullHistory: false,
})
@ -105,7 +112,7 @@ const text = computed(() => props.i18n.ui.setting)
const about = computed(() => props.i18n.ui.about)
const saveSetting = async () => {
const keys = ['lang', 'logType', 'proxyMode', 'fetchFullHistory']
const keys = ['lang', 'logType', 'proxyMode', 'autoUpdate', 'fetchFullHistory']
for (let key of keys) {
await ipcRenderer.invoke('SAVE_CONFIG', [key, settingForm[key]])
}

View File

@ -54,7 +54,7 @@ const gachaDetail = (data) => {
detail.ssrPos.push([name, index + 1 - lastSSR, time, key])
lastSSR = index + 1
detail.count4++
detail.countMio++
detail.countMio = 0
if (isWeapon(type)) {
detail.count4w++
itemCount(detail.weapon4, name)

4160
yarn.lock

File diff suppressed because it is too large Load Diff