Compare commits

...

18 Commits

Author SHA1 Message Date
5dec6a8273 chore: bump version to 1.1.4 2024-07-29 15:44:10 +08:00
0e429a4762 fix: OS servers log path 2024-07-29 15:43:25 +08:00
5164a17dca style: pylint 2024-07-28 10:13:21 +08:00
a660c03bb5 docs: fix old project name 2024-07-27 15:56:13 +08:00
d7457f2bfb chore: bump version to 1.1.3 2024-07-27 15:50:31 +08:00
223ab899e0 fix(uigf): add bangboo to ID table
close: #5
2024-07-27 15:50:02 +08:00
f62ca1d7e7 chore: bump version to 1.1.2 2024-07-26 10:06:44 +08:00
c034b2e70a feat(uigf): metadata from local db 2024-07-26 10:05:02 +08:00
a2faa86f0c chore: bump version to 1.1.1 2024-07-25 21:45:45 +08:00
510bfdab7a feat: add support for Europe, America, TW,HK,MO servers 2024-07-25 21:44:38 +08:00
f616944755 chore: bump version to 1.1.0 2024-07-25 13:47:37 +08:00
7300c6e719 refactor(i18n): UIGF v4.0 is no longer in beta 2024-07-25 13:26:57 +08:00
6fe12da9be feat(uigf): import UIGFv4.0
BREAKING CHANGES: `region` is no longer stored in/read from local file
2024-07-25 13:25:26 +08:00
14cfda3986 chore: bump version to 1.0.10 2024-07-24 21:02:40 +08:00
8156b5a9b7 fix: UIGF prod_gf_jp timezone 2024-07-24 21:00:50 +08:00
dd098fcd08 chore: bump version to 1.0.9 2024-07-23 15:03:52 +08:00
0cdc7662f7 feat(uigf): support multiple account export 2024-07-23 15:03:30 +08:00
2814ed211b fix(uigf): incorrect timezone 2024-07-23 12:51:22 +08:00
12 changed files with 2815 additions and 75 deletions

View File

@ -2,7 +2,7 @@ const fs = require('fs-extra')
const path = require('path')
const crypto = require('crypto')
const AdmZip = require('adm-zip')
const { version } = require('../package.json')
const { version, autoUpdateActive, autoUpdateFrom } = require('../package.json')
const hash = (data, type = 'sha256') => {
const hmac = crypto.createHmac(type, 'nap')
@ -32,9 +32,9 @@ const start = async () => {
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',
active: autoUpdateActive,
version: version,
from: autoUpdateFrom,
name: `${hashName}.zip`,
hash: sha256
})

View File

