[FEAT] UIGF v4 支持 (#72)

This commit is contained in:
2024-08-05 11:22:01 +08:00
committed by GitHub
parent 34f87fb41c
commit 9a4aa130e0
12 changed files with 4660 additions and 102 deletions

View File

@ -5,7 +5,8 @@
"ui.button.directUpdate": "Direct update", "ui.button.directUpdate": "Direct update",
"ui.button.files": "Export Files", "ui.button.files": "Export Files",
"ui.button.excel": "Export Excel", "ui.button.excel": "Export Excel",
"ui.button.srgf": "Export JSON", "ui.button.uigf": "Export UIGF",
"ui.button.import": "Import UIGF",
"ui.button.url": "Input URL", "ui.button.url": "Input URL",
"ui.button.setting": "Settings", "ui.button.setting": "Settings",
"ui.button.option": "Option", "ui.button.option": "Option",
@ -96,8 +97,9 @@
"excel.customFont": "Arial", "excel.customFont": "Arial",
"excel.filePrefix": "Star Rail Warp logger", "excel.filePrefix": "Star Rail Warp logger",
"excel.fileType": "Excel file", "excel.fileType": "Excel file",
"srgf.fileType": "Star Rail Gacha Log Format file", "uigf.fileType": "Uniformed Interchangeable GachaLog Format v4.0",
"ui.extra.cacheClean": "1. Confirm whether the warp 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 Star Rail \n3. Click the \"Open Web Cache Folder\" button above to open the \"Cache\" folder \n4. Delete the \"Cache_ Data\" folder \n5. Start the Star Rail game and open the warp history page in the game \n6. Close this dialog and click the \"Update Data\" button", "ui.extra.cacheClean": "1. Confirm whether the warp 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 Star Rail \n3. Click the \"Open Web Cache Folder\" button above to open the \"Cache\" folder \n4. Delete the \"Cache_Data\" folder \n5. Start the Star Rail game and open the warp 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/Star Rail/Games/StarRail_Data/webCaches/Cache/\"", "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/Star Rail/Games/StarRail_Data/webCaches/Cache/\"",
"ui.extra.urlCopied": "URL Copied" "ui.extra.urlCopied": "URL Copied",
"ui.uigf.title": "Please select the UID(s) you want to export"
} }

View File

@ -5,7 +5,8 @@
"ui.button.directUpdate": "直接更新", "ui.button.directUpdate": "直接更新",
"ui.button.files": "导出文件", "ui.button.files": "导出文件",
"ui.button.excel": "导出Excel", "ui.button.excel": "导出Excel",
"ui.button.srgf":"导出JSON", "ui.button.uigf":"导出UIGF",
"ui.button.import":"导入UIGF",
"ui.button.url": "输入URL", "ui.button.url": "输入URL",
"ui.button.setting": "设置", "ui.button.setting": "设置",
"ui.button.option": "选项", "ui.button.option": "选项",
@ -96,8 +97,9 @@
"excel.customFont": "微软雅黑", "excel.customFont": "微软雅黑",
"excel.filePrefix": "星穹铁道跃迁记录", "excel.filePrefix": "星穹铁道跃迁记录",
"excel.fileType": "Excel文件", "excel.fileType": "Excel文件",
"srgf.fileType":"星穹铁道跃迁记录格式文件", "uigf.fileType":"统一可交换抽卡记录标准 v4.0",
"ui.extra.cacheClean": "1. 确认是否已经打开游戏内的抽卡历史记录,如果仍然出现“身份认证已过期”的错误,再尝试下面的步骤\n2. 关闭星穹铁道的游戏窗口\n3. 点击上方的“打开缓存文件夹”按钮打开Cache文件夹\n4. 删除Cache_Data文件夹\n5. 启动星穹铁道游戏,打开游戏内抽卡历史记录页面\n6. 关闭这个对话框,再点击“更新数据”按钮", "ui.extra.cacheClean": "1. 确认是否已经打开游戏内的抽卡历史记录,如果仍然出现“身份认证已过期”的错误,再尝试下面的步骤\n2. 关闭星穹铁道的游戏窗口\n3. 点击上方的“打开缓存文件夹”按钮打开Cache文件夹\n4. 删除Cache_Data文件夹\n5. 启动星穹铁道游戏,打开游戏内抽卡历史记录页面\n6. 关闭这个对话框,再点击“更新数据”按钮",
"ui.extra.findCacheFolder": "如果点“打开缓存文件夹”按钮没有反应,可以手动找到游戏的网页缓存文件夹,目录为“你的游戏安装路径/Star Rail/Game/StarRail_Data/webCaches/Cache/”", "ui.extra.findCacheFolder": "如果点“打开缓存文件夹”按钮没有反应,可以手动找到游戏的网页缓存文件夹,目录为“你的游戏安装路径/Star Rail/Game/StarRail_Data/webCaches/Cache/”",
"ui.extra.urlCopied": "URL已复制" "ui.extra.urlCopied": "URL已复制",
"ui.uigf.title": "请选择要导出的UID"
} }

