Compare commits

...

10 Commits

19 changed files with 737 additions and 572 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: https://afdian.net/a/haisha

View File

@ -1,31 +0,0 @@
name: Build Update
on:
workflow_dispatch:
push:
branches: [ main ]
jobs:
main:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: Build Update
run: |
yarn --frozen-lockfile
yarn build:dir
yarn build-update
- name: Deploy
if: success()
uses: crazy-max/ghaction-github-pages@v2
with:
commit_message: Update app
build_dir: ./build/update
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,6 +1,6 @@
{ {
"name": "star-rail-warp-export", "name": "star-rail-warp-export",
"version": "0.0.16", "version": "0.1.2",
"main": "./dist/electron/main/main.js", "main": "./dist/electron/main/main.js",
"author": "biuuu <https://github.com/biuuu>", "author": "biuuu <https://github.com/biuuu>",
"license": "MIT", "license": "MIT",
@ -71,7 +71,7 @@
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@element-plus/icons-vue": "^0.2.6", "@element-plus/icons-vue": "^2.1.0",
"@rollup/plugin-alias": "^3.1.9", "@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",
@ -87,13 +87,14 @@
"del": "^6.0.0", "del": "^6.0.0",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"electron": "^16.0.7", "electron": "^16.0.7",
"electron-builder": "^22.14.5", "electron-builder": "^23.0.2",
"electron-fetch": "^1.7.4", "electron-fetch": "^1.7.4",
"electron-unhandled": "^3.0.2", "electron-unhandled": "^3.0.2",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"element-plus": "^1.3.0-beta.7", "element-plus": "^2.3.7",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"get-stream": "^6.0.1", "get-stream": "^6.0.1",
"glob": "^10.3.3",
"jest": "^29.5.0", "jest": "^29.5.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"moment": "^2.29.1", "moment": "^2.29.1",

View File

@ -3,6 +3,7 @@
"ui.button.load": "Lade Daten", "ui.button.load": "Lade Daten",
"ui.button.update": "Aktualisieren", "ui.button.update": "Aktualisieren",
"ui.button.excel": "In Excel exportieren", "ui.button.excel": "In Excel exportieren",
"ui.button.srgf": "In JSON exportieren",
"ui.button.url": "Eingabe URL", "ui.button.url": "Eingabe URL",
"ui.button.setting": "Einstellungen", "ui.button.setting": "Einstellungen",
"ui.button.option": "Optionen", "ui.button.option": "Optionen",
@ -75,5 +76,6 @@
"excel.wish2": "Wunsch 2", "excel.wish2": "Wunsch 2",
"excel.customFont": "Arial", "excel.customFont": "Arial",
"excel.filePrefix": "", "excel.filePrefix": "",
"excel.fileType": "Excel Datei" "excel.fileType": "Excel Datei",
"srgf.fileType": "Star Rail Gacha Log Format Datei"
} }

View File

@ -3,13 +3,16 @@
"ui.button.load": "Load data", "ui.button.load": "Load data",
"ui.button.update": "Update", "ui.button.update": "Update",
"ui.button.directUpdate": "Direct update", "ui.button.directUpdate": "Direct update",
"ui.button.files": "Export Files",
"ui.button.excel": "Export Excel", "ui.button.excel": "Export Excel",
"ui.button.srgf": "Export JSON",
"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",
"ui.button.startProxy": "Proxy mode", "ui.button.startProxy": "Proxy mode",
"ui.button.solution": "Solution", "ui.button.solution": "Solution",
"ui.button.cacheFolder": "Open cache folder", "ui.button.cacheFolder": "Open cache folder",
"ui.button.copyUrl": "Copy URL",
"ui.select.newAccount": "New account", "ui.select.newAccount": "New account",
"ui.hint.newAccount": "Export data from other accounts", "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", "ui.hint.init": "Please open your warp history inside the game client before clicking on the 'Load data' button",
@ -41,6 +44,7 @@
"ui.setting.cnServer": "CN server", "ui.setting.cnServer": "CN server",
"ui.setting.seaServer": "Global server", "ui.setting.seaServer": "Global server",
"ui.setting.logTypeHint": "Choose which server generated logs to be used first when acquiring URL from game logs", "ui.setting.logTypeHint": "Choose which server generated logs to be used first when acquiring URL from game logs",
"ui.setting.dataManagerHint": "Unnecessary data can be deleted",
"ui.setting.autoUpdate": "Auto update", "ui.setting.autoUpdate": "Auto update",
"ui.setting.hideNovice": "Hide Starter Warp", "ui.setting.hideNovice": "Hide Starter Warp",
"ui.setting.proxyMode": "Proxy mode", "ui.setting.proxyMode": "Proxy mode",
@ -56,6 +60,15 @@
"ui.urlDialog.placeholder": "Please enter the URL with authentication information", "ui.urlDialog.placeholder": "Please enter the URL with authentication information",
"ui.common.cancel": "Cancel", "ui.common.cancel": "Cancel",
"ui.common.ok": "OK", "ui.common.ok": "OK",
"ui.common.data": "Data",
"ui.common.dataManage": "Data Management",
"ui.common.updateTime": "Update Date",
"ui.common.status": "Status",
"ui.common.action": "Operation",
"ui.common.deleted": "Deleted",
"ui.common.normal": "Normal",
"ui.common.delete": "Delete",
"ui.common.restore": "Restore",
"log.save.failed": "Failed to save local data", "log.save.failed": "Failed to save local data",
"log.file.notFound": "Unable to find game logs, please make sure you already opened warp history inside the game client", "log.file.notFound": "Unable to find game logs, please make sure you already opened warp history inside the game client",
"log.url.notFound": "Unable to find URL", "log.url.notFound": "Unable to find URL",
@ -83,6 +96,8 @@
"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",
"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"
} }