@ -6,7 +6,7 @@
一个使用 Electron 制作的小工具,需要在 Windows 操作系统上运行。
通过读取游戏日志或者代理模式获取访问游戏跃迁记录 API 所需的 authKey然后再使用获取到的 authKey 来读取游戏跃迁记录。
通过读取游戏日志或者代理模式获取访问游戏调频记录 API 所需的 authKey然后再使用获取到的 authKey 来读取游戏调频记录。
## 其它语言
@ -15,7 +15,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) / [蓝奏云(密码:zzzz](https://www.lanzouh.com/b00eewtvxa)
2. 打开游戏的跃迁详情页面
2. 打开游戏的调频详情页面
![详情页面](/docs/wish-history.jpg)
@ -34,7 +34,7 @@
如果需要导出多个账号的数据,可以点击旁边的加号按钮。
然后游戏切换的新账号,再打开跃迁历史记录,工具再点击“加载数据”按钮。
然后游戏切换的新账号,再打开调频历史记录,工具再点击“加载数据”按钮。
## Devlopment

View File

@ -6,7 +6,7 @@ This project is modified from the [star-rail-warp-export](https://github.com/biu
A tool made from Electron that runs on the Windows operating system.
Read the game log or proxy to get the authKey needed to access the game warp history API, and then use the authKey to read the game wish history.
Read the game log or proxy to get the authKey needed to access the game signal search history API, and then use the authKey to read the game wish history.
## Other languages
@ -18,7 +18,7 @@ If you feel that the existing translation is inappropriate, you can send a pull
1. Unzip after downloading the tool - [GitHub](https://github.com/earthjasonlin/zzz-signal-search-export/releases/latest/download/ZzzSignalSearchExport.zip)
2. Open the warp details page of the game
2. Open the signal search details page of the game
![warp details](/docs/wish-history-en.jpg)

View File

@ -1,6 +1,8 @@
{
"name": "zzz-signal-search-export",
"version": "1.0.8",
"version": "1.1.4",
"autoUpdateActive": true,
"autoUpdateFrom": "1.1.0",
"main": "./dist/electron/main/main.js",
"author": "earthjasonlin <https://git.loliquq.cn/earthjasonlin>",
"homepage": "https://github.com/earthjasonlin/zzz-signal-search-export",

View File

@ -5,7 +5,8 @@
"ui.button.directUpdate": "Direct update",
"ui.button.files": "Export Files",
"ui.button.excel": "Export Excel",
"ui.button.uigf": "Export UIGF (Beta)",
"ui.button.uigf": "Export UIGF",
"ui.button.import": "Import UIGF",
"ui.button.url": "Input URL",
"ui.button.setting": "Settings",
"ui.button.option": "Option",
@ -102,5 +103,6 @@
"uigf.fileType": "Uniformed Interchangeable GachaLog Format v4.0 (Beta)",
"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": "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.files": "导出文件",
"ui.button.excel": "导出Excel",
"ui.button.uigf":"导出UIGF (Beta)",
"ui.button.uigf":"导出UIGF",
"ui.button.import":"导入UIGF",
"ui.button.url": "输入URL",
"ui.button.setting": "设置",
"ui.button.option": "选项",
@ -101,5 +102,6 @@
"uigf.fileType":"统一可交换抽卡记录标准 v4.0Beta",
"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": "URL已复制",
"ui.uigf.title": "请选择要导出的UID"
}

View File

@ -5,7 +5,8 @@
"ui.button.directUpdate": "直接更新",
"ui.button.files": "導出文件",
"ui.button.excel": "導出Excel",
"ui.button.uigf":"導出UIGF (Beta)",
"ui.button.uigf":"導出UIGF",
"ui.button.import":"導入UIGF",
"ui.button.url": "輸入URL",
"ui.button.setting": "設置",
"ui.button.option": "選項",
@ -100,5 +101,6 @@
"uigf.fileType":"統一可交換抽卡記錄標準 v4.0Beta",
"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": "URL已復製",
"ui.uigf.title": "請選擇要導出的UID"
}

2487
src/idJson.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,13 @@
const { app, ipcMain, dialog } = require('electron')
const fs = require('fs-extra')
const path = require('path')
const getData = require('./getData').getData
const { getData, saveData, changeCurrent, convertTimeZone } = require('./getData')
const config = require('./config')
const { name, version } = require('../../package.json')
const i18n = require('./i18n')
const { exit } = require('process')
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)
@ -16,13 +19,7 @@ const formatDate = (date) => {
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 exportUIGF = async (uids) => {
const result = {
info: {
export_timestamp: Math.ceil(Date.now() / 1000),
@ -30,39 +27,49 @@ const start = async () => {
export_app_version: `v${version}`,
version: "v4.0"
},
nap: [
{
uid: current,
timezone: data.region_time_zone,
lang: data.lang,
list: []
}
]
nap: []
}
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
const { dataMap, current } = await getData()
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
})
})
}
listTemp.sort((a, b) => Number(BigInt(a.id) - BigInt(b.id)))
listTemp.forEach(item => {
result.nap[0].list.push({
...item
})
result.nap.push(dataTemp)
})
const filePath = dialog.showSaveDialogSync({
defaultPath: path.join(app.getPath('downloads'), `UIGF_${data.uid}_${getTimeString()}`),
defaultPath: path.join(app.getPath('downloads'), fulldata.length > 1 ? `UIGF_${getTimeString()}` : `UIGF_${fulldata[0].uid}_${getTimeString()}`),
filters: [
{ name: i18n.uigf.fileType, extensions: ['json'] }
]
@ -73,6 +80,95 @@ const start = async () => {
}
}
ipcMain.handle('EXPORT_UIGF_JSON', async () => {
await start()
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.nap.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: 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,27 @@ const defaultTypeMap = new Map([
['5', '邦布频段']
])
const serverTimeZone = new Map([
["prod_gf_cn", 8],
["prod_gf_jp", 8],
["prod_gf_us", -5],
["prod_gf_eu", 1],
["prod_gf_sg", 8]
])
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 files = await readdir(dataPath)
if (files?.length) {
@ -93,7 +114,7 @@ const changeCurrent = async (uid) => {
const detectGameLocale = async (userPath) => {
let list = []
const lang = app.getLocale()
const arr = ['/miHoYo/绝区零/', '/Cognosphere/Zenless Zone Zero/']
const arr = ['/miHoYo/绝区零/', '/miHoYo/ZenlessZoneZero/']
arr.forEach(str => {
try {
const pathname = path.join(userPath, '/AppData/LocalLow/', str, 'Player.log')
@ -203,7 +224,6 @@ const getGachaLogs = async ({ name, key }, queryString) => {
let logs = []
let uid = ''
let region = ''
let region_time_zone = ''
let endId = '0'
const url = `${apiDomain}/common/gacha_record/api/getGachaLog?${queryString}`
do {
@ -221,9 +241,6 @@ const getGachaLogs = async ({ name, key }, queryString) => {
if (!region) {
region = res.region
}
if (!region_time_zone) {
region_time_zone = res.region_time_zone
}
list.push(...logs)
page += 1
@ -252,7 +269,7 @@ const getGachaLogs = async ({ name, key }, queryString) => {
}
}
} while (logs.length > 0)
return { list, uid, region, region_time_zone }
return { list, uid, region }
}
const checkResStatus = (res) => {
@ -425,10 +442,25 @@ const fetchData = async (urlOverride) => {
const typeMap = new Map()
const lang = searchParams.get('lang')
let originUid = ''
let originRegion = ''
let originTimeZone = ''
let localTimeZone
for (const type of gachaType) {
const { list, uid, region, region_time_zone } = await getGachaLogs(type, queryString)
const { list, uid, region} = await getGachaLogs(type, queryString)
const region_time_zone = serverTimeZone.get(region)
if(!region_time_zone) {
sendMsg('不支持此服务器')
console.error('不支持此服务器')
return
}
if (localTimeZone === undefined) {
localTimeZone = dataMap.get(uid)?.region_time_zone
if (localTimeZone === undefined) {
localTimeZone = region_time_zone
}
}
localTimeZone === Number(localTimeZone)
list.forEach(item => {
item.time = convertTimeZone(item.time, region_time_zone, localTimeZone)
})
const logs = list.map((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, count }
@ -439,14 +471,8 @@ const fetchData = async (urlOverride) => {
if (!originUid) {
originUid = uid
}
if (!originRegion) {
originRegion = region
}
if (!originTimeZone) {
originTimeZone = region_time_zone
}
}
const data = { result, typeMap, time: Date.now(), uid: originUid, lang, region: originRegion, region_time_zone: originTimeZone }
const data = { result, typeMap, time: Date.now(), uid: originUid, lang, region_time_zone: localTimeZone }
const localData = dataMap.get(originUid)
const mergedResult = mergeData(localData, data)
data.result = mergedResult
@ -527,5 +553,9 @@ exports.getData = () => {
}
}
exports.serverTimeZone = serverTimeZone
exports.getUrl = getUrl
exports.deleteData = deleteData
exports.saveData = saveData
exports.changeCurrent = changeCurrent
exports.convertTimeZone = convertTimeZone

View File

@ -4,7 +4,7 @@
<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-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}}
<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
@ -15,6 +15,7 @@
</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>
@ -91,7 +92,7 @@ import Setting from './components/Setting.vue'
import gachaDetail from './gachaDetail'
import { version } from '../../package.json'
import gachaType from '../gachaType.json'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
const state = reactive({
status: 'init',
@ -239,7 +240,49 @@ const saveExcel = async () => {
}
const exportUIGFJSON = () => {
ipcRenderer.invoke('EXPORT_UIGF_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) => {
@ -249,7 +292,6 @@ const exportCommand = (type) => {
exportUIGFJSON()
}
}
const openCacheFolder = async () => {
await ipcRenderer.invoke('OPEN_CACHE_FOLDER')
}

77
tools/getIdMap.py Normal file
View File

@ -0,0 +1,77 @@
# pylint: disable=C0116, C0103, C0201
"""Download and process data from the Hakushin API"""
import json
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'
# 语言映射配置
language_map = {
"zh-cn": "CHS",
"zh-tw": "CHS", # 简体转繁体
"en-us": "EN",
"ja-jp": "JA",
"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」"}
}
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'])
transformed[lang][id_] = {
"name": name,
"item_type": type_map[item_type][lang],
"rank_type": item['rank']
}
return transformed
def main():
try:
weapon_data = fetch_json(weapon_url)
character_data = fetch_json(character_url)
bangboo_data = fetch_json(bangboo_url)
transformed_data = {lang: {} for lang in language_map.keys()}
weapon_transformed = transform_data(weapon_data, "weapon")
character_transformed = transform_data(character_data, "character")
bangboo_transformed = transform_data(bangboo_data, "bangboo")
for lang in language_map.keys():
transformed_data[lang].update(weapon_transformed[lang])
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:
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()