Compare commits

..

27 Commits

Author SHA1 Message Date
5605b3f6fc chore: bump version to 1.1.16 2026-01-24 21:33:50 +08:00
A-nony-mous
66b1c51d21 feat: add Exclusive Rescreening & W-Engine Reverberation (#21)
* feat: add Exclusive Rescreening & W-Engine Reverberation

* feat: reorder

---------

Co-authored-by: Zichao Lin <earthjasonlin@163.com>
2026-01-24 21:28:43 +08:00
Aues6uen11Z
0e2a345371 Merge pull request #19 from Aues6uen11Z/feat/api-url
feat: 添加用于小程序的API URL复制
2026-01-09 22:09:13 +08:00
GitHub Action
176a7de220 chore: update idJson to 2.5.0 2025-11-09 01:43:32 +00:00
GitHub Action
43bfc906bd chore: update idJson to 2.4.0 2025-10-14 01:26:31 +00:00
b496dd870c feat: UIGF-v4.1 2025-10-06 15:47:54 +08:00
0c317f78b3 refactor: log stat for idmap script 2025-10-06 15:26:02 +08:00
GitHub Action
f84a5282db chore: update idJson to 2.3.0 2025-10-06 07:01:24 +00:00
66acb3e4dc ci: fix GH_TOKEN 2025-10-06 14:59:36 +08:00
b10a11b177 ci: fix can't trigger release 2025-10-06 14:50:03 +08:00
db587f3537 ci: update script and add workflow 2025-10-06 14:38:55 +08:00
afb06390c0 chore: bump version to 1.1.12 2025-06-07 06:55:22 +08:00
6b84fe8670 chore: update idJson 2025-06-07 06:53:30 +08:00
e74f561ce5 chore: bump version to 1.1.11 2025-01-26 07:40:03 +08:00
9738b41372 chore: update idJson 2025-01-26 07:39:15 +08:00
2d0a5d38bb docs: Stargazers over time 2025-01-05 10:41:22 +08:00
16e01b7a13 chore: bump version to 1.1.10 2024-12-07 16:02:00 +08:00
8f492376a0 chore: update idJson 2024-12-07 16:01:20 +08:00
0642c52db2 chore: bump version to 1.1.9 2024-09-27 21:15:19 +08:00
af256fba7d chore: update idJson 2024-09-27 21:14:44 +08:00
6599fbe6d3 chore: bump version to 1.1.8 2024-09-08 12:23:13 +08:00
a99959e6e5 chore: update idJson 2024-09-08 12:22:27 +08:00
c9c92da926 chore: bump version to 1.1.7 2024-08-21 12:27:56 +08:00
fcff120657 chore: update idJson and add display for idJson version 2024-08-21 12:27:19 +08:00
0ec7cb7c4f fix: winreg compatibility issues
sync with upstream
2024-08-07 17:17:54 +08:00
5a3159d4cb chore: bump version to 1.1.6 2024-08-07 17:08:15 +08:00
38b99bf4dc feat: import export ui layout 2024-08-07 17:07:18 +08:00
16 changed files with 2227 additions and 332 deletions

91
.github/workflows/idmap.yml vendored Normal file
View File

@@ -0,0 +1,91 @@
name: Update ID Map and Version
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
update-idmap:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests opencc
- name: Run getIdMap.py
run: |
python tools/getIdMap.py
- name: Check if idJson.json changed
id: check_changes
run: |
git add -A
if git diff --staged --quiet -- src/idJson.json; then
echo "changes=false" >> $GITHUB_OUTPUT
echo "No changes detected in src/idJson.json"
else
echo "changes=true" >> $GITHUB_OUTPUT
echo "Changes detected in src/idJson.json"
fi
- name: Get current version from idJson
if: steps.check_changes.outputs.changes == 'true'
id: get_version
run: |
VERSION=$(python -c "import json; print(json.load(open('src/idJson.json', encoding='utf-8'))['version'])")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Current version in idJson: $VERSION"
- name: Bump package.json version
if: steps.check_changes.outputs.changes == 'true'
id: bump_version
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "Current package.json version: $CURRENT_VERSION"
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
NEW_PATCH=$((PATCH + 1))
NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
echo "New package.json version: $NEW_VERSION"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
node -e "
const fs = require('fs');
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
packageJson.version = '$NEW_VERSION';
fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2) + '\n');
"
- name: Create and push tag
if: steps.check_changes.outputs.changes == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add src/idJson.json package.json
git commit -m "chore: update idJson to ${{ steps.get_version.outputs.version }}"
TAG_NAME="v${{ steps.bump_version.outputs.new_version }}"
git tag $TAG_NAME
git push
git push origin $TAG_NAME
echo "Created and pushed tag: $TAG_NAME"

View File

@@ -28,7 +28,7 @@ jobs:
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ZzzSignalSearchExport ${{ github.ref }}
@@ -39,7 +39,7 @@ jobs:
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_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
@@ -52,4 +52,4 @@ jobs:
commit_message: Update app
build_dir: ./build/update
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

View File

