mirror of
https://github.com/earthjasonlin/zzz-signal-search-export.git
synced 2025-04-21 16:00:17 +08:00
Compare commits
11 Commits
dd098fcd08
...
v1.1.3
Author | SHA1 | Date | |
---|---|---|---|
d7457f2bfb
|
|||
223ab899e0
|
|||
f62ca1d7e7
|
|||
c034b2e70a
|
|||
a2faa86f0c
|
|||
510bfdab7a
|
|||
f616944755
|
|||
7300c6e719
|
|||
6fe12da9be
|
|||
14cfda3986
|
|||
8156b5a9b7
|
@ -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
|
||||
})
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"name": "zzz-signal-search-export",
|
||||
"version": "1.0.9",
|
||||
"version": "1.1.3",
|
||||
"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",
|
||||
|
@ -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",
|
||||
|
@ -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": "选项",
|
||||
|
@ -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": "選項",
|
||||
|
2487
src/idJson.json
Normal file
2487
src/idJson.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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,8 +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 (uids) => {
|
||||
const exportUIGF = async (uids) => {
|
||||
const result = {
|
||||
info: {
|
||||
export_timestamp: Math.ceil(Date.now() / 1000),
|
||||
@ -35,15 +37,7 @@ const start = async (uids) => {
|
||||
if (!fulldata.length) {
|
||||
throw new Error('数据为空')
|
||||
}
|
||||
const serverTimeZone = new Map([
|
||||
["prod_gf_cn", 8]
|
||||
])
|
||||
fulldata.forEach(data => {
|
||||
let timezone
|
||||
timezone = serverTimeZone.get(data.region)
|
||||
if(!timezone) {
|
||||
throw new Error('不支持此服务器')
|
||||
}
|
||||
const listTemp = []
|
||||
for (let [type, arr] of data.result) {
|
||||
arr.forEach(log => {
|
||||
@ -63,7 +57,7 @@ const start = async (uids) => {
|
||||
listTemp.sort((a, b) => Number(BigInt(a.id) - BigInt(b.id)))
|
||||
let dataTemp = {
|
||||
uid: data.uid,
|
||||
timezone: timezone,
|
||||
timezone: data.region_time_zone,
|
||||
lang: data.lang,
|
||||
list: []
|
||||
}
|
||||
@ -86,6 +80,95 @@ const start = async (uids) => {
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle('EXPORT_UIGF_JSON', async (event, uids) => {
|
||||
await start(uids)
|
||||
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()
|
||||
})
|
@ -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) {
|
||||
@ -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
|
@ -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>
|
||||
@ -272,6 +273,18 @@ const exportUIGFJSON = () => {
|
||||
});
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (type === 'excel') {
|
||||
saveExcel()
|
||||
|
72
tools/getIdMap.py
Normal file
72
tools/getIdMap.py
Normal file
@ -0,0 +1,72 @@
|
||||
import requests
|
||||
import json
|
||||
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)
|
||||
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()
|
Reference in New Issue
Block a user