View File

@ -5,7 +5,8 @@
"ui.button.directUpdate": "直接更新", "ui.button.directUpdate": "直接更新",
"ui.button.files": "匯出檔案", "ui.button.files": "匯出檔案",
"ui.button.excel": "匯出 Excel", "ui.button.excel": "匯出 Excel",
"ui.button.srgf": "匯出 JSON", "ui.button.uigf": "匯出 UIGF",
"ui.button.import":"導入 UIGF",
"ui.button.url": "輸入 URL", "ui.button.url": "輸入 URL",
"ui.button.setting": "設定", "ui.button.setting": "設定",
"ui.button.option": "選項", "ui.button.option": "選項",
@ -85,7 +86,8 @@
"excel.customFont": "微軟正黑體", "excel.customFont": "微軟正黑體",
"excel.filePrefix": "星穹鐵道躍遷紀錄", "excel.filePrefix": "星穹鐵道躍遷紀錄",
"excel.fileType": "Excel 檔案", "excel.fileType": "Excel 檔案",
"srgf.fileType":"星穹鐵道躍遷紀錄格式檔案", "srgf.fileType":"統一可交換抽卡記錄標準 v4.0",
"ui.extra.cacheClean": "1. 確認是否已經開啟遊戲內的躍遷歷史紀錄,如果仍然出現「身分驗證已過期」的錯誤,再嘗試下面的步驟\n2. 關閉「崩壞:星穹鐵道」的遊戲視窗\n3. 按一下上方的「開啟快取資料夾」按鈕開啟「Cache」資料夾\n4. 刪除「Cache_Data」資料夾\n5. 啟動「崩壞:星穹鐵道」遊戲,開啟遊戲內躍遷歷史紀錄頁面\n6. 關閉這個對話方塊,再按下「更新資料」按鈕", "ui.extra.cacheClean": "1. 確認是否已經開啟遊戲內的躍遷歷史紀錄,如果仍然出現「身分驗證已過期」的錯誤,再嘗試下面的步驟\n2. 關閉「崩壞:星穹鐵道」的遊戲視窗\n3. 按一下上方的「開啟快取資料夾」按鈕開啟「Cache」資料夾\n4. 刪除「Cache_Data」資料夾\n5. 啟動「崩壞:星穹鐵道」遊戲,開啟遊戲內躍遷歷史紀錄頁面\n6. 關閉這個對話方塊,再按下「更新資料」按鈕",
"ui.extra.findCacheFolder": "如果按下「開啟快取資料夾」按鈕沒有回應,可以手動找到遊戲的網頁快取資料夾,目錄為「您的遊戲安裝路徑/Star Rail/Games/StarRail_Data/webCaches/Cache/」" "ui.extra.findCacheFolder": "如果按下「開啟快取資料夾」按鈕沒有回應,可以手動找到遊戲的網頁快取資料夾,目錄為「您的遊戲安裝路徑/Star Rail/Games/StarRail_Data/webCaches/Cache/」",
"ui.uigf.title": "請選擇要導出的UID"
} }