@@ -36,6 +36,10 @@
然后游戏切换的新账号,再打开调频历史记录,工具再点击“加载数据”按钮。
## Stargazers over time
[![Stargazers over time](https://starchart.cc/earthjasonlin/zzz-signal-search-export.svg)](https://starchart.cc/earthjasonlin/zzz-signal-search-export)
## Devlopment
```bash

View File

@@ -1,6 +1,6 @@
{
"name": "zzz-signal-search-export",
"version": "1.1.5",
"version": "1.1.16",
"autoUpdateActive": true,
"autoUpdateFrom": "1.1.0",
"main": "./dist/electron/main/main.js",
@@ -110,7 +110,7 @@
"tailwindcss": "^3.0.16",
"vite": "2.7.13",
"vue": "^3.2.29",
"winreg": "^1.2.4",
"winreg": "1.2.4",
"yauzl": "^2.10.0"
},
"keywords": [

View File

@@ -10,6 +10,14 @@
"key": "3",
"name": "音擎频段"
},
{
"key": "102",
"name": "独家重映"
},
{
"key": "103",
"name": "音擎回响"
},
{
"key": "1",
"name": "常驻频段"
@@ -31,6 +39,14 @@
"key": "3",
"name": "音擎頻段"
},
{
"key": "102",
"name": "獨家重映"
},
{
"key": "103",
"name": "音擎回響"
},
{
"key": "1",
"name": "常駐頻段"
@@ -52,6 +68,14 @@
"key": "3",
"name": "W-Engine Channel"
},
{
"key": "102",
"name": "Exclusive Rescreening"
},
{
"key": "103",
"name": "W-Engine Reverberation"
},
{
"key": "1",
"name": "Stable Channel"

View File

@@ -3,7 +3,7 @@
"ui.button.load": "Load data",
"ui.button.update": "Update",
"ui.button.directUpdate": "Direct update",
"ui.button.files": "Export Files",
"ui.button.files": "Import/Export",
"ui.button.excel": "Export Excel",
"ui.button.uigf": "Export UIGF",
"ui.button.import": "Import UIGF",
@@ -13,7 +13,8 @@
"ui.button.startProxy": "Proxy mode",
"ui.button.solution": "Solution",
"ui.button.cacheFolder": "Open cache folder",
"ui.button.copyUrl": "Copy URL",
"ui.button.copyUrl": "Copy Web URL",
"ui.button.copyApiUrl": "Copy API URL",
"ui.select.newAccount": "New account",
"ui.hint.newAccount": "Export data from other accounts",
"ui.hint.init": "Please open your warp history inside the game client before clicking on the 'Load data' button",
@@ -57,6 +58,7 @@
"ui.setting.fetchFullHistoryHint": "When this option is enabled, click the \"Update Data\" button to get all the card draw records within 6 months. When there are incorrect data within 6 months, this function can be used to repair.",
"ui.setting.closeProxy": "Disable system proxy",
"ui.setting.closeProxyHint": "When you choose proxy mode, if the program crashes it can cause unwanted results that may affect your system. You can click this button to clear the system proxy settings.",
"ui.setting.idVersion": "ID database version",
"ui.about.title": "About",
"ui.about.license": "This software is opensource using MIT license.",
"ui.urlDialog.title": "Input URL manually",
@@ -100,9 +102,10 @@
"excel.customFont": "Arial",
"excel.filePrefix": "Zenless Zone Zero Signal Search Log",
"excel.fileType": "Excel file",
"uigf.fileType": "Uniformed Interchangeable GachaLog Format v4.0 (Beta)",
"uigf.fileType": "Uniformed Interchangeable GachaLog Format v4.0, v4.1",
"ui.extra.cacheClean": "1. Confirm whether the search history in the game has been opened, and if the error \"User authentication expired\" still appears, try the following steps \n2. Close the game window of Zenless Zone Zero \n3. Click the \"Open Web Cache Folder\" button above to open the \"Cache\" folder \n4. Delete the \"Cache_Data\" folder \n5. Start the Zenless Zone Zero game and open the search history page in the game \n6. Close this dialog and click the \"Update Data\" button",
"ui.extra.findCacheFolder": "If the \"Open cache folder\" button does not respond, you can manually find the game's web cache folder. The directory is \"Your game installation path/ZenlessZoneZero_Data/webCaches/Cache/\"",
"ui.extra.urlCopied": "URL Copied",
"ui.extra.urlCopied": "Web URL Copied",
"ui.extra.apiUrlCopied": "API URL Copied",
"ui.uigf.title": "Please select the UID(s) you want to export"
}

View File

@@ -3,7 +3,7 @@
"ui.button.load": "加载数据",
"ui.button.update": "更新数据",
"ui.button.directUpdate": "直接更新",
"ui.button.files": "导出文件",
"ui.button.files": "导入/导出",
"ui.button.excel": "导出Excel",
"ui.button.uigf":"导出UIGF",
"ui.button.import":"导入UIGF",
@@ -13,7 +13,8 @@
"ui.button.startProxy": "代理模式",
"ui.button.solution": "解决办法",
"ui.button.cacheFolder": "打开网页缓存文件夹",
"ui.button.copyUrl": "复制URL",
"ui.button.copyUrl": "复制网页链接",
"ui.button.copyApiUrl": "复制API链接",
"ui.select.newAccount": "新账号",
"ui.hint.newAccount": "从其它账号导出数据",
"ui.hint.init": "请先在游戏里打开任意一个抽卡记录后再点击“加载数据”按钮",
@@ -56,6 +57,7 @@
"ui.setting.fetchFullHistoryHint": "开启时点击“更新数据”按钮会完整获取6个月内所有的抽卡记录当记录里有6个月范围以内的错误数据时可以通过这个功能修复。",
"ui.setting.closeProxy": "关闭系统代理",
"ui.setting.closeProxyHint": "如果使用过代理模式时工具非正常关闭,可能导致系统代理设置没能清除,可以通过这个按钮来清除设置过的系统代理。",
"ui.setting.idVersion": "ID 数据库版本",
"ui.about.title": "关于",
"ui.about.license": "本工具为开源软件,源代码使用 MIT 协议授权",
"ui.urlDialog.title": "手动输入URL",
@@ -99,9 +101,10 @@
"excel.customFont": "微软雅黑",
"excel.filePrefix": "绝区零调频记录",
"excel.fileType": "Excel文件",
"uigf.fileType":"统一可交换抽卡记录标准 v4.0Beta",
"uigf.fileType":"统一可交换抽卡记录标准 v4.0, v4.1",
"ui.extra.cacheClean": "1. 确认是否已经打开游戏内的抽卡历史记录,如果仍然出现“身份认证已过期”的错误,再尝试下面的步骤\n2. 关闭绝区零的游戏窗口\n3. 点击上方的“打开缓存文件夹”按钮打开Cache文件夹\n4. 删除Cache_Data文件夹\n5. 启动绝区零游戏,打开游戏内抽卡历史记录页面\n6. 关闭这个对话框,再点击“更新数据”按钮",
"ui.extra.findCacheFolder": "如果点“打开缓存文件夹”按钮没有反应,可以手动找到游戏的网页缓存文件夹,目录为“你的游戏安装路径/ZenlessZoneZero_Data/webCaches/Cache/”",
"ui.extra.urlCopied": "URL已复制",
"ui.extra.urlCopied": "网页链接已复制",
"ui.extra.apiUrlCopied": "API链接已复制",
"ui.uigf.title": "请选择要导出的UID"
}

View File

@@ -3,7 +3,7 @@
"ui.button.load": "加載數據",
"ui.button.update": "更新數據",
"ui.button.directUpdate": "直接更新",
"ui.button.files": "導出文件",
"ui.button.files": "導入/匯出",
"ui.button.excel": "導出Excel",
"ui.button.uigf":"導出UIGF",
"ui.button.import":"導入UIGF",
@@ -13,7 +13,8 @@
"ui.button.startProxy": "代理模式",
"ui.button.solution": "解決辦法",
"ui.button.cacheFolder": "打開網頁緩存文件夾",
"ui.button.copyUrl": "復製URL",
"ui.button.copyUrl": "復製網頁鏈接",
"ui.button.copyApiUrl": "復製API鏈接",
"ui.select.newAccount": "新賬號",
"ui.hint.newAccount": "從其它賬號導出數據",
"ui.hint.init": "請先在遊戲裏打開任意一個抽卡記錄後再點擊「加載數據」按鈕",
@@ -55,6 +56,7 @@
"ui.setting.fetchFullHistoryHint": "開啟時點擊「更新數據」按鈕會完整獲取6個月內所有的抽卡記錄當記錄裏有6個月範圍以內的錯誤數據時可以通過這個功能修復。",
"ui.setting.closeProxy": "關閉系統代理",
"ui.setting.closeProxyHint": "如果使用過代理模式時工具非正常關閉,可能導致系統代理設置沒能清除,可以通過這個按鈕來清除設置過的系統代理。",
"ui.setting.idVersion": "ID 數據庫版本",
"ui.about.title": "關於",
"ui.about.license": "本工具為開源軟件,源代碼使用 MIT 協議授權",
"ui.urlDialog.title": "手動輸入URL",
@@ -98,9 +100,10 @@
"excel.customFont": "微軟雅黑",
"excel.filePrefix": "絕區零調頻記錄",
"excel.fileType": "Excel文件",
"uigf.fileType":"統一可交換抽卡記錄標準 v4.0Beta",
"uigf.fileType":"統一可交換抽卡記錄標準 v4.0, v4.1",
"ui.extra.cacheClean": "1. 確認是否已經打開遊戲內的抽卡歷史記錄,如果仍然出現「身份認證已過期」的錯誤,再嘗試下面的步驟\n2. 關閉絕區零的遊戲窗口\n3. 點擊上方的「打開緩存文件夾」按鈕打開Cache文件夾\n4. 刪除Cache_Data文件夾\n5. 啟動絕區零遊戲,打開遊戲內抽卡歷史記錄頁面\n6. 關閉這個對話框,再點擊「更新數據」按鈕",
"ui.extra.findCacheFolder": "如果點「打開緩存文件夾」按鈕沒有反應,可以手動找到遊戲的網頁緩存文件夾,目錄為「你的遊戲安裝路徑/ZenlessZoneZero_Data/webCaches/Cache/」",
"ui.extra.urlCopied": "URL已復製",
"ui.extra.urlCopied": "網頁鏈接已復製",
"ui.extra.apiUrlCopied": "API鏈接已復製",
"ui.uigf.title": "請選擇要導出的UID"
}

File diff suppressed because it is too large Load Diff

View File

@@ -92,7 +92,7 @@ const importUIGF = async () => {
try {
const jsonData = fs.readJsonSync(filepath[0])
if('info' in jsonData && 'version' in jsonData.info) {
if (jsonData.info.version !== 'v4.0') {
if (jsonData.info.version !== 'v4.0' && jsonData.info.version !== 'v4.1') {
sendMsg('不支持此版本UIGF')
console.error('不支持此版本UIGF')
return

View File

@@ -1,5 +1,5 @@
const { clipboard, ipcMain } = require('electron')
const { getUrl, deleteData } = require('./getData')
const { getUrl, deleteData, getApiUrl } = require('./getData')
ipcMain.handle('COPY_URL', async () => {
const url = await getUrl()
@@ -10,6 +10,15 @@ ipcMain.handle('COPY_URL', async () => {
return false
})
ipcMain.handle('COPY_API_URL', async () => {
const apiUrl = await getApiUrl()
if (apiUrl) {
clipboard.writeText(apiUrl)
return true
}
return false
})
ipcMain.handle('DELETE_DATA', async (event, uid, action) => {
await deleteData(uid, action)
})

View File

@@ -26,7 +26,9 @@ const defaultTypeMap = new Map([
['2', '独家频段'],
['3', '音擎频段'],
['1', '常驻频段'],
['5', '邦布频段']
['5', '邦布频段'],
['102', '独家重映'],
['103', '音擎回响']
])
const serverTimeZone = new Map([
@@ -408,6 +410,17 @@ const getUrl = async () => {
return url
}
const getApiUrl = async () => {
const url = await getUrl()
if (!url) return null
const searchParams = getQuerystring(url)
if (!searchParams) return null
const apiUrl = `${apiDomain}/common/gacha_record/api/getGachaLog?${searchParams.toString()}`
return apiUrl
}
const fetchData = async (urlOverride) => {
const text = i18n.log
await readData()
@@ -559,3 +572,4 @@ exports.deleteData = deleteData
exports.saveData = saveData
exports.changeCurrent = changeCurrent
exports.convertTimeZone = convertTimeZone
exports.getApiUrl = getApiUrl

View File

@@ -2,81 +2,221 @@
<div v-if="ui" class="relative">
<div class="flex justify-between">
<div class="space-x-3">
<el-button type="primary" :icon="state.status === 'init' ? 'milk-tea': 'refresh-right'" class="focus:outline-none" :disabled="!allowClick()" plain @click="fetchData()" :loading="state.status === 'loading'">{{state.status === 'init' ? ui.button.load: ui.button.update}}</el-button>
<el-button
type="primary"
:icon="state.status === 'init' ? 'milk-tea' : 'refresh-right'"
class="focus:outline-none"
:disabled="!allowClick()"
plain
@click="fetchData()"
:loading="state.status === 'loading'"
>{{
state.status === "init" ? ui.button.load : ui.button.update
}}</el-button
>
<el-dropdown :disabled="!gachaData" @command="exportCommand">
<el-button :disabled="!gachaData" icon="download" class="focus:outline-none" type="success" plain>
<el-button
:disabled="!gachaData"
icon="folder-opened"
class="focus:outline-none"
type="success"
plain
>
{{ ui.button.files }}
<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="excel">{{ui.button.excel}}</el-dropdown-item>
<el-dropdown-item command="uigf-json">{{ui.button.uigf}}</el-dropdown-item>
<el-dropdown-item command="excel">{{
ui.button.excel
}}</el-dropdown-item>
<el-dropdown-item command="uigf-json">{{
ui.button.uigf
}}</el-dropdown-item>
<el-dropdown-item command="import-json" divided>{{
ui.button.import
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button @click="importData()" icon="upload" class="focus:outline-none" type="success" plain>{{ui.button.import}}</el-button>
<el-tooltip v-if="detail && state.status !== 'loading'" :content="ui.hint.newAccount" placement="bottom">
<el-button @click="newUser()" plain icon="plus" class="focus:outline-none"></el-button>
<el-tooltip
v-if="detail && state.status !== 'loading'"
:content="ui.hint.newAccount"
placement="bottom"
>
<el-button
@click="newUser()"
plain
icon="plus"
class="focus:outline-none"
></el-button>
</el-tooltip>
<el-tooltip v-if="state.status === 'updated'" :content="ui.hint.relaunchHint" placement="bottom">
<el-button @click="relaunch()" type="success" icon="refresh" class="focus:outline-none" style="margin-left: 48px">{{ui.button.directUpdate}}</el-button>
<el-tooltip
v-if="state.status === 'updated'"
:content="ui.hint.relaunchHint"
placement="bottom"
>
<el-button
@click="relaunch()"
type="success"
icon="refresh"
class="focus:outline-none"
style="margin-left: 48px"
>{{ ui.button.directUpdate }}</el-button
>
</el-tooltip>
</div>
<div class="flex gap-2">
<el-select v-if="state.status !== 'loading' && dataMap && (dataMap.size > 1 || (dataMap.size === 1 && state.current === 0))" class="w-44" @change="changeCurrent" v-model="uidSelectText">
<el-select
v-if="
state.status !== 'loading' &&
dataMap &&
(dataMap.size > 1 || (dataMap.size === 1 && state.current === 0))
"
class="w-44"
@change="changeCurrent"
v-model="uidSelectText"
>
<el-option
v-for="item of dataMap"
:key="item[0]"
:label="maskUid(item[0])"
:value="item[0]">
:value="item[0]"
>
</el-option>
</el-select>
<el-dropdown @command="optionCommand">
<el-button @click="showSetting(true)" class="focus:outline-none" plain type="info" icon="more" >{{ui.button.option}}</el-button>
<el-button
@click="showSetting(true)"
class="focus:outline-none"
plain
type="info"
icon="more"
>{{ ui.button.option }}</el-button
>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="setting" icon="setting">{{ui.button.setting}}</el-dropdown-item>
<el-dropdown-item :disabled="!allowClick() || state.status === 'loading'" command="url" icon="link">{{ui.button.url}}</el-dropdown-item>
<el-dropdown-item command="copyUrl" icon="DocumentCopy">{{ui.button.copyUrl}}</el-dropdown-item>
<el-dropdown-item :disabled="!allowClick() || state.status === 'loading'" command="proxy" icon="position">{{ui.button.startProxy}}</el-dropdown-item>
<el-dropdown-item command="setting" icon="setting">{{
ui.button.setting
}}</el-dropdown-item>
<el-dropdown-item
:disabled="!allowClick() || state.status === 'loading'"
command="url"
icon="link"
>{{ ui.button.url }}</el-dropdown-item
>
<el-dropdown-item command="copyUrl" icon="DocumentCopy">{{
ui.button.copyUrl
}}</el-dropdown-item>
<el-dropdown-item command="copyApiUrl" icon="Link">{{
ui.button.copyApiUrl
}}</el-dropdown-item>
<el-dropdown-item
:disabled="!allowClick() || state.status === 'loading'"
command="proxy"
icon="position"
>{{ ui.button.startProxy }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<p class="text-gray-400 my-2 text-xs">{{hint}}<el-button @click="(state.showCacheCleanDlg=true)" v-if="state.authkeyTimeout" style="margin-left: 8px;" size="small" plain round>{{ui.button.solution}}</el-button></p>
<div v-if="detail" class="gap-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 2xl:grid-cols-4">
<p class="text-gray-400 my-2 text-xs">
{{ hint
}}<el-button
@click="state.showCacheCleanDlg = true"
v-if="state.authkeyTimeout"
style="margin-left: 8px"
size="small"
plain
round
>{{ ui.button.solution }}</el-button
>
</p>
<div
v-if="detail"
class="gap-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 2xl:grid-cols-4"
>
<div class="mb-4" v-for="(item, i) of detail" :key="i">
<div :class="{ hidden: state.config.hideNovice && item[0] === '2' }">
<p class="text-center text-gray-600 my-2">{{typeMap.get(item[0])}}</p>
<pie-chart :data="item" :i18n="state.i18n" :typeMap="typeMap"></pie-chart>
<gacha-detail :i18n="state.i18n" :data="item" :typeMap="typeMap"></gacha-detail>
<p class="text-center text-gray-600 my-2">
{{ typeMap.get(item[0]) }}
</p>
<pie-chart
:data="item"
:i18n="state.i18n"
:typeMap="typeMap"
></pie-chart>
<gacha-detail
:i18n="state.i18n"
:data="item"
:typeMap="typeMap"
></gacha-detail>
</div>
</div>
</div>
<Setting v-show="state.showSetting" :i18n="state.i18n" :gacha-data-info="dataInfo" @refreshData="readData()" @changeLang="getI18nData()" @close="showSetting(false)"></Setting>
<Setting
v-show="state.showSetting"
:i18n="state.i18n"
:gacha-data-info="dataInfo"
@refreshData="readData()"
@changeLang="getI18nData()"
@close="showSetting(false)"
></Setting>
<el-dialog :title="ui.urlDialog.title" v-model="state.showUrlDlg" width="90%" class="max-w-md">
<el-dialog
:title="ui.urlDialog.title"
v-model="state.showUrlDlg"
width="90%"
class="max-w-md"
>
<p class="mb-4 text-gray-500">{{ ui.urlDialog.hint }}</p>
<el-input type="textarea" :autosize="{minRows: 4, maxRows: 6}" :placeholder="ui.urlDialog.placeholder" v-model="state.urlInput" spellcheck="false"></el-input>
<el-input
type="textarea"
:autosize="{ minRows: 4, maxRows: 6 }"
:placeholder="ui.urlDialog.placeholder"
v-model="state.urlInput"
spellcheck="false"
></el-input>
<template #footer>
<span class="dialog-footer">
<el-button @click="state.showUrlDlg = false" class="focus:outline-none">{{ui.common.cancel}}</el-button>
<el-button type="primary" @click="state.showUrlDlg = false, fetchData(state.urlInput)" class="focus:outline-none">{{ui.common.ok}}</el-button>
<el-button
@click="state.showUrlDlg = false"
class="focus:outline-none"
>{{ ui.common.cancel }}</el-button
>
<el-button
type="primary"
@click="(state.showUrlDlg = false), fetchData(state.urlInput)"
class="focus:outline-none"
>{{ ui.common.ok }}</el-button
>
</span>
</template>
</el-dialog>
<el-dialog :title="ui.button.solution" v-model="state.showCacheCleanDlg" width="90%" class="max-w-md cache-clean-dialog">
<el-button plain icon="folder" type="success" @click="openCacheFolder">{{ui.button.cacheFolder}}</el-button>
<el-dialog
:title="ui.button.solution"
v-model="state.showCacheCleanDlg"
width="90%"
class="max-w-md cache-clean-dialog"
>
<el-button plain icon="folder" type="success" @click="openCacheFolder">{{
ui.button.cacheFolder
}}</el-button>
<p class="my-2 flex flex-col text-teal-800 text-[13px]">
<span class="my-1" v-for="txt of cacheCleanTextList">{{ txt }}</span>
</p>
<p class="my-2 text-gray-500 text-xs">{{ ui.extra.findCacheFolder }}</p>
<template #footer>
<div class="dialog-footer text-center">
<el-button type="primary" @click="state.showCacheCleanDlg = false" class="focus:outline-none">{{ui.common.ok}}</el-button>
<el-button
type="primary"
@click="state.showCacheCleanDlg = false"
class="focus:outline-none"
>{{ ui.common.ok }}</el-button
>
</div>
</template>
</el-dialog>
@@ -84,19 +224,19 @@
</template>
<script setup>
const { ipcRenderer } = require('electron')
import { reactive, computed, watch, onMounted } from 'vue'
import PieChart from './components/PieChart.vue'
import GachaDetail from './components/GachaDetail.vue'
import Setting from './components/Setting.vue'
import gachaDetail from './gachaDetail'
import { version } from '../../package.json'
import gachaType from '../gachaType.json'
import { ElMessage, ElMessageBox } from 'element-plus'
const { ipcRenderer } = require("electron");
import { reactive, computed, watch, onMounted } from "vue";
import PieChart from "./components/PieChart.vue";
import GachaDetail from "./components/GachaDetail.vue";
import Setting from "./components/Setting.vue";
import gachaDetail from "./gachaDetail";
import { version } from "../../package.json";
import gachaType from "../gachaType.json";
import { ElMessage, ElMessageBox } from "element-plus";
const state = reactive({
status: 'init',
log: '',
status: "init",
log: "",
data: null,
dataMap: new Map(),
current: 0,
@@ -104,157 +244,163 @@ const state = reactive({
i18n: null,
showUrlDlg: false,
showCacheCleanDlg: false,
urlInput: '',
urlInput: "",
authkeyTimeout: false,
config: {}
})
config: {},
});
const dataMap = computed(() => {
const result = new Map()
const result = new Map();
for (let [uid, data] of state.dataMap) {
if (!data.deleted) {
result.set(uid, data)
result.set(uid, data);
}
}
return result
})
return result;
});
const dataInfo = computed(() => {
const result = []
const result = [];
for (let [uid, data] of state.dataMap) {
result.push({
uid, time: data.time, deleted: data.deleted
})
uid,
time: data.time,
deleted: data.deleted,
});
}
return result
})
return result;
});
const ui = computed(() => {
if (state.i18n) {
return state.i18n.ui
return state.i18n.ui;
}
})
});
const cacheCleanTextList = computed(() => {
if (ui.value) {
return ui.value.extra?.cacheClean?.split('\n')
return ui.value.extra?.cacheClean?.split("\n");
}
return []
})
return [];
});
const gachaData = computed(() => {
return state.dataMap.get(state.current)
})
return state.dataMap.get(state.current);
});
const uidSelectText = computed(() => {
if (state.current === 0) {
return state.i18n.ui.select.newAccount
return state.i18n.ui.select.newAccount;
} else {
return state.current
return state.current;
}
})
});
const allowClick = () => {
const data = state.dataMap.get(state.current)
if (!data) return true
const data = state.dataMap.get(state.current);
if (!data) return true;
if (Date.now() - data.time < 1000 * 10) {
return false
}
return true
return false;
}
return true;
};
const hint = computed(() => {
const data = state.dataMap.get(state.current)
const data = state.dataMap.get(state.current);
if (!state.i18n) {
return 'Loading...'
return "Loading...";
}
const { hint } = state.i18n.ui
const { colon } = state.i18n.symbol
if (state.status === 'init') {
return hint.init
} else if (state.status === 'loaded') {
return `${hint.lastUpdate}${colon}${new Date(data.time).toLocaleString()}`
} else if (state.status === 'loading') {
return state.log || 'Loading...'
} else if (state.status === 'updated') {
return state.log
} else if (state.status === 'failed') {
return state.log + ` - ${hint.failed}`
const { hint } = state.i18n.ui;
const { colon } = state.i18n.symbol;
if (state.status === "init") {
return hint.init;
} else if (state.status === "loaded") {
return `${hint.lastUpdate}${colon}${new Date(data.time).toLocaleString()}`;
} else if (state.status === "loading") {
return state.log || "Loading...";
} else if (state.status === "updated") {
return state.log;
} else if (state.status === "failed") {
return state.log + ` - ${hint.failed}`;
}
return ' '
})
return " ";
});
const detail = computed(() => {
const data = dataMap.value.get(state.current)
const data = dataMap.value.get(state.current);
if (data) {
return gachaDetail(data.result)
return gachaDetail(data.result);
}
})
});
const typeMap = computed(() => {
const gachaTypeMap = new Map(gachaType)
const type = gachaTypeMap.get(state.config.lang)
const result = new Map()
const gachaTypeMap = new Map(gachaType);
const type = gachaTypeMap.get(state.config.lang);
const result = new Map();
if (type) {
for (let { key, name } of type) {
result.set(key, name)
result.set(key, name);
}
}
return result
})
return result;
});
const fetchData = async (url) => {
state.log = ''
state.status = 'loading'
const data = await ipcRenderer.invoke('FETCH_DATA', url)
state.log = "";
state.status = "loading";
const data = await ipcRenderer.invoke("FETCH_DATA", url);
if (data) {
state.dataMap = data.dataMap
state.current = data.current
state.status = 'loaded'
state.dataMap = data.dataMap;
state.current = data.current;
state.status = "loaded";
} else {
state.status = 'failed'
}
state.status = "failed";
}
};
const readData = async () => {
const data = await ipcRenderer.invoke('READ_DATA')
const data = await ipcRenderer.invoke("READ_DATA");
if (data) {
state.dataMap = data.dataMap
state.current = data.current
state.dataMap = data.dataMap;
state.current = data.current;
if (data.dataMap.get(data.current)) {
state.status = 'loaded'
}
state.status = "loaded";
}
}
};
const getI18nData = async () => {
const data = await ipcRenderer.invoke('I18N_DATA')
const data = await ipcRenderer.invoke("I18N_DATA");
if (data) {
state.i18n = data
setTitle()
}
state.i18n = data;
setTitle();
}
};
const saveExcel = async () => {
await ipcRenderer.invoke('SAVE_EXCEL')
}
await ipcRenderer.invoke("SAVE_EXCEL");
};
const exportUIGFJSON = () => {
let uidList = []
dataMap.value.forEach(item => {
uidList.push(item.uid)
})
let uidList = [];
dataMap.value.forEach((item) => {
uidList.push(item.uid);
});
ElMessageBox({
title: state.i18n.ui.uigf.title,
message: `
<div>
${uidList.map(uid => `
${uidList
.map(
(uid) => `
<div>
<input type="checkbox" id="${uid}" value="${uid}" />
<label for="${uid}">${uid}</label>
</div>
`).join('')}
`
)
.join("")}
</div>
`,
dangerouslyUseHTMLString: true,
@@ -262,122 +408,137 @@ const exportUIGFJSON = () => {
confirmButtonText: state.i18n.ui.common.ok,
cancelButtonText: state.i18n.ui.common.cancel,
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
const selected_uids = uidList.filter(uid => document.getElementById(uid).checked);
ipcRenderer.invoke('EXPORT_UIGF_JSON', selected_uids);
if (action === "confirm") {
const selected_uids = uidList.filter(
(uid) => document.getElementById(uid).checked
);
ipcRenderer.invoke("EXPORT_UIGF_JSON", selected_uids);
}
done();
}
}).then(() => {
}).catch(() => {
});
}
},
})
.then(() => {})
.catch(() => {});
};
const importData = async () => {
state.status = 'loading'
const data = await ipcRenderer.invoke('IMPORT_UIGF_JSON')
state.status = "loading";
const data = await ipcRenderer.invoke("IMPORT_UIGF_JSON");
if (data) {
state.dataMap = data.dataMap
state.current = data.current
state.status = 'loaded'
state.dataMap = data.dataMap;
state.current = data.current;
state.status = "loaded";
} else {
state.status = 'failed'
}
state.status = "failed";
}
};
const exportCommand = (type) => {
if (type === 'excel') {
saveExcel()
} else if (type === 'uigf-json') {
exportUIGFJSON()
}
if (type === "excel") {
saveExcel();
} else if (type === "uigf-json") {
exportUIGFJSON();
} else if (type === "import-json") {
importData();
}
};
const openCacheFolder = async () => {
await ipcRenderer.invoke('OPEN_CACHE_FOLDER')
}
await ipcRenderer.invoke("OPEN_CACHE_FOLDER");
};
const changeCurrent = async (uid) => {
if (uid === 0) {
state.status = 'init'
state.status = "init";
} else {
state.status = 'loaded'
}
state.current = uid
await ipcRenderer.invoke('CHANGE_UID', uid)
state.status = "loaded";
}
state.current = uid;
await ipcRenderer.invoke("CHANGE_UID", uid);
};
const newUser = async () => {
await changeCurrent(0)
}
await changeCurrent(0);
};
const relaunch = async () => {
await ipcRenderer.invoke('RELAUNCH')
}
await ipcRenderer.invoke("RELAUNCH");
};
const maskUid = (uid) => {
return `${uid}`.replace(/(.{3})(.+)(.{3})$/, '$1***$3')
}
return `${uid}`.replace(/(.{3})(.+)(.{3})$/, "$1***$3");
};
const showSetting = (show) => {
if (show) {
state.showSetting = true
state.showSetting = true;
} else {
state.showSetting = false
updateConfig()
}
state.showSetting = false;
updateConfig();
}
};
const optionCommand = (type) => {
if (type === 'setting') {
showSetting(true)
} else if (type === 'url') {
state.urlInput = ''
state.showUrlDlg = true
} else if (type === 'proxy') {
fetchData('proxy')
} else if (type === 'copyUrl') {
copyUrl()
}
if (type === "setting") {
showSetting(true);
} else if (type === "url") {
state.urlInput = "";
state.showUrlDlg = true;
} else if (type === "proxy") {
fetchData("proxy");
} else if (type === "copyUrl") {
copyUrl();
} else if (type === "copyApiUrl") {
copyApiUrl();
}
};
const setTitle = () => {
document.title = `${state.i18n.ui.win.title} - v${version}`
}
document.title = `${state.i18n.ui.win.title} - v${version}`;
};
const updateConfig = async () => {
state.config = await ipcRenderer.invoke('GET_CONFIG')
}
state.config = await ipcRenderer.invoke("GET_CONFIG");
};
const copyUrl = async () => {
const successed = await ipcRenderer.invoke('COPY_URL')
const successed = await ipcRenderer.invoke("COPY_URL");
if (successed) {
ElMessage.success(ui.value.extra.urlCopied)
ElMessage.success(ui.value.extra.urlCopied);
} else {
ElMessage.error(state.i18n.log.url.notFound)
ElMessage.error(state.i18n.log.url.notFound);
}
};
const copyApiUrl = async () => {
const successed = await ipcRenderer.invoke("COPY_API_URL");
if (successed) {
ElMessage.success(ui.value.extra.apiUrlCopied);
} else {
ElMessage.error(state.i18n.log.url.notFound);
}
};
onMounted(async () => {
await readData()
await getI18nData()
await readData();
await getI18nData();
ipcRenderer.on('LOAD_DATA_STATUS', (event, message) => {
state.log = message
})
ipcRenderer.on("LOAD_DATA_STATUS", (event, message) => {
state.log = message;
});
ipcRenderer.on('ERROR', (event, err) => {
console.error(err)
})
ipcRenderer.on("ERROR", (event, err) => {
console.error(err);
});
ipcRenderer.on('UPDATE_HINT', (event, message) => {
state.log = message
state.status = 'updated'
})
ipcRenderer.on("UPDATE_HINT", (event, message) => {
state.log = message;
state.status = "updated";
});
ipcRenderer.on('AUTHKEY_TIMEOUT', (event, message) => {
state.authkeyTimeout = message
})
ipcRenderer.on("AUTHKEY_TIMEOUT", (event, message) => {
state.authkeyTimeout = message;
});
await updateConfig()
})
await updateConfig();
});
</script>

View File

@@ -47,6 +47,7 @@
</el-form-item>
</el-form>
<h3 class="text-lg my-4">{{about.title}}</h3>
<p class="text-gray-600 text-xs mt-1">{{text.idVersion}} {{idJson.version}}</p>
<p class="text-gray-600 text-xs mt-1">{{about.license}}</p>
<p class="text-gray-600 text-xs mt-1">GitHub: <a @click="openGithub" class="cursor-pointer text-blue-400">https://github.com/earthjasonlin/zzz-signal-search-export</a></p>
<p class="text-gray-600 text-xs mt-1 pb-6">UIGF: <a @click="openUIGF" class="cursor-pointer text-blue-400">https://uigf.org/</a></p>
@@ -82,6 +83,7 @@
<script setup>
const { ipcRenderer, shell } = require('electron')
import idJson from '../../idJson.json'
import { reactive, onMounted, computed } from 'vue'
const emit = defineEmits(['close', 'changeLang', 'refreshData'])

View File

@@ -6,12 +6,7 @@ import requests
from opencc import OpenCC
# 初始化 OpenCC 转换器
cc = OpenCC('s2t')
# 获取 JSON 数据
weapon_url = 'https://api.hakush.in/zzz/data/weapon.json'
character_url = 'https://api.hakush.in/zzz/data/character.json'
bangboo_url = 'https://api.hakush.in/zzz/data/bangboo.json'
cc = OpenCC("s2t")
# 语言映射配置
language_map = {
@@ -19,43 +14,77 @@ language_map = {
"zh-tw": "CHS", # 简体转繁体
"en-us": "EN",
"ja-jp": "JA",
"ko-kr": "KO"
"ko-kr": "KO",
}
# 类型映射配置
type_map = {
"weapon": {"zh-cn": "音擎", "zh-tw": "音擎", "en-us": "W-Engines", "ja-jp": "音動機", "ko-kr": "W-엔진"},
"character": {"zh-cn": "代理人", "zh-tw": "代理人", "en-us": "Agents",
"ja-jp": "エージェント", "ko-kr": "에이전트"},
"bangboo": {"zh-cn": "邦布", "zh-tw": "邦布", "en-us": "Bangboo",
"ja-jp": "ボンプ", "ko-kr": "「Bangboo」"}
"weapon": {
"zh-cn": "音擎",
"zh-tw": "音擎",
"en-us": "W-Engines",
"ja-jp": "音動機",
"ko-kr": "W-엔진",
},
"character": {
"zh-cn": "代理人",
"zh-tw": "代理人",
"en-us": "Agents",
"ja-jp": "エージェント",
"ko-kr": "에이전트",
},
"bangboo": {
"zh-cn": "邦布",
"zh-tw": "邦布",
"en-us": "Bangboo",
"ja-jp": "ボンプ",
"ko-kr": "「Bangboo」",
},
}
def fetch_json(url):
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
def transform_data(data, item_type):
transformed = {lang: {} for lang in language_map.keys()}
for id_, item in data.items():
for lang, key in language_map.items():
name = item[key] if lang != 'zh-tw' else cc.convert(item['CHS'])
name = item[key] if lang != "zh-tw" else cc.convert(item["CHS"])
transformed[lang][id_] = {
"name": name,
"item_type": type_map[item_type][lang],
"rank_type": item['rank']
"rank_type": item["rank"],
}
return transformed
def main():
try:
version_url = "https://api.hakush.in/zzz/new.json"
version_data = fetch_json(version_url)
latest_version = ".".join(version_data["version"].split(".")[:2]) + ".0"
print(f"Latest version: {latest_version}")
weapon_url = f"https://api.hakush.in/zzz/{latest_version}/weapon.json"
character_url = f"https://api.hakush.in/zzz/{latest_version}/character.json"
bangboo_url = f"https://api.hakush.in/zzz/{latest_version}/bangboo.json"
weapon_data = fetch_json(weapon_url)
print("Fetched", len(weapon_data), "weapons")
character_data = fetch_json(character_url)
print("Fetched", len(character_data), "characters")
bangboo_data = fetch_json(bangboo_url)
print("Fetched", len(bangboo_data), "bangboos")
transformed_data = {lang: {} for lang in language_map.keys()}
transformed_data["version"] = latest_version
weapon_transformed = transform_data(weapon_data, "weapon")
character_transformed = transform_data(character_data, "character")
bangboo_transformed = transform_data(bangboo_data, "bangboo")
@@ -65,7 +94,7 @@ def main():
transformed_data[lang].update(character_transformed[lang])
transformed_data[lang].update(bangboo_transformed[lang])
with open('./src/idJson.json', 'w', encoding='utf-8') as f:
with open("./src/idJson.json", "w", encoding="utf-8") as f:
json.dump(transformed_data, f, ensure_ascii=False, indent=2)
print("Data successfully transformed and saved")
@@ -73,5 +102,6 @@ def main():
except requests.RequestException as e:
print(f"Error fetching data: {e}")
if __name__ == "__main__":
main()

View File

@@ -5205,10 +5205,10 @@ window-size@^1.1.1:
define-property "^1.0.0"
is-number "^3.0.0"
winreg@^1.2.4:
winreg@1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b"
integrity sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=
integrity sha512-IHpzORub7kYlb8A43Iig3reOvlcBJGX9gZ0WycHhghHtA65X0LYnMRuJs+aH1abVnMJztQkvQNlltnbPi5aGIA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"