View File

@ -4,6 +4,7 @@
"ui.button.update": "Mettre à jour", "ui.button.update": "Mettre à jour",
"ui.button.directUpdate": "Mise à jour directe", "ui.button.directUpdate": "Mise à jour directe",
"ui.button.excel": "Exporter vers Excel", "ui.button.excel": "Exporter vers Excel",
"ui.button.srgf": "Exporter vers JSON",
"ui.button.url": "URL d'import", "ui.button.url": "URL d'import",
"ui.button.setting": "Paramètres", "ui.button.setting": "Paramètres",
"ui.button.option": "Options", "ui.button.option": "Options",

View File

@ -3,6 +3,7 @@
"ui.button.load": "データの読み込み", "ui.button.load": "データの読み込み",
"ui.button.update": "更新データ", "ui.button.update": "更新データ",
"ui.button.excel": "Excelにエクスポート", "ui.button.excel": "Excelにエクスポート",
"ui.button.srgf": "JSONにエクスポート",
"ui.button.url": "URL入力", "ui.button.url": "URL入力",
"ui.button.setting": "設定", "ui.button.setting": "設定",
"ui.button.option": "オプション", "ui.button.option": "オプション",

View File

@ -3,13 +3,16 @@
"ui.button.load": "加载数据", "ui.button.load": "加载数据",
"ui.button.update": "更新数据", "ui.button.update": "更新数据",
"ui.button.directUpdate": "直接更新", "ui.button.directUpdate": "直接更新",
"ui.button.files": "导出文件",
"ui.button.excel": "导出Excel", "ui.button.excel": "导出Excel",
"ui.button.srgf":"导出JSON",
"ui.button.url": "输入URL", "ui.button.url": "输入URL",
"ui.button.setting": "设置", "ui.button.setting": "设置",
"ui.button.option": "选项", "ui.button.option": "选项",
"ui.button.startProxy": "代理模式", "ui.button.startProxy": "代理模式",
"ui.button.solution": "解决办法", "ui.button.solution": "解决办法",
"ui.button.cacheFolder": "打开网页缓存文件夹", "ui.button.cacheFolder": "打开网页缓存文件夹",
"ui.button.copyUrl": "复制URL",
"ui.select.newAccount": "新账号", "ui.select.newAccount": "新账号",
"ui.hint.newAccount": "从其它账号导出数据", "ui.hint.newAccount": "从其它账号导出数据",
"ui.hint.init": "请先在游戏里打开任意一个抽卡记录后再点击“加载数据”按钮", "ui.hint.init": "请先在游戏里打开任意一个抽卡记录后再点击“加载数据”按钮",
@ -41,6 +44,7 @@
"ui.setting.cnServer": "国服", "ui.setting.cnServer": "国服",
"ui.setting.seaServer": "外服", "ui.setting.seaServer": "外服",
"ui.setting.logTypeHint": "使用游戏日志获取URL时优先选择哪种服务器生成的日志文件。", "ui.setting.logTypeHint": "使用游戏日志获取URL时优先选择哪种服务器生成的日志文件。",
"ui.setting.dataManagerHint": "可以删除不需要的数据。",
"ui.setting.autoUpdate": "自动更新", "ui.setting.autoUpdate": "自动更新",
"ui.setting.hideNovice": "隐藏新手跃迁", "ui.setting.hideNovice": "隐藏新手跃迁",
"ui.setting.proxyMode": "代理模式", "ui.setting.proxyMode": "代理模式",
@ -56,6 +60,15 @@
"ui.urlDialog.placeholder": "请输入带有身份认证信息的URL", "ui.urlDialog.placeholder": "请输入带有身份认证信息的URL",
"ui.common.cancel": "取消", "ui.common.cancel": "取消",
"ui.common.ok": "确定", "ui.common.ok": "确定",
"ui.common.data": "数据",
"ui.common.dataManage": "数据管理",
"ui.common.updateTime": "更新日期",
"ui.common.status": "状态",
"ui.common.action": "操作",
"ui.common.deleted": "已删除",
"ui.common.normal": "正常",
"ui.common.delete": "删除",
"ui.common.restore": "恢复",
"log.save.failed": "保存本地数据失败", "log.save.failed": "保存本地数据失败",
"log.file.notFound": "未找到游戏日志,确认是否已打开游戏抽卡记录", "log.file.notFound": "未找到游戏日志,确认是否已打开游戏抽卡记录",
"log.url.notFound": "未找到URL", "log.url.notFound": "未找到URL",
@ -83,6 +96,8 @@
"excel.customFont": "微软雅黑", "excel.customFont": "微软雅黑",
"excel.filePrefix": "星穹铁道跃迁记录", "excel.filePrefix": "星穹铁道跃迁记录",
"excel.fileType": "Excel文件", "excel.fileType": "Excel文件",
"srgf.fileType":"星穹铁道跃迁记录格式文件",
"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已复制"
} }