4312
src/idJson.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +0,0 @@
const { app, ipcMain, dialog } = require('electron')
const fs = require('fs-extra')
const path = require('path')
const getData = require('./getData').getData
const { version } = require('../../package.json')
const i18n = require('./i18n')
const getTimeString = () => {
return new Date().toLocaleString('sv').replace(/[- :]/g, '').slice(0, -2)
}
const formatDate = (date) => {
let y = date.getFullYear()
let m = `${date.getMonth()+1}`.padStart(2, '0')
let d = `${date.getDate()}`.padStart(2, '0')
return `${y}-${m}-${d} ${date.toLocaleString('zh-cn', { hour12: false }).slice(-8)}`
}
const start = async () => {
const { dataMap, current } = await getData()
const data = dataMap.get(current)
if (!data.result.size) {
throw new Error('数据为空')
}
const result = {
info: {
uid: data.uid,
lang: data.lang,
export_time: formatDate(new Date()),
export_timestamp: Math.ceil(Date.now() / 1000),
export_app: 'star-rail-warp-export',
export_app_version: `v${version}`,
region_time_zone: data.region_time_zone,
srgf_version: 'v1.0'
},
list: []
}
const listTemp = []
for (let [type, arr] of data.result) {
arr.forEach(log => {
listTemp.push({
gacha_id: log.gacha_id,
gacha_type: log.gacha_type,
item_id: log.item_id,
count: '1',
time: log.time,
name: log.name,
item_type: log.item_type,
rank_type: log.rank_type,
id: log.id
})
})
}
listTemp.sort((a, b) => Number(BigInt(a.id) - BigInt(b.id)))
listTemp.forEach(item => {
result.list.push({
...item
})
})
const filePath = dialog.showSaveDialogSync({
defaultPath: path.join(app.getPath('downloads'), `SRGF_${data.uid}_${getTimeString()}`),
filters: [
{ name: i18n.srgf.fileType, extensions: ['json'] }
]
})
if (filePath) {
await fs.ensureFile(filePath)
await fs.writeFile(filePath, JSON.stringify(result))
}
}
ipcMain.handle('EXPORT_SRGF_JSON', async () => {
await start()
})

174
src/main/UIGFJson.js Normal file
View File

