Compare commits

...

16 Commits

11 changed files with 105 additions and 62 deletions

View File

@ -35,7 +35,7 @@ const start = async () => {
await fs.outputJSON(path.join(outputPath, 'manifest.json'), {
active: true,
version,
from: '0.1.5',
from: '0.0.1',
name: `${hashName}.zip`,
hash: sha256
})

View File

@ -14,7 +14,7 @@
## 使用说明
1. 下载工具后解压 - 下载地址: [Github](https://github.com/biuuu/star-rail-warp-export/releases/latest/download/StarRailWarpExport.zip) / [蓝奏云]()
1. 下载工具后解压 - 下载地址: [Github](https://github.com/biuuu/star-rail-warp-export/releases/latest/download/StarRailWarpExport.zip) / [蓝奏云](https://wwvt.lanzoum.com/b022mikwh) 密码:f1iy
2. 打开游戏的跃迁历史记录
3. 点击工具的“加载数据”按钮

View File

@ -1,6 +1,6 @@
{
"name": "star-rail-warp-export",
"version": "0.0.1",
"version": "0.0.8",
"main": "./dist/electron/main/main.js",
"author": "biuuu <https://github.com/biuuu>",
"license": "MIT",

View File

@ -83,6 +83,6 @@
"excel.customFont": "微软雅黑",
"excel.filePrefix": "星穹铁道跃迁记录",
"excel.fileType": "Excel文件",
"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/”"
}

View File

@ -1,4 +1,4 @@
const { readJSON, saveJSON, decipherAes, cipherAes, detectLocale } = require('./utils')
const { readJSON, saveJSON, decipherAes, cipherAes, detectLocale, userDataPath, globalUserDataPath } = require('./utils')
const config = {
urls: [],
@ -13,7 +13,10 @@ const config = {
}
const getLocalConfig = async () => {
const localConfig = await readJSON('config.json')
let localConfig = await readJSON(userDataPath, 'config.json')
if (!localConfig) {
localConfig = await readJSON(globalUserDataPath, 'config.json')
}
if (!localConfig) return
const configTemp = {}
for (let key in localConfig) {

View File

@ -3,7 +3,7 @@ const util = require('util')
const path = require('path')
const { URL } = require('url')
const { app, ipcMain, shell } = require('electron')
const { sleep, request, sendMsg, readJSON, saveJSON, detectLocale, userDataPath, userPath, localIp, langMap } = require('./utils')
const { sleep, request, sendMsg, readJSON, saveJSON, detectLocale, userDataPath, userPath, localIp, langMap, globalUserDataPath } = require('./utils')
const config = require('./config')
const i18n = require('./i18n')
const { enableProxy, disableProxy } = require('./module/system-proxy')
@ -18,7 +18,6 @@ const saveData = async (data, url) => {
const obj = Object.assign({}, data)
obj.result = [...obj.result]
obj.typeMap = [...obj.typeMap]
config.urls.set(data.uid, url)
await config.save()
await saveJSON(`gacha-list-${data.uid}.json`, obj)
}
@ -30,25 +29,42 @@ const defaultTypeMap = new Map([
['2', '始发跃迁']
])
const findDataFiles = async (dataPath, fileMap) => {
const files = await readdir(dataPath)
if (files?.length) {
for (let name of files) {
if (/^gacha-list-\d+\.json$/.test(name) && !fileMap.has(name)) {
fileMap.set(name, dataPath)
}
}
}
}
const collectDataFiles = async () => {
await fs.ensureDir(userDataPath)
await fs.ensureDir(globalUserDataPath)
const fileMap = new Map()
await findDataFiles(userDataPath, fileMap)
await findDataFiles(globalUserDataPath, fileMap)
return fileMap
}
let localDataReaded = false
const readdir = util.promisify(fs.readdir)
const readData = async () => {
if (localDataReaded) return
localDataReaded = true
await fs.ensureDir(userDataPath)
const files = await readdir(userDataPath)
for (let name of files) {
if (/^gacha-list-\d+\.json$/.test(name)) {
try {
const data = await readJSON(name)
data.typeMap = new Map(data.typeMap) || defaultTypeMap
data.result = new Map(data.result)
if (data.uid) {
dataMap.set(data.uid, data)
}
} catch (e) {
sendMsg(e, 'ERROR')
const fileMap = await collectDataFiles()
for (let [name, dataPath] of fileMap) {
try {
const data = await readJSON(dataPath, name)
data.typeMap = new Map(data.typeMap) || defaultTypeMap
data.result = new Map(data.result)
if (data.uid) {
dataMap.set(data.uid, data)
}
} catch (e) {
sendMsg(e, 'ERROR')
}
}
if ((!config.current && dataMap.size) || (config.current && dataMap.size && !dataMap.has(config.current))) {
@ -85,17 +101,17 @@ const detectGameLocale = async (userPath) => {
const getLatestUrl = (list) => {
let result = list[list.length - 1]
let time = 0
for (let i = 0; i < list.length; i++) {
const tsMch = list[i].match(/timestamp=(\d+)/)
if (tsMch?.[1]) {
const ts = parseInt(tsMch[1])
if (time < parseInt(tsMch[1])) {
time = ts
result = list[i]
}
}
}
// let time = 0
// for (let i = 0; i < list.length; i++) {
// const tsMch = list[i].match(/timestamp=(\d+)/)
// if (tsMch?.[1]) {
// const ts = parseInt(tsMch[1])
// if (time <= parseInt(tsMch[1])) {
// time = ts
// result = list[i]
// }
// }
// }
return result
}
@ -116,10 +132,13 @@ const readLog = async () => {
}
const promises = logPaths.map(async logpath => {
const logText = await fs.readFile(logpath, 'utf8')
const gamePathMch = logText.match(/\w:\/.+(Star\sRail\/Game\/StarRail_Data)/)
const gamePathMch = logText.match(/\w:\/.*?\/StarRail_Data\//)
if (gamePathMch) {
const cacheText = await fs.readFile(path.join(gamePathMch[0], '/webCaches/Cache/Cache_Data/data_2'), 'utf8')
const urlMch = cacheText.match(/https.+?&auth_appid=webview_gacha&.+?authkey=.+?&game_biz=hkrpg_.+/g)
let cacheText = ''
try {
cacheText = await fs.readFile(path.join(gamePathMch[0], '/webCaches/Cache/Cache_Data/data_2'), 'utf8')
} catch (e) {}
const urlMch = cacheText.match(/https.+?&auth_appid=webview_gacha&.+?authkey=.+?&game_biz=hkrpg_.+?&plat_type=pc/g)
if (urlMch) {
cacheFolder = path.join(gamePathMch[0], '/webCaches/Cache/')
return getLatestUrl(urlMch)
@ -236,7 +255,6 @@ const tryGetUid = async (queryString) => {
try {
for (let [key] of defaultTypeMap) {
const res = await request(`${url}&gacha_type=${key}&page=1&size=6`)
checkResStatus(res)
if (res.data.list && res.data.list.length) {
return res.data.list[0].uid
}
@ -334,10 +352,7 @@ const tryRequest = async (url, retry = false) => {
const gachaTypeUrl = `${apiDomain}/common/gacha_record/api/getGachaLog?${queryString}&page=1&size=5&gacha_type=1&end_id=0`
try {
const res = await request(gachaTypeUrl)
if (res.retcode !== 0) {
return false
}
return true
checkResStatus(res)
} catch (e) {
if (e.code === 'ERR_PROXY_CONNECTION_FAILED' && !retry) {
await disableProxy()
@ -352,11 +367,6 @@ const getUrl = async () => {
let url = await readLog()
if (!url && config.proxyMode) {
url = await useProxy()
} else if (url) {
const result = await tryRequest(url)
if (!result && config.proxyMode) {
url = await useProxy()
}
}
return url
}
@ -373,6 +383,9 @@ const fetchData = async (urlOverride) => {
sendMsg(message)
throw new Error(message)
}
await tryRequest(url)
const searchParams = getQuerystring(url)
if (!searchParams) {
const message = text.url.incorrect
@ -457,7 +470,7 @@ ipcMain.handle('READ_DATA', async () => {
})
ipcMain.handle('CHANGE_UID', (event, uid) => {
config.current = uid
changeCurrent(uid)
})
ipcMain.handle('GET_CONFIG', () => {

View File

@ -42,14 +42,24 @@ const parseData = (data) => {
return result
}
const assignData = (objA, objB) => {
const temp = { ...objA }
for (let key in objB) {
if (objB[key]) {
temp[key] = objB[key]
}
}
return temp
}
const i18nMap = new Map()
const prepareData = () => {
for (let key in raw) {
let temp = {}
if (key === 'zh-tw') {
Object.assign(temp, raw['zh-cn'], raw[key])
temp = assignData(raw['zh-cn'], raw[key])
} else {
Object.assign(temp, raw['zh-cn'], raw['en-us'], raw[key])
temp = assignData(raw['zh-cn'], assignData(raw['en-us'], raw[key]))
}
i18nMap.set(key, parseData(temp))
}

View File

@ -11,8 +11,9 @@ const Registry = require('winreg')
const isDev = !app.isPackaged
const userPath = app.getPath('userData')
const appRoot = isDev ? path.resolve(__dirname, '..', '..') : userPath
const appRoot = isDev ? path.resolve(__dirname, '..', '..') : path.resolve(app.getAppPath(), '..', '..')
const userDataPath = path.resolve(appRoot, 'userData')
const globalUserDataPath = path.resolve(userPath, 'userData')
let win = null
const initWindow = () => {
@ -56,7 +57,7 @@ const saveLog = () => {
const text = item[2]
return `[${type}][${time}]${text}`
}).join('\r\n')
fs.outputFileSync(path.join(userDataPath, 'log.txt'), text)
fs.outputFile(path.join(userDataPath, 'log.txt'), text)
}
const authkeyMask = (text = '') => {
@ -144,19 +145,20 @@ const detectLocale = (value) => {
const saveJSON = async (name, data) => {
try {
await fs.outputJSON(path.join(userDataPath, name), data, {
spaces: 2
})
await fs.outputJSON(path.join(userDataPath, name), data)
if (!isDev) {
await fs.outputJSON(path.join(globalUserDataPath, name), data)
}
} catch (e) {
sendMsg(e, 'ERROR')
await sleep(3)
}
}
const readJSON = async (name) => {
const readJSON = async (dataPath, name) => {
let data = null
try {
data = await fs.readJSON(path.join(userDataPath, name))
data = await fs.readJSON(path.join(dataPath, name))
} catch (e) {}
return data
}
@ -203,5 +205,5 @@ const localIp = () => {
module.exports = {
sleep, request, hash, cipherAes, decipherAes, saveLog,
sendMsg, readJSON, saveJSON, initWindow, getWin, localIp, userPath, detectLocale, langMap,
appRoot, userDataPath
appRoot, userDataPath, globalUserDataPath
}

View File

@ -55,10 +55,12 @@
</template>
</el-dialog>
<el-dialog :title="ui.button.solution" v-model="state.showCacheCleanDlg" width="90%" custom-class="max-w-md">
<el-button plain icon="folder" type="primary" @click="openCacheFolder">{{ui.button.cacheFolder}}</el-button>
<p class="my-4 leading-2 text-gray-600 text-sm whitespace-pre-line">{{ui.extra.cacheClean}}</p>
<p class="my-2 text-gray-400 text-xs">{{ui.extra.findCacheFolder}}</p>
<el-dialog :title="ui.button.solution" v-model="state.showCacheCleanDlg" width="90%" custom-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>
@ -99,6 +101,13 @@ const ui = computed(() => {
}
})
const cacheCleanTextList = computed(() => {
if (ui.value) {
return ui.value.extra?.cacheClean?.split('\n')
}
return []
})
const gachaData = computed(() => {
return state.dataMap.get(state.current)
})

View File

@ -13,4 +13,10 @@
body::-webkit-scrollbar-thumb {
@apply rounded-full bg-gray-300;
}
}
@layer utilities {
.cache-clean-dialog .el-dialog__body {
padding: 0 20px;
}
}

View File

@ -1,11 +1,11 @@
import * as IconComponents from '@element-plus/icons-vue'
const weaponTypeNames = new Set([
'光锥', 'Light Cone', '光錐', 'Lichtkegel', 'Conos de luz', 'cônes de lumière', '光円錐', '광추', 'Cones de Luz', 'Световые конусы', 'Nón Ánh Sáng'
'光锥', '光錐', 'Lichtkegel', 'Light Cone', 'Conos de luz', 'cônes de lumière', '光円錐', '광추', 'Cones de Luz', 'Световые конусы', 'Nón Ánh Sáng'
])
const characterTypeNames = new Set([
'角色', 'Character', '캐릭터', 'キャラクター', 'Personaje', 'Personnage', 'Персонажи', 'ตัวละคร', 'Nhân Vật', 'Figur', 'Karakter', 'Personagem'
'角色', 'Figur', 'Character', 'Personajes', 'Personnages', 'Karakter', 'キャラクター', '캐릭터', 'Personagens', 'Персонажи', 'ตัวละคร', 'Nhân Vật'
])
const isCharacter = (name) => characterTypeNames.has(name)