View File

@ -3,7 +3,9 @@
"ui.button.load": "載入資料", "ui.button.load": "載入資料",
"ui.button.update": "更新資料", "ui.button.update": "更新資料",
"ui.button.directUpdate": "直接更新", "ui.button.directUpdate": "直接更新",
"ui.button.files": "匯出檔案",
"ui.button.excel": "匯出 Excel", "ui.button.excel": "匯出 Excel",
"ui.button.srgf": "匯出 JSON",
"ui.button.url": "輸入 URL", "ui.button.url": "輸入 URL",
"ui.button.setting": "設定", "ui.button.setting": "設定",
"ui.button.option": "選項", "ui.button.option": "選項",
@ -83,6 +85,7 @@
"excel.customFont": "微軟正黑體", "excel.customFont": "微軟正黑體",
"excel.filePrefix": "星穹鐵道躍遷紀錄", "excel.filePrefix": "星穹鐵道躍遷紀錄",
"excel.fileType": "Excel 檔案", "excel.fileType": "Excel 檔案",
"srgf.fileType":"星穹鐵道躍遷紀錄格式檔案",
"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/」"
} }

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra')
const path = require('path') const path = require('path')
const getData = require('./getData').getData const getData = require('./getData').getData
const { version } = require('../../package.json') const { version } = require('../../package.json')
const i18n = require('./i18n')
const getTimeString = () => { const getTimeString = () => {
return new Date().toLocaleString('sv').replace(/[- :]/g, '').slice(0, -2) return new Date().toLocaleString('sv').replace(/[- :]/g, '').slice(0, -2)
@ -15,67 +16,51 @@ const formatDate = (date) => {
return `${y}-${m}-${d} ${date.toLocaleString('zh-cn', { hour12: false }).slice(-8)}` return `${y}-${m}-${d} ${date.toLocaleString('zh-cn', { hour12: false }).slice(-8)}`
} }
const fakeIdFn = () => {
let id = 1000000000000000000n
return () => {
id = id + 1n
return id.toString()
}
}
const shouldBeString = (value) => {
if (typeof value !== 'string') {
return ''
}
return value
}
const start = async () => { const start = async () => {
const { dataMap, current } = await getData() const { dataMap, current } = await getData()
const data = dataMap.get(current) const data = dataMap.get(current)
if (!data.result.size) { if (!data.result.size) {
throw new Error('数据为空') throw new Error('数据为空')
} }
const fakeId = fakeIdFn()
const result = { const result = {
info: { info: {
uid: data.uid, uid: data.uid,
lang: data.lang, lang: data.lang,
export_time: formatDate(new Date()), export_time: formatDate(new Date()),
export_timestamp: Date.now(), export_timestamp: Math.ceil(Date.now() / 1000),
export_app: 'genshin-wish-export', export_app: 'star-rail-warp-export',
export_app_version: `v${version}`, export_app_version: `v${version}`,
uigf_version: 'v2.2' region_time_zone: data.region_time_zone,
srgf_version: 'v1.0'
}, },
list: [] list: []
} }
const listTemp = [] const listTemp = []
for (let [type, arr] of data.result) { for (let [type, arr] of data.result) {
arr.forEach(item => { arr.forEach(log => {
listTemp.push({ listTemp.push({
gacha_type: shouldBeString(item[4]) || type, gacha_id: log.gacha_id,
time: item[0], gacha_type: log.gacha_type,
timestamp: new Date(item[0]).getTime(), item_id: log.item_id,
name: item[1], count: '1',
item_type: item[2], time: log.time,
rank_type: `${item[3]}`, name: log.name,
id: shouldBeString(item[5]) || '', item_type: log.item_type,
uigf_gacha_type: type rank_type: log.rank_type,
id: log.id
}) })
}) })
} }
listTemp.sort((a, b) => a.timestamp - b.timestamp) listTemp.sort((a, b) => Number(BigInt(a.id) - BigInt(b.id)))
listTemp.forEach(item => { listTemp.forEach(item => {
delete item.timestamp
result.list.push({ result.list.push({
...item, ...item
id: item.id || fakeId()
}) })
}) })
const filePath = dialog.showSaveDialogSync({ const filePath = dialog.showSaveDialogSync({
defaultPath: path.join(app.getPath('downloads'), `UIGF_${data.uid}_${getTimeString()}`), defaultPath: path.join(app.getPath('downloads'), `SRGF_${data.uid}_${getTimeString()}`),
filters: [ filters: [
{ name: 'JSON文件', extensions: ['json'] } { name: i18n.srgf.fileType, extensions: ['json'] }
] ]
}) })
if (filePath) { if (filePath) {
@ -84,6 +69,6 @@ const start = async () => {
} }
} }
ipcMain.handle('EXPORT_UIGF_JSON', async () => { ipcMain.handle('EXPORT_SRGF_JSON', async () => {
await start() await start()
}) })

15
src/main/bridge.js Normal file
View File

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

View File

@ -3,7 +3,7 @@ const util = require('util')
const path = require('path') const path = require('path')
const { URL } = require('url') const { URL } = require('url')
const { app, ipcMain, shell } = require('electron') const { app, ipcMain, shell } = require('electron')
const { sleep, request, sendMsg, readJSON, saveJSON, detectLocale, userDataPath, userPath, localIp, langMap, globalUserDataPath } = require('./utils') const { sleep, request, sendMsg, readJSON, saveJSON, detectLocale, getCacheText, userDataPath, userPath, localIp, langMap, globalUserDataPath } = require('./utils')
const config = require('./config') const config = require('./config')
const i18n = require('./i18n') const i18n = require('./i18n')
const { enableProxy, disableProxy } = require('./module/system-proxy') const { enableProxy, disableProxy } = require('./module/system-proxy')
@ -72,6 +72,14 @@ const readData = async () => {
} }
} }
const deleteData = async (uid, action) => {
const data = dataMap.get(uid)
if (data) {
data.deleted = action
await saveData(data)
}
}
const changeCurrent = async (uid) => { const changeCurrent = async (uid) => {
config.current = uid config.current = uid
await config.save() await config.save()
@ -134,13 +142,10 @@ const readLog = async () => {
const logText = await fs.readFile(logpath, 'utf8') const logText = await fs.readFile(logpath, 'utf8')
const gamePathMch = logText.match(/\w:\/.*?\/StarRail_Data\//i) const gamePathMch = logText.match(/\w:\/.*?\/StarRail_Data\//i)
if (gamePathMch) { if (gamePathMch) {
let cacheText = '' const [cacheText, cacheFile] = await getCacheText(gamePathMch[0])
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) const urlMch = cacheText.match(/https.+?&auth_appid=webview_gacha&.+?authkey=.+?&game_biz=hkrpg_.+?&plat_type=pc/g)
if (urlMch) { if (urlMch) {
cacheFolder = path.join(gamePathMch[0], '/webCaches/Cache/') cacheFolder = cacheFile.replace(/Cache_Data[/\\]data_2$/, '')
return getLatestUrl(urlMch) return getLatestUrl(urlMch)
} }
} }
@ -163,7 +168,10 @@ const getGachaLog = async ({ key, page, name, retryCount, url, endId }) => {
const text = i18n.log const text = i18n.log
try { try {
const res = await request(`${url}&gacha_type=${key}&page=${page}&size=${20}${endId ? '&end_id=' + endId : ''}`) const res = await request(`${url}&gacha_type=${key}&page=${page}&size=${20}${endId ? '&end_id=' + endId : ''}`)
if (res?.data?.list) {
return res?.data return res?.data
}
throw new Error(res?.message || res)
} catch (e) { } catch (e) {
if (retryCount) { if (retryCount) {
sendMsg(i18n.parse(text.fetch.retry, { name, page, count: 6 - retryCount })) sendMsg(i18n.parse(text.fetch.retry, { name, page, count: 6 - retryCount }))
@ -508,3 +516,6 @@ exports.getData = () => {
current: config.current current: config.current
} }
} }
exports.getUrl = getUrl
exports.deleteData = deleteData

View File

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

View File

@ -2,8 +2,9 @@ const { app, BrowserWindow, ipcMain } = require('electron')
const { initWindow } = require('./utils') const { initWindow } = require('./utils')
const { disableProxy, proxyStatus } = require('./module/system-proxy') const { disableProxy, proxyStatus } = require('./module/system-proxy')
require('./getData') require('./getData')
require('./bridge')
require('./excel') require('./excel')
require('./UIGFJson') require('./SRGFJson')
const { getUpdateInfo } = require('./update/index') const { getUpdateInfo } = require('./update/index')
const isDev = !app.isPackaged const isDev = !app.isPackaged

View File

@ -6,6 +6,7 @@ const crypto = require('crypto')
const unhandled = require('electron-unhandled') const unhandled = require('electron-unhandled')
const windowStateKeeper = require('electron-window-state') const windowStateKeeper = require('electron-window-state')
const debounce = require('lodash/debounce') const debounce = require('lodash/debounce')
const { glob } = require('glob')
const isDev = !app.isPackaged const isDev = !app.isPackaged
@ -202,8 +203,23 @@ const localIp = () => {
return '127.0.0.1' return '127.0.0.1'
} }
async function getCacheText(gamePath) {
const results = await glob(path.join(gamePath, '/webCaches{/,/*/}Cache/Cache_Data/data_2'), {
stat: true,
withFileTypes: true,
nodir: true,
windowsPathsNoEscape: true
})
const timeSortedFiles = results
.sort((a, b) => b.mtimeMs - a.mtimeMs)
.map(path => path.fullpath())
const cacheText = await fs.readFile(path.join(timeSortedFiles[0]), 'utf8')
return [cacheText, timeSortedFiles[0]]
}
module.exports = { module.exports = {
sleep, request, hash, cipherAes, decipherAes, saveLog, sleep, request, hash, cipherAes, decipherAes, saveLog, getCacheText,
sendMsg, readJSON, saveJSON, initWindow, getWin, localIp, userPath, detectLocale, langMap, sendMsg, readJSON, saveJSON, initWindow, getWin, localIp, userPath, detectLocale, langMap,
appRoot, userDataPath, globalUserDataPath appRoot, userDataPath, globalUserDataPath
} }

View File

@ -1,9 +1,20 @@
<template> <template>
<div v-if="ui" class="relative"> <div v-if="ui" class="relative">
<div class="flex justify-between"> <div class="flex justify-between">
<div> <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-button icon="folder-opened" @click="saveExcel" class="focus:outline-none" :disabled="!gachaData" type="success" plain>{{ui.button.excel}}</el-button> <el-dropdown :disabled="!gachaData" @command="exportCommand">
<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="srgf-json">{{ui.button.srgf}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<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>
@ -12,9 +23,9 @@
</el-tooltip> </el-tooltip>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<el-select v-if="state.status !== 'loading' && state.dataMap && (state.dataMap.size > 1 || (state.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 <el-option
v-for="item of state.dataMap" v-for="item of dataMap"
:key="item[0]" :key="item[0]"
:label="maskUid(item[0])" :label="maskUid(item[0])"
:value="item[0]"> :value="item[0]">
@ -26,6 +37,7 @@
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="setting" icon="setting">{{ui.button.setting}}</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 :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 :disabled="!allowClick() || state.status === 'loading'" command="proxy" icon="position">{{ui.button.startProxy}}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@ -42,9 +54,9 @@
</div> </div>
</div> </div>
</div> </div>
<Setting v-show="state.showSetting" :i18n="state.i18n" @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%" custom-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> <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> <template #footer>
@ -55,7 +67,7 @@
</template> </template>
</el-dialog> </el-dialog>
<el-dialog :title="ui.button.solution" v-model="state.showCacheCleanDlg" width="90%" custom-class="max-w-md cache-clean-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-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]"> <p class="my-2 flex flex-col text-teal-800 text-[13px]">
<span class="my-1" v-for="txt of cacheCleanTextList">{{ txt }}</span> <span class="my-1" v-for="txt of cacheCleanTextList">{{ txt }}</span>
@ -67,7 +79,6 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -80,6 +91,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'
const state = reactive({ const state = reactive({
status: 'init', status: 'init',
@ -96,6 +108,26 @@ const state = reactive({
config: {} config: {}
}) })
const dataMap = computed(() => {
const result = new Map()
for (let [uid, data] of state.dataMap) {
if (!data.deleted) {
result.set(uid, data)
}
}
return result
})
const dataInfo = computed(() => {
const result = []
for (let [uid, data] of state.dataMap) {
result.push({
uid, time: data.time, deleted: data.deleted
})
}
return result
})
const ui = computed(() => { const ui = computed(() => {
if (state.i18n) { if (state.i18n) {
return state.i18n.ui return state.i18n.ui
@ -124,7 +156,7 @@ const uidSelectText = computed(() => {
const allowClick = () => { const allowClick = () => {
const data = state.dataMap.get(state.current) const data = state.dataMap.get(state.current)
if (!data) return true if (!data) return true
if (Date.now() - data.time < 1000 * 60) { if (Date.now() - data.time < 1000 * 10) {
return false return false
} }
return true return true
@ -152,7 +184,7 @@ const hint = computed(() => {
}) })
const detail = computed(() => { const detail = computed(() => {
const data = state.dataMap.get(state.current) const data = dataMap.value.get(state.current)
if (data) { if (data) {
return gachaDetail(data.result) return gachaDetail(data.result)
} }
@ -206,6 +238,18 @@ const saveExcel = async () => {
await ipcRenderer.invoke('SAVE_EXCEL') await ipcRenderer.invoke('SAVE_EXCEL')
} }
const exportSRGFJSON = () => {
ipcRenderer.invoke('EXPORT_SRGF_JSON')
}
const exportCommand = (type) => {
if (type === 'excel') {
saveExcel()
} else if (type === 'srgf-json') {
exportSRGFJSON()
}
}
const openCacheFolder = async () => { const openCacheFolder = async () => {
await ipcRenderer.invoke('OPEN_CACHE_FOLDER') await ipcRenderer.invoke('OPEN_CACHE_FOLDER')
} }
@ -249,6 +293,8 @@ const optionCommand = (type) => {
state.showUrlDlg = true state.showUrlDlg = true
} else if (type === 'proxy') { } else if (type === 'proxy') {
fetchData('proxy') fetchData('proxy')
} else if (type === 'copyUrl') {
copyUrl()
} }
} }
@ -260,6 +306,15 @@ 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')
if (successed) {
ElMessage.success(ui.value.extra.urlCopied)
} else {
ElMessage.error(state.i18n.log.url.notFound)
}
}
onMounted(async () => { onMounted(async () => {
await readData() await readData()
await getI18nData() await getI18nData()

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="bg-white pt-2 pb-4 px-6 w-full h-full absolute inset-0"> <div class="bg-white py-4 px-6 w-screen h-screen fixed inset-0 overflow-y-auto">
<div class="flex content-center items-center mb-4 justify-between"> <div class="flex content-center items-center mb-4 justify-between">
<h3 class="text-lg">{{text.title}}</h3> <h3 class="text-lg">{{text.title}}</h3>
<el-button icon="close" @click="closeSetting" plain circle type="default" class="w-8 h-8 relative -right-4 -top-2 shadow-md focus:shadow-none focus:outline-none"></el-button> <el-button icon="close" @click="closeSetting" plain circle type="default" class="w-8 h-8 shadow-md focus:shadow-none focus:outline-none fixed top-4 right-6"></el-button>
</div> </div>
<el-form :model="settingForm" label-width="120px"> <el-form :model="settingForm" label-width="120px">
<el-form-item :label="text.language"> <el-form-item :label="text.language">
@ -19,6 +19,10 @@
</el-radio-group> </el-radio-group>
<p class="text-gray-400 text-xs m-1.5">{{text.logTypeHint}}</p> <p class="text-gray-400 text-xs m-1.5">{{text.logTypeHint}}</p>
</el-form-item> </el-form-item>
<el-form-item :label="common.data">
<el-button type="primary" plain @click="state.showDataDialog = true">{{common.dataManage}}</el-button>
<p class="text-gray-400 text-xs m-1.5">{{text.dataManagerHint}}</p>
</el-form-item>
<el-form-item :label="text.autoUpdate"> <el-form-item :label="text.autoUpdate">
<el-switch <el-switch
@change="saveSetting" @change="saveSetting"
@ -51,21 +55,49 @@
<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 pb-6">Github: <a @click="openGithub" class="cursor-pointer text-blue-400">https://github.com/biuuu/star-rail-warp-export</a></p>
<el-dialog v-model="state.showDataDialog" :title="common.dataManage" width="90%">
<div class="">
<el-table :data="gachaDataInfo" border stripe>
<el-table-column property="uid" label="UID" width="128" />
<el-table-column property="time" :label="common.updateTime">
<template #default="scope">
{{ new Date(scope.row.time).toLocaleString() }}
</template>
</el-table-column>
<el-table-column property="deleted" :label="common.status" width="128">
<template #default="scope">
<el-tag type="info" size="small" v-if="scope.row.deleted">{{common.deleted}}</el-tag>
<el-tag type="success" size="small" v-else>{{common.normal}}</el-tag>
</template>
</el-table-column>
<el-table-column property="deleted" :label="common.action" width="128">
<template #default="scope">
<el-tooltip :content="scope.row.deleted ? common.restore : common.delete" placement="top">
<el-button :loading="state.dataActionLoading" size="small" icon="refresh" plain type="success" @click="deleteData(scope.row.uid, false)" v-if="scope.row.deleted"></el-button>
<el-button :loading="state.dataActionLoading" size="small" icon="delete" plain type="danger" @click="deleteData(scope.row.uid, true)" v-else></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div> </div>
</el-dialog>
</div>
</template> </template>
<script setup> <script setup>
const { ipcRenderer, shell } = require('electron') const { ipcRenderer, shell } = require('electron')
import { reactive, onMounted, computed } from 'vue' import { reactive, onMounted, computed } from 'vue'
const emit = defineEmits(['close', 'changeLang']) const emit = defineEmits(['close', 'changeLang', 'refreshData'])
const props = defineProps({ const props = defineProps({
i18n: Object i18n: Object,
gachaDataInfo: Array
}) })
const data = reactive({ const data = reactive({
langMap: new Map() langMap: new Map(),
}) })
const settingForm = reactive({ const settingForm = reactive({
@ -77,6 +109,12 @@ const settingForm = reactive({
hideNovice: true hideNovice: true
}) })
const state = reactive({
showDataDialog: false,
dataActionLoading: false
})
const common = computed(() => props.i18n.ui.common)
const text = computed(() => props.i18n.ui.setting) const text = computed(() => props.i18n.ui.setting)
const about = computed(() => props.i18n.ui.about) const about = computed(() => props.i18n.ui.about)
@ -101,8 +139,11 @@ 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 openLink = (link) => shell.openExternal(link) const openLink = (link) => shell.openExternal(link)
const exportUIGFJSON = () => { const deleteData = async (uid, action) => {
ipcRenderer.invoke('EXPORT_UIGF_JSON') state.dataActionLoading = true
await ipcRenderer.invoke('DELETE_DATA', uid, action)
state.dataActionLoading = false
emit('refreshData')
} }
onMounted(async () => { onMounted(async () => {

View File

@ -6,11 +6,11 @@
:root { :root {
--el-font-size-base: 12px !important; --el-font-size-base: 12px !important;
} }
body::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;
height: 6px; height: 6px;
} }
body::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
@apply rounded-full bg-gray-300; @apply rounded-full bg-gray-300;
} }
} }

959
yarn.lock

File diff suppressed because it is too large Load Diff