@ -0,0 +1,174 @@
const { app, ipcMain, dialog } = require('electron')
const fs = require('fs-extra')
const path = require('path')
const { getData, saveData, changeCurrent, convertTimeZone } = require('./getData')
const config = require('./config')
const { name, version } = require('../../package.json')
const i18n = require('./i18n')
const { mergeData } = require('./utils/mergeData')
const { sendMsg } = require('./utils')
const idJson = require('../idJson.json')
const getTimeString = () => {
return new Date().toLocaleString('sv').replace(/[- :]/g, '').slice(0, -2)
}
const formatDate = (date) => {
let y = date.getFullYear()
let m = `${date.getMonth()+1}`.padStart(2, '0')
let d = `${date.getDate()}`.padStart(2, '0')
return `${y}-${m}-${d} ${date.toLocaleString('zh-cn', { hour12: false }).slice(-8)}`
}
const exportUIGF = async (uids) => {
const { dataMap, current } = await getData()
const result = {
info: {
export_timestamp: Math.ceil(Date.now() / 1000),
export_app: `${name}`,
export_app_version: `v${version}`,
version: "v4.0"
},
hkrpg: []
}
let fulldata = []
uids.forEach(uid => {
fulldata.push(dataMap.get(uid))
})
if (!fulldata.length) {
throw new Error('数据为空')
}
fulldata.forEach(data => {
const listTemp = []
for (let [type, arr] of data.result) {
arr.forEach(log => {
listTemp.push({
gacha_id: log.gacha_id,
gacha_type: log.gacha_type,
item_id: log.item_id,
count: log.count,
time: log.time,
name: log.name,
item_type: log.item_type,
rank_type: log.rank_type,
id: log.id
})
})
}
listTemp.sort((a, b) => Number(BigInt(a.id) - BigInt(b.id)))
let dataTemp = {
uid: data.uid,
timezone: data.region_time_zone,
lang: data.lang,
list: []
}
listTemp.forEach(item => {
dataTemp.list.push({
...item
})
})
result.hkrpg.push(dataTemp)
})
const filePath = dialog.showSaveDialogSync({
defaultPath: path.join(app.getPath('downloads'), fulldata.length > 1 ? `UIGF_${getTimeString()}` : `UIGF_${fulldata[0].uid}_${getTimeString()}`),
filters: [
{ name: i18n.uigf.fileType, extensions: ['json'] }
]
})
if (filePath) {
await fs.ensureFile(filePath)
await fs.writeFile(filePath, JSON.stringify(result))
}
}
const importUIGF = async () => {
const filepath = await dialog.showOpenDialogSync({
properties: ['openFile'],
filters: [
{ name: i18n.uigf.fileType, extensions: ['json'] }
]
})
if (!filepath) return
const { dataMap, current } = await getData()
try {
const jsonData = fs.readJsonSync(filepath[0])
if('info' in jsonData && 'version' in jsonData.info) {
if (jsonData.info.version !== 'v4.0') {
sendMsg('不支持此版本UIGF')
console.error('不支持此版本UIGF')
return
}
} else {
sendMsg('UIGF格式错误')
console.error('UIGF格式错误')
return
}
jsonData.hkrpg.forEach(uidData => {
const resultTemp = []
const isNew = !Boolean(dataMap.has(uidData.uid))
let region_time_zone
if (!isNew) region_time_zone = dataMap.get(uidData.uid).region_time_zone
else region_time_zone = uidData.timezone
let targetLang
if (!isNew) targetLang = dataMap.get(uidData.uid).lang
else targetLang = uidData.lang
if(!idJson[targetLang] && (!uidData.list[0].name || !uidData.list[0].item_type || !uidData.list[0].rank_type)) targetLang = config.lang
let idTargetLangJson = idJson[targetLang]
uidData.list.forEach(recordEntry => {
let rank_type
if (idTargetLangJson?.[recordEntry.item_id].rank_type) rank_type = String(idTargetLangJson[recordEntry.item_id].rank_type)
else rank_type = recordEntry.rank_type
resultTemp.push({
gacha_id: recordEntry.gacha_id,
gacha_type: recordEntry.gacha_type,
item_id: recordEntry.item_id,
count: recordEntry.count ?? "1",
time: convertTimeZone(recordEntry.time, uidData.timezone, region_time_zone),
name: idTargetLangJson?.[recordEntry.item_id].name ?? recordEntry.name,
item_type: idTargetLangJson?.[recordEntry.item_id].item_type ?? recordEntry.item_type,
rank_type: recordEntry.rank_type,
id: recordEntry.id
})
})
const resultTempGrouped = resultTemp.reduce((acc, curr) => {
if (!acc[curr.gacha_type]) {
acc[curr.gacha_type] = []
}
acc[curr.gacha_type].push(curr)
return acc;
}, {})
const resultTempMap = new Map(Object.entries(resultTempGrouped))
const resultMap = { result: resultTempMap, uid: uidData.uid}
let data
const mergedData = mergeData(dataMap.get(uidData.uid), resultMap)
if (isNew) {
data = { result: mergedData, time: Date.now(), uid: uidData.uid, lang: targetLang, region_time_zone: uidData.timezone, deleted: false }
} else {
data = { result: mergedData, time: Date.now(), uid: dataMap.get(uidData.uid).uid, lang: targetLang, region_time_zone: dataMap.get(uidData.uid).region_time_zone, deleted: dataMap.get(uidData.uid).deleted }
}
saveData(data, '')
changeCurrent(uidData.uid)
dataMap.set(uidData.uid, data)
})
return {
dataMap,
current: config.current
}
} catch (error) {
sendMsg(error, 'ERROR')
console.error(error)
}
}
ipcMain.handle('EXPORT_UIGF_JSON', async (event, uids) => {
await exportUIGF(uids)
})
ipcMain.handle('IMPORT_UIGF_JSON', async () => {
return await importUIGF()
})

View File

@ -29,6 +29,19 @@ const defaultTypeMap = new Map([
['2', '新手跃迁'] ['2', '新手跃迁']
]) ])
const convertTimeZone = (dateTimeStr, fromTimeZoneOffset, toTimeZoneOffset) => {
let date = new Date(dateTimeStr.replace(' ', 'T') + 'Z');
let utcDate = new Date(date.getTime() - fromTimeZoneOffset * 60 * 60 * 1000);
let targetDate = new Date(utcDate.getTime() + toTimeZoneOffset * 60 * 60 * 1000);
let year = targetDate.getUTCFullYear();
let month = String(targetDate.getUTCMonth() + 1).padStart(2, '0');
let day = String(targetDate.getUTCDate()).padStart(2, '0');
let hours = String(targetDate.getUTCHours()).padStart(2, '0');
let minutes = String(targetDate.getUTCMinutes()).padStart(2, '0');
let seconds = String(targetDate.getUTCSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
const findDataFiles = async (dataPath, fileMap) => { const findDataFiles = async (dataPath, fileMap) => {
const files = await readdir(dataPath) const files = await readdir(dataPath)
if (files?.length) { if (files?.length) {
@ -58,6 +71,13 @@ const readData = async () => {
const data = await readJSON(dataPath, name) const data = await readJSON(dataPath, name)
data.typeMap = new Map(data.typeMap) || defaultTypeMap data.typeMap = new Map(data.typeMap) || defaultTypeMap
data.result = new Map(data.result) data.result = new Map(data.result)
data.result.forEach((value, key) => {
value.forEach(item => {
if (!('count' in item)) {
item.count = "1";
}
});
});
if (data.uid) { if (data.uid) {
dataMap.set(data.uid, data) dataMap.set(data.uid, data)
} }
@ -414,13 +434,21 @@ const fetchData = async (urlOverride) => {
const lang = searchParams.get('lang') const lang = searchParams.get('lang')
let originUid = '' let originUid = ''
let originRegion = '' let originRegion = ''
let originTimeZone = '' let localTimeZone
for (const type of gachaType) { for (const type of gachaType) {
const { list, uid, region, region_time_zone } = await getGachaLogs(type, queryString) const { list, uid, region, region_time_zone } = await getGachaLogs(type, queryString)
await sleep(0.3) if (localTimeZone === undefined) {
localTimeZone = dataMap.get(uid)?.region_time_zone
if (localTimeZone === undefined) {
localTimeZone = region_time_zone
}
}
list.forEach(item => {
item.time = convertTimeZone(item.time, region_time_zone, localTimeZone)
})
const logs = list.map((item) => { const logs = list.map((item) => {
const { id, item_id, item_type, name, rank_type, time, gacha_id, gacha_type } = item const { id, item_id, item_type, name, rank_type, time, gacha_id, gacha_type, count} = item
return { id, item_id, item_type, name, rank_type, time, gacha_id, gacha_type } return { id, item_id, item_type, name, rank_type, time, gacha_id, gacha_type, count }
}) })
logs.reverse() logs.reverse()
typeMap.set(type.key, type.name) typeMap.set(type.key, type.name)
@ -431,11 +459,8 @@ const fetchData = async (urlOverride) => {
if (!originRegion) { if (!originRegion) {
originRegion = region originRegion = region
} }
if (!originTimeZone) {
originTimeZone = region_time_zone
} }
} const data = { result, typeMap, time: Date.now(), uid: originUid, lang, region: originRegion, region_time_zone: localTimeZone }
const data = { result, typeMap, time: Date.now(), uid: originUid, lang, region: originRegion, region_time_zone: originTimeZone }
const localData = dataMap.get(originUid) const localData = dataMap.get(originUid)
const mergedResult = mergeData(localData, data) const mergedResult = mergeData(localData, data)
data.result = mergedResult data.result = mergedResult
@ -518,3 +543,6 @@ exports.getData = () => {
exports.getUrl = getUrl exports.getUrl = getUrl
exports.deleteData = deleteData exports.deleteData = deleteData
exports.saveData = saveData
exports.changeCurrent = changeCurrent
exports.convertTimeZone = convertTimeZone

View File

@ -76,7 +76,7 @@ const parseText = (text, data) => {
} }
const mainProps = [ const mainProps = [
'symbol', 'ui', 'log', 'excel',"srgf" 'symbol', 'ui', 'log', 'excel',"uigf"
] ]
const i18n = new Proxy(raw, { const i18n = new Proxy(raw, {

View File

@ -4,7 +4,7 @@ const { disableProxy, proxyStatus } = require('./module/system-proxy')
require('./getData') require('./getData')
require('./bridge') require('./bridge')
require('./excel') require('./excel')
require('./SRGFJson') require('./UIGFJson')
const { getUpdateInfo } = require('./update/index') const { getUpdateInfo } = require('./update/index')
const isDev = !app.isPackaged const isDev = !app.isPackaged

View File

@ -4,17 +4,18 @@
<div class="space-x-3"> <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-dropdown :disabled="!gachaData" @command="exportCommand">
<el-button :disabled="!gachaData" icon="folder-opened" class="focus:outline-none" type="success" plain> <el-button :disabled="!gachaData" icon="download" class="focus:outline-none" type="success" plain>
{{ui.button.files}} {{ui.button.files}}
<el-icon class="el-icon--right"><arrow-down /></el-icon> <el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="excel">{{ui.button.excel}}</el-dropdown-item> <el-dropdown-item command="excel">{{ui.button.excel}}</el-dropdown-item>
<el-dropdown-item command="srgf-json">{{ui.button.srgf}}</el-dropdown-item> <el-dropdown-item command="uigf-json">{{ui.button.uigf}}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </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-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-button @click="newUser()" plain icon="plus" class="focus:outline-none"></el-button>
</el-tooltip> </el-tooltip>
@ -91,7 +92,7 @@ import Setting from './components/Setting.vue'
import gachaDetail from './gachaDetail' import gachaDetail from './gachaDetail'
import { version } from '../../package.json' import { version } from '../../package.json'
import gachaType from '../gachaType.json' import gachaType from '../gachaType.json'
import { ElMessage } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
const state = reactive({ const state = reactive({
status: 'init', status: 'init',
@ -238,15 +239,57 @@ const saveExcel = async () => {
await ipcRenderer.invoke('SAVE_EXCEL') await ipcRenderer.invoke('SAVE_EXCEL')
} }
const exportSRGFJSON = () => { const exportUIGFJSON = () => {
ipcRenderer.invoke('EXPORT_SRGF_JSON') let uidList = []
dataMap.value.forEach(item => {
uidList.push(item.uid)
})
ElMessageBox({
title: state.i18n.ui.uigf.title,
message: `
<div>
${uidList.map(uid => `
<div>
<input type="checkbox" id="${uid}" value="${uid}" />
<label for="${uid}">${uid}</label>
</div>
`).join('')}
</div>
`,
dangerouslyUseHTMLString: true,
showCancelButton: true,
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);
}
done();
}
}).then(() => {
}).catch(() => {
});
}
const importData = async () => {
state.status = 'loading'
const data = await ipcRenderer.invoke('IMPORT_UIGF_JSON')
if (data) {
state.dataMap = data.dataMap
state.current = data.current
state.status = 'loaded'
} else {
state.status = 'failed'
}
} }
const exportCommand = (type) => { const exportCommand = (type) => {
if (type === 'excel') { if (type === 'excel') {
saveExcel() saveExcel()
} else if (type === 'srgf-json') { } else if (type === 'uigf-json') {
exportSRGFJSON() exportUIGFJSON()
} }
} }

View File

@ -54,7 +54,8 @@
</el-form> </el-form>
<h3 class="text-lg my-4">{{about.title}}</h3> <h3 class="text-lg my-4">{{about.title}}</h3>
<p class="text-gray-600 text-xs mt-1">{{about.license}}</p> <p class="text-gray-600 text-xs mt-1">{{about.license}}</p>
<p class="text-gray-600 text-xs mt-1 pb-6">Github: <a @click="openGithub" class="cursor-pointer text-blue-400">https://github.com/biuuu/star-rail-warp-export</a></p> <p class="text-gray-600 text-xs mt-1">Github: <a @click="openGithub" class="cursor-pointer text-blue-400">https://github.com/biuuu/star-rail-warp-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>
<el-dialog v-model="state.showDataDialog" :title="common.dataManage" width="90%"> <el-dialog v-model="state.showDataDialog" :title="common.dataManage" width="90%">
<div class=""> <div class="">
<el-table :data="gachaDataInfo" border stripe> <el-table :data="gachaDataInfo" border stripe>
@ -137,6 +138,7 @@ const disableProxy = async () => {
} }
const openGithub = () => shell.openExternal('https://github.com/biuuu/star-rail-warp-export') const openGithub = () => shell.openExternal('https://github.com/biuuu/star-rail-warp-export')
const openUIGF = () => shell.openExternal('https://uigf.org/')
const openLink = (link) => shell.openExternal(link) const openLink = (link) => shell.openExternal(link)
const deleteData = async (uid, action) => { const deleteData = async (uid, action) => {

67
tools/getIdMap.py Normal file
View File

@ -0,0 +1,67 @@
import requests
import json
from opencc import OpenCC
# 初始化 OpenCC 转换器
cc = OpenCC('s2t')
# 获取 JSON 数据
weapon_url = 'https://api.hakush.in/hsr/data/lightcone.json'
character_url = 'https://api.hakush.in/hsr/data/character.json'
# 语言映射配置
language_map = {
"zh-cn": "cn",
"zh-tw": "cn", # 简体转繁体
"en-us": "en",
"ja-jp": "jp",
"ko-kr": "kr"
}
# 类型映射配置
type_map = {
"weapon": {"zh-cn": "光锥", "zh-tw": "光錐", "en-us": "Light Cone", "ja-jp": "光円錐", "ko-kr": "무기"},
"character": {"zh-cn": "角色", "zh-tw": "角色", "en-us": "Character", "ja-jp": "キャラ", "ko-kr": "캐릭터"}
}
def fetch_json(url):
response = requests.get(url)
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['cn'])
transformed[lang][id] = {
"name": name,
"item_type": type_map[item_type][lang],
"rank_type": item['rank'][-1]
}
return transformed
def main():
try:
weapon_data = fetch_json(weapon_url)
character_data = fetch_json(character_url)
transformed_data = {lang: {} for lang in language_map.keys()}
weapon_transformed = transform_data(weapon_data, "weapon")
character_transformed = transform_data(character_data, "character")
for lang in language_map.keys():
transformed_data[lang].update(weapon_transformed[lang])
transformed_data[lang].update(character_transformed[lang])
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")
except requests.RequestException as e:
print(f"Error fetching data: {e}")
if __name__ == "__main__":
main()