mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-13 09:28:14 +08:00
✨ UIGFv4支持
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file src/data/index.ts
|
||||
* @description 数据文件入口
|
||||
* @since Beta v0.4.7
|
||||
* @since Beta v0.5.0
|
||||
*/
|
||||
|
||||
import type { Schema } from "ajv";
|
||||
@@ -19,6 +19,7 @@ import arcBirDraw from "./archive/birth_draw.json";
|
||||
import arcBirRole from "./archive/birth_role.json";
|
||||
import schemaUiaf from "./schema/uiaf-schema.json";
|
||||
import schemaUigf from "./schema/uigf-schema.json";
|
||||
import scheamUigf4 from "./schema/uigf4-schema.json";
|
||||
import wikiCharacter from "./WIKI/character.json";
|
||||
import wikiMaterial from "./WIKI/material.json";
|
||||
import wikiWeapon from "./WIKI/weapon.json";
|
||||
@@ -35,6 +36,7 @@ export const AppWeaponData: TGApp.App.Weapon.WikiBriefInfo[] = weapon;
|
||||
// Schema
|
||||
export const UiafSchema: Schema = schemaUiaf;
|
||||
export const UigfSchema: Schema = schemaUigf;
|
||||
export const Uigf4Schema: Schema = scheamUigf4;
|
||||
// Archive
|
||||
export const ArcBirCalendar: TGApp.Archive.Birth.CalendarData = arcBirCalendar;
|
||||
export const ArcBirDraw: TGApp.Archive.Birth.DrawItem[] = arcBirDraw;
|
||||
|
||||
310
src/data/schema/uigf4-schema.json
Normal file
310
src/data/schema/uigf4-schema.json
Normal file
@@ -0,0 +1,310 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"export_timestamp": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"description": "导出档案的时间戳,秒级"
|
||||
},
|
||||
"export_app": {
|
||||
"type": "string",
|
||||
"description": "导出档案的 App 名称"
|
||||
},
|
||||
"export_app_version": {
|
||||
"type": "string",
|
||||
"description": "导出档案的 App 版本"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^v\\d+\\.\\d+$",
|
||||
"description": "导出档案的 UIGF 版本号,格式为 'v{major}.{minor}',如 v4.0"
|
||||
}
|
||||
},
|
||||
"required": ["export_timestamp", "export_app", "export_app_version", "version"]
|
||||
},
|
||||
"hk4e": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"description": "UID"
|
||||
},
|
||||
"timezone": {
|
||||
"type": "integer",
|
||||
"description": "时区偏移,由米哈游 API 返回,若与服务器时区不同请注意 list 中 time 的转换"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"description": "语言代码",
|
||||
"enum": [
|
||||
"de-de",
|
||||
"en-us",
|
||||
"es-es",
|
||||
"fr-fr",
|
||||
"id-id",
|
||||
"it-it",
|
||||
"ja-jp",
|
||||
"ko-kr",
|
||||
"pt-pt",
|
||||
"ru-ru",
|
||||
"th-th",
|
||||
"tr-tr",
|
||||
"vi-vn",
|
||||
"zh-cn",
|
||||
"zh-tw"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uigf_gacha_type": {
|
||||
"type": "string",
|
||||
"description": "UIGF 卡池类型,用于区分卡池类型不同,但卡池保底计算相同的物品",
|
||||
"enum": ["100", "200", "301", "302", "500"]
|
||||
},
|
||||
"gacha_type": {
|
||||
"type": "string",
|
||||
"description": "卡池类型,米哈游 API 返回",
|
||||
"enum": ["100", "200", "301", "302", "400", "500"]
|
||||
},
|
||||
"item_id": {
|
||||
"type": "string",
|
||||
"description": "物品的内部 ID"
|
||||
},
|
||||
"count": {
|
||||
"type": "string",
|
||||
"description": "物品个数,一般为1,米哈游 API 返回"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"description": "抽取物品时对应时区(timezone)下的当地时间"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "物品名称,米哈游 API 返回"
|
||||
},
|
||||
"item_type": {
|
||||
"type": "string",
|
||||
"description": "物品类型,米哈游 API 返回"
|
||||
},
|
||||
"rank_type": {
|
||||
"type": "string",
|
||||
"description": "物品等级,米哈游 API 返回"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "记录内部 ID,米哈游 API 返回"
|
||||
}
|
||||
},
|
||||
"required": ["uigf_gacha_type", "gacha_type", "item_id", "time", "id"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["uid", "timezone", "list"]
|
||||
}
|
||||
},
|
||||
"hkrpg": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"description": "UID"
|
||||
},
|
||||
"timezone": {
|
||||
"type": "integer",
|
||||
"description": "时区偏移,由米哈游 API 返回,若与服务器时区不同请注意 list 中 time 的转换"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"description": "语言代码",
|
||||
"enum": [
|
||||
"de-de",
|
||||
"en-us",
|
||||
"es-es",
|
||||
"fr-fr",
|
||||
"id-id",
|
||||
"it-it",
|
||||
"ja-jp",
|
||||
"ko-kr",
|
||||
"pt-pt",
|
||||
"ru-ru",
|
||||
"th-th",
|
||||
"tr-tr",
|
||||
"vi-vn",
|
||||
"zh-cn",
|
||||
"zh-tw"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gacha_id": {
|
||||
"type": "string",
|
||||
"description": "卡池 Id"
|
||||
},
|
||||
"gacha_type": {
|
||||
"type": "string",
|
||||
"description": "卡池类型",
|
||||
"enum": ["1", "2", "11", "12"]
|
||||
},
|
||||
"item_id": {
|
||||
"type": "string",
|
||||
"description": "物品的内部 ID"
|
||||
},
|
||||
"count": {
|
||||
"type": "string",
|
||||
"description": "物品个数,一般为1,米哈游 API 返回"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"description": "抽取物品时对应时区(timezone)下的当地时间"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "物品名称,米哈游 API 返回"
|
||||
},
|
||||
"item_type": {
|
||||
"type": "string",
|
||||
"description": "物品类型,米哈游 API 返回"
|
||||
},
|
||||
"rank_type": {
|
||||
"type": "string",
|
||||
"description": "物品等级,米哈游 API 返回"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "内部 Id"
|
||||
}
|
||||
},
|
||||
"required": ["gacha_type", "time", "item_id", "id"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["uid", "timezone", "list"]
|
||||
}
|
||||
},
|
||||
"nap": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"description": "UID"
|
||||
},
|
||||
"timezone": {
|
||||
"type": "integer",
|
||||
"description": "时区偏移,由米哈游 API 返回,若与服务器时区不同请注意 list 中 time 的转换"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"description": "语言代码",
|
||||
"enum": [
|
||||
"de-de",
|
||||
"en-us",
|
||||
"es-es",
|
||||
"fr-fr",
|
||||
"id-id",
|
||||
"it-it",
|
||||
"ja-jp",
|
||||
"ko-kr",
|
||||
"pt-pt",
|
||||
"ru-ru",
|
||||
"th-th",
|
||||
"tr-tr",
|
||||
"vi-vn",
|
||||
"zh-cn",
|
||||
"zh-tw"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gacha_id": {
|
||||
"type": "string",
|
||||
"description": "卡池 Id"
|
||||
},
|
||||
"gacha_type": {
|
||||
"type": "string",
|
||||
"description": "卡池类型",
|
||||
"enum": ["1", "2", "3", "5"]
|
||||
},
|
||||
"item_id": {
|
||||
"type": "string",
|
||||
"description": "物品的内部 ID"
|
||||
},
|
||||
"count": {
|
||||
"type": "string",
|
||||
"description": "物品个数,一般为1,米哈游 API 返回"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"description": "抽取物品时对应时区(timezone)下的当地时间"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "物品名称,米哈游 API 返回"
|
||||
},
|
||||
"item_type": {
|
||||
"type": "string",
|
||||
"description": "物品类型,米哈游 API 返回"
|
||||
},
|
||||
"rank_type": {
|
||||
"type": "string",
|
||||
"description": "物品等级,米哈游 API 返回"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "记录内部 ID,米哈游 API 返回"
|
||||
}
|
||||
},
|
||||
"required": ["gacha_type", "item_id", "time", "id"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["uid", "timezone", "list"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["info", "hk4e"]
|
||||
}
|
||||
@@ -10,15 +10,17 @@
|
||||
<v-btn prepend-icon="mdi-refresh" class="gacha-top-btn" @click="confirmRefresh(true)"
|
||||
>全量刷新
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn()">导入</v-btn>
|
||||
<v-btn prepend-icon="mdi-export" class="gacha-top-btn" @click="handleExportBtn">导出</v-btn>
|
||||
<v-btn prepend-icon="mdi-cloud-download" class="gacha-top-btn" @click="backupGacha">
|
||||
备份
|
||||
<v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn(false)"
|
||||
>导入
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-delete" class="gacha-top-btn" @click="deleteGacha">删除</v-btn>
|
||||
<v-btn prepend-icon="mdi-cloud-upload" class="gacha-top-btn" @click="restoreGacha">
|
||||
恢复
|
||||
<v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn(true)"
|
||||
>导入(v4)
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-export" class="gacha-top-btn" @click="exportUigf()">导出 </v-btn>
|
||||
<v-btn prepend-icon="mdi-export" class="gacha-top-btn" @click="exportUigf4()"
|
||||
>导出(v4)
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-delete" class="gacha-top-btn" @click="deleteGacha()">删除</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gacha-container">
|
||||
@@ -54,14 +56,18 @@ import GroOverview from "../../components/gachaRecord/gro-overview.vue";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
import { AppCharacterData, AppWeaponData } from "../../data/index.js";
|
||||
import TSUserGacha from "../../plugins/Sqlite/modules/userGacha.js";
|
||||
import { useAppStore } from "../../store/modules/app.js";
|
||||
import { useUserStore } from "../../store/modules/user.js";
|
||||
import TGLogger from "../../utils/TGLogger.js";
|
||||
import { backupUigfData, exportUigfData, readUigfData, verifyUigfData } from "../../utils/UIGF.js";
|
||||
import {
|
||||
exportUigfData,
|
||||
readUigf4Data,
|
||||
readUigfData,
|
||||
verifyUigfData,
|
||||
exportUigf4Data,
|
||||
} from "../../utils/UIGF.js";
|
||||
import TGRequest from "../../web/request/TGRequest.js";
|
||||
|
||||
// store
|
||||
const appStore = useAppStore();
|
||||
const userStore = storeToRefs(useUserStore());
|
||||
const account = userStore.account.value;
|
||||
const authkey = ref<string>("");
|
||||
@@ -267,36 +273,24 @@ async function getGachaLogs(
|
||||
}
|
||||
|
||||
// 导入按钮点击事件
|
||||
async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
await TGLogger.Info("[UserGacha][handleImportBtn] 导入祈愿数据");
|
||||
let selectedFile;
|
||||
if (savePath) {
|
||||
selectedFile = await open({
|
||||
multiple: false,
|
||||
title: "选择要导入的祈愿数据文件",
|
||||
filters: [
|
||||
{
|
||||
name: "UIGF JSON",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
defaultPath: savePath,
|
||||
directory: false,
|
||||
});
|
||||
async function handleImportBtn(isV4: boolean): Promise<void> {
|
||||
if (isV4) {
|
||||
await TGLogger.Info("[UserGacha][handleImportBtn] 导入祈愿数据(v4)");
|
||||
} else {
|
||||
selectedFile = await open({
|
||||
multiple: false,
|
||||
title: "选择要导入的祈愿数据文件",
|
||||
filters: [
|
||||
{
|
||||
name: "UIGF JSON",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
defaultPath: `${await path.downloadDir()}`,
|
||||
directory: false,
|
||||
});
|
||||
await TGLogger.Info("[UserGacha][handleImportBtn] 导入祈愿数据");
|
||||
}
|
||||
const selectedFile = await open({
|
||||
multiple: false,
|
||||
title: "选择要导入的祈愿数据文件",
|
||||
filters: [
|
||||
{
|
||||
name: "UIGF JSON",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
defaultPath: await path.downloadDir(),
|
||||
directory: false,
|
||||
});
|
||||
if (!selectedFile) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
@@ -304,14 +298,24 @@ async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const check = await verifyUigfData(selectedFile.path);
|
||||
const check = await verifyUigfData(selectedFile.path, isV4);
|
||||
if (!check) return;
|
||||
const remoteData = await readUigfData(selectedFile.path);
|
||||
if (isV4) {
|
||||
await importUigf4(selectedFile.path);
|
||||
} else {
|
||||
await importUigf(selectedFile.path);
|
||||
}
|
||||
}
|
||||
|
||||
// 导入 v4 版本的祈愿数据
|
||||
async function importUigf4(filePath: string): Promise<void> {
|
||||
const remoteData = await readUigf4Data(filePath);
|
||||
const uidCount = remoteData.hk4e.length;
|
||||
const dataCount = remoteData.hk4e.reduce((acc, cur) => acc + cur.list.length, 0);
|
||||
const res = await showConfirm({
|
||||
title: "是否导入祈愿数据?",
|
||||
text: `UID:${remoteData.info.uid} 共 ${remoteData.list.length} 条数据`,
|
||||
text: `共 ${uidCount} 个 UID,${dataCount} 条数据`,
|
||||
});
|
||||
await TGLogger.Info(`[UserGacha][${account.gameUid}][handleImportBtn] 确认导入祈愿数据`);
|
||||
if (!res) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
@@ -319,6 +323,37 @@ async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在导入祈愿数据(v4)";
|
||||
loading.value = true;
|
||||
for (const account of remoteData.hk4e) {
|
||||
loadingSub.value = `正在导入 ${account.uid} 的祈愿数据`;
|
||||
await TSUserGacha.mergeUIGF4(account);
|
||||
}
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
text: `成功导入 ${uidCount} 个 UID 的 ${dataCount} 条祈愿数据`,
|
||||
});
|
||||
await TGLogger.Info(
|
||||
`[UserGacha][importUigf4] 成功导入 ${uidCount} 个 UID,${dataCount} 条祈愿数据`,
|
||||
);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function importUigf(filePath: string): Promise<void> {
|
||||
const remoteData = await readUigfData(filePath);
|
||||
const confirm = await showConfirm({
|
||||
title: "是否导入祈愿数据?",
|
||||
text: `UID:${remoteData.info.uid},共 ${remoteData.list.length} 条数据`,
|
||||
});
|
||||
if (!confirm) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
text: "已取消祈愿数据导入",
|
||||
});
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在导入祈愿数据";
|
||||
loading.value = true;
|
||||
if (remoteData.list.length === 0) {
|
||||
@@ -335,15 +370,16 @@ async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
text: `成功导入 ${remoteData.list.length} 条祈愿数据`,
|
||||
});
|
||||
await TGLogger.Info(
|
||||
`[UserGacha][handleImportBtn] 成功导入 ${remoteData.info.uid} 的 ${remoteData.list.length} 条祈愿数据`,
|
||||
`[UserGacha][importUigf] 成功导入 ${remoteData.info.uid} 的 ${remoteData.list.length} 条祈愿数据`,
|
||||
);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 导出按钮点击事件
|
||||
async function handleExportBtn(): Promise<void> {
|
||||
// 导出当前UID的祈愿数据
|
||||
async function exportUigf(): Promise<void> {
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf] 导出祈愿数据`);
|
||||
const gachaList = await TSUserGacha.getGachaRecords(uidCur.value);
|
||||
if (gachaList.length === 0) {
|
||||
showSnackbar({
|
||||
@@ -352,15 +388,14 @@ async function handleExportBtn(): Promise<void> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const res = await showConfirm({
|
||||
const res = showConfirm({
|
||||
title: "是否导出祈愿数据?",
|
||||
text: `UID:${uidCur.value},共 ${gachaList.length} 条数据`,
|
||||
});
|
||||
await TGLogger.Info(`[UserGacha][${account.gameUid}][handleExportBtn] 导出祈愿数据`);
|
||||
if (!res) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
text: "已取消祈愿数据导出",
|
||||
text: `已取消 UID ${uidCur.value} 的祈愿数据导出`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -372,7 +407,7 @@ async function handleExportBtn(): Promise<void> {
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
defaultPath: `${await path.downloadDir()}${path.sep()}UIGF${uidCur.value}.json`,
|
||||
defaultPath: `${await path.downloadDir()}${path.sep()}UIGF_${uidCur.value}.json`,
|
||||
});
|
||||
if (!file) {
|
||||
showSnackbar({
|
||||
@@ -382,55 +417,69 @@ async function handleExportBtn(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
await TGLogger.Info(
|
||||
`[UserGacha][${account.gameUid}][handleExportBtn] 导出${gachaList.length} 条祈愿数据到 ${file}`,
|
||||
`[UserGacha][${uidCur.value}][exportUigf] 导出${gachaList.length} 条祈愿数据到 ${file}`,
|
||||
);
|
||||
loadingTitle.value = "正在导出祈愿数据";
|
||||
loadingTitle.value = `正在导出 ${uidCur.value} 的祈愿数据`;
|
||||
loading.value = true;
|
||||
await exportUigfData(uidCur.value, gachaList, file);
|
||||
loading.value = false;
|
||||
showSnackbar({ text: `成功导出 ${uidCur.value} 的祈愿数据` });
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf] 导出祈愿数据完成`);
|
||||
}
|
||||
|
||||
// 导出 UIGF v4 版本的祈愿数据
|
||||
async function exportUigf4(): Promise<void> {
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf4] 导出祈愿数据(v4)`);
|
||||
const allConfirm = await showConfirm({
|
||||
title: "是否导出所有 UID 的祈愿数据?",
|
||||
text: "取消则只导出当前 UID 的祈愿数据",
|
||||
});
|
||||
if (allConfirm === undefined) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
text: "已取消祈愿数据导出",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (allConfirm === false) {
|
||||
const gachaList = await TSUserGacha.getGachaRecords(uidCur.value);
|
||||
if (gachaList.length === 0) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: `UID ${uidCur.value} 暂无祈愿数据`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const file = await save({
|
||||
title: "选择导出祈愿数据的文件路径",
|
||||
filters: [
|
||||
{
|
||||
name: "UIGF JSON",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
defaultPath: `${await path.downloadDir()}${path.sep()}UIGF4.json`,
|
||||
});
|
||||
if (!file) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
text: "已取消文件保存",
|
||||
});
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在导出祈愿数据";
|
||||
loading.value = true;
|
||||
if (allConfirm === false) {
|
||||
await exportUigf4Data(file, uidCur.value);
|
||||
} else {
|
||||
await exportUigf4Data(file);
|
||||
}
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
text: "祈愿数据已成功导出",
|
||||
});
|
||||
await TGLogger.Info(`[UserGacha][${account.gameUid}][handleExportBtn] 导出祈愿数据完成`);
|
||||
}
|
||||
|
||||
// 恢复UID祈愿数据,相当于导入祈愿数据,不过目录固定
|
||||
async function restoreGacha(): Promise<void> {
|
||||
await handleImportBtn(appStore.userDir);
|
||||
}
|
||||
|
||||
// 备份当前 UID 的祈愿数据
|
||||
async function backupGacha(): Promise<void> {
|
||||
if (gachaListCur.value.length === 0) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: "暂无祈愿数据",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][backupGacha] 备份祈愿数据`);
|
||||
const res = await showConfirm({
|
||||
title: "是否备份祈愿数据?",
|
||||
text: `UID:${uidCur.value},共 ${gachaListCur.value.length} 条数据`,
|
||||
});
|
||||
if (!res) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
text: "已取消祈愿数据备份",
|
||||
});
|
||||
await TGLogger.Warn(`[UserGacha][${uidCur.value}][backupGacha] 已取消祈愿数据备份`);
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在备份祈愿数据";
|
||||
loading.value = true;
|
||||
await backupUigfData(appStore.userDir, uidCur.value, gachaListCur.value);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
text: `已成功备份 ${uidCur.value} 的祈愿数据`,
|
||||
});
|
||||
await TGLogger.Info(
|
||||
`[UserGacha][${uidCur.value}][backupGacha] 成功备份 ${gachaListCur.value.length} 条祈愿数据`,
|
||||
);
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf4] 导出祈愿数据完成`);
|
||||
}
|
||||
|
||||
// 删除当前 UID 的祈愿数据
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file plugins/Sqlite/modules/userGacha.ts
|
||||
* @description 用户祈愿模块
|
||||
* @since Beta v0.4.7
|
||||
* @since Beta v0.5.0
|
||||
*/
|
||||
|
||||
import { AppCharacterData, AppWeaponData } from "../../../data/index.js";
|
||||
@@ -135,6 +135,21 @@ async function mergeUIGF(uid: string, data: TGApp.Plugins.UIGF.GachaItem[]): Pro
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 合并祈愿数据(v4.0)
|
||||
* @since Beta v0.5.0
|
||||
* @param {TGApp.Plugins.UIGF.GachaHk4e} data - UIGF数据
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function mergeUIGF4(data: TGApp.Plugins.UIGF.GachaHk4e): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
for (const gacha of data.list) {
|
||||
const trans = transGacha(gacha);
|
||||
const sql = importUIGFData(data.uid.toString(), trans);
|
||||
await db.execute(sql);
|
||||
}
|
||||
}
|
||||
|
||||
const TSUserGacha = {
|
||||
getUidList,
|
||||
getGachaCheck,
|
||||
@@ -142,6 +157,7 @@ const TSUserGacha = {
|
||||
getGachaItemType,
|
||||
deleteGachaRecords,
|
||||
mergeUIGF,
|
||||
mergeUIGF4,
|
||||
};
|
||||
|
||||
export default TSUserGacha;
|
||||
|
||||
74
src/types/Plugins/UIGF.d.ts
vendored
74
src/types/Plugins/UIGF.d.ts
vendored
@@ -1,28 +1,41 @@
|
||||
/**
|
||||
* @file types/Plugins/UIGF.d.ts
|
||||
* @description UIGF 插件类型定义文件
|
||||
* @since Beta v0.3.5
|
||||
* @version UIGF v2.4
|
||||
* @since Beta v0.5.0
|
||||
* @version UIGF v3.0 | UIGF v4.0
|
||||
*/
|
||||
|
||||
declare namespace TGApp.Plugins.UIGF {
|
||||
/**
|
||||
* @description UIGF 数据
|
||||
* @since Alpha v0.2.3
|
||||
* @interface FullData
|
||||
* @property {Export} info - UIGF 头部信息
|
||||
* @since Beta v0.5.0
|
||||
* @interface Schema
|
||||
* @property {Info} info - UIGF 头部信息
|
||||
* @property {GachaItem[]} list - UIGF 祈愿列表
|
||||
* @return FullData
|
||||
* @return Schema
|
||||
*/
|
||||
interface FullData {
|
||||
info: Export;
|
||||
interface Schema {
|
||||
info: Info;
|
||||
list: GachaItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description UIGF 数据, v4.0
|
||||
* @since Beta v0.5.0
|
||||
* @interface Schema4
|
||||
* @property {Info4} info - UIGF 头部信息
|
||||
* @property {GachaItem4[]} hk4e - UIGF 祈愿列表,原神数据
|
||||
* @return Schema4
|
||||
*/
|
||||
interface Schema4 {
|
||||
info: Info4;
|
||||
hk4e: GachaHk4e[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description UIGF 头部信息
|
||||
* @since Beta v0.3.5
|
||||
* @interface Export
|
||||
* @since Beta v0.5.0
|
||||
* @interface Info
|
||||
* @see docs\UIGF.md
|
||||
* @property {string} uid - UID
|
||||
* @property {string} lang - 语言
|
||||
@@ -32,9 +45,9 @@ declare namespace TGApp.Plugins.UIGF {
|
||||
* @property {string} export_app - 导出应用
|
||||
* @property {string} export_app_version - 导出应用版本
|
||||
* @property {number} region_time_zone - 时区
|
||||
* @return Export
|
||||
* @return Info
|
||||
*/
|
||||
interface Export {
|
||||
interface Info {
|
||||
uid: string;
|
||||
lang: string;
|
||||
uigf_version: string;
|
||||
@@ -45,6 +58,24 @@ declare namespace TGApp.Plugins.UIGF {
|
||||
region_time_zone?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description UIGF 头部信息, v4.0
|
||||
* @since Beta v0.5.0
|
||||
* @interface Info4
|
||||
* @see docs\UIGF4.md
|
||||
* @property {string} export_timestamp - 导出时间戳(秒)
|
||||
* @property {string} export_app - 导出应用
|
||||
* @property {string} export_app_version - 导出应用版本
|
||||
* @property {string} version - UIGF 版本
|
||||
* @return Info4
|
||||
*/
|
||||
interface Info4 {
|
||||
export_timestamp: string;
|
||||
export_app: string;
|
||||
export_app_version: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 祈愿类型
|
||||
* @since Alpha v0.2.3
|
||||
@@ -97,6 +128,7 @@ declare namespace TGApp.Plugins.UIGF {
|
||||
* @return GachaItem
|
||||
*/
|
||||
interface GachaItem {
|
||||
uigf_gacha_type: string;
|
||||
gacha_type: string;
|
||||
item_id?: string;
|
||||
count?: string;
|
||||
@@ -105,6 +137,22 @@ declare namespace TGApp.Plugins.UIGF {
|
||||
item_type?: string;
|
||||
rank_type?: string;
|
||||
id: string;
|
||||
uigf_gacha_type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description UIGF 祈愿列表, v4.0,原神数据
|
||||
* @since Beta v0.5.0
|
||||
* @interface GachaHk4e
|
||||
* @property {string|number} uid - UID
|
||||
* @property {number} timezone - 时区
|
||||
* @property {string} lang - 语言
|
||||
* @property {GachaItem[]} list - 祈愿列表
|
||||
* @return GachaHk4e
|
||||
*/
|
||||
interface GachaHk4e {
|
||||
uid: string | number;
|
||||
timezone: number;
|
||||
lang?: string;
|
||||
list: GachaItem[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
*/
|
||||
|
||||
import { app, path } from "@tauri-apps/api";
|
||||
import { mkdir, exists, readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
|
||||
import Ajv from "ajv";
|
||||
import { ErrorObject } from "ajv/lib/types/index.js";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar.js";
|
||||
import { UigfSchema } from "../data/index.js";
|
||||
import { Uigf4Schema, UigfSchema } from "../data/index.js";
|
||||
import TSUserGacha from "../plugins/Sqlite/modules/userGacha.js";
|
||||
|
||||
import TGLogger from "./TGLogger.js";
|
||||
import { timestampToDate } from "./toolFunc.js";
|
||||
@@ -31,9 +32,9 @@ function getUigfTimeZone(uid: string): number {
|
||||
* @description 获取 UIGF 头部信息
|
||||
* @since Beta v0.4.4
|
||||
* @param {string} uid - UID
|
||||
* @returns {Promise<TGApp.Plugins.UIGF.Export>}
|
||||
* @returns {Promise<TGApp.Plugins.UIGF.Info>}
|
||||
*/
|
||||
export async function getUigfHeader(uid: string): Promise<TGApp.Plugins.UIGF.Export> {
|
||||
async function getUigfHeader(uid: string): Promise<TGApp.Plugins.UIGF.Info> {
|
||||
const stamp = Date.now();
|
||||
return {
|
||||
uid,
|
||||
@@ -47,13 +48,28 @@ export async function getUigfHeader(uid: string): Promise<TGApp.Plugins.UIGF.Exp
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取 UIGF v4.0 头部信息
|
||||
* @since Beta v0.5.0
|
||||
* @returns {TGApp.Plugins.UIGF.Info4} UIGF v4.0 头部信息
|
||||
*/
|
||||
async function getUigf4Header(): Promise<TGApp.Plugins.UIGF.Info4> {
|
||||
const stamp = Date.now();
|
||||
return {
|
||||
export_timestamp: Math.floor(stamp / 1000).toString(),
|
||||
export_app: "TeyvatGuide",
|
||||
export_app_version: await app.getVersion(),
|
||||
version: "v4.0",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 数据转换-数据库到 UIGF
|
||||
* @since Alpha v0.2.3
|
||||
* @param {TGApp.Sqlite.GachaRecords.SingleTable[]} data - 数据库数据
|
||||
* @returns {TGApp.Plugins.UIGF.GachaItem[]} UIGF 数据
|
||||
*/
|
||||
export function convertDataToUigf(
|
||||
function convertDataToUigf(
|
||||
data: TGApp.Sqlite.GachaRecords.SingleTable[],
|
||||
): TGApp.Plugins.UIGF.GachaItem[] {
|
||||
return data.map((gacha) => {
|
||||
@@ -75,26 +91,15 @@ export function convertDataToUigf(
|
||||
* @description 检测是否存在 UIGF 数据,采用 ajv 验证 schema
|
||||
* @since Beta v0.5.0
|
||||
* @param {string} path - UIGF 数据路径
|
||||
* @param {boolean} isVersion4 - 是否为 UIGF v4.0
|
||||
* @returns {Promise<boolean>} 是否存在 UIGF 数据
|
||||
*/
|
||||
export async function verifyUigfData(path: string): Promise<boolean> {
|
||||
export async function verifyUigfData(path: string, isVersion4: boolean = false): Promise<boolean> {
|
||||
const fileData: string = await readTextFile(path);
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UigfSchema);
|
||||
try {
|
||||
const fileJson = JSON.parse(fileData);
|
||||
if (!validate(fileJson)) {
|
||||
if (!validate.errors || validate.errors.length === 0) return false;
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIGF 数据验证失败,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (isVersion4) return validateUigf4Data(fileJson);
|
||||
return validateUigfData(fileJson);
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIGF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIGF 数据格式错误,文件路径:${path}`);
|
||||
@@ -103,15 +108,68 @@ export async function verifyUigfData(path: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 验证 UIGF 数据
|
||||
* @since Beta v0.5.0
|
||||
* @param {object} data - UIGF 数据
|
||||
* @returns {boolean} 是否验证通过
|
||||
*/
|
||||
function validateUigfData(data: object): boolean {
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UigfSchema);
|
||||
if (!validate(data)) {
|
||||
if (!validate.errors || validate.errors.length === 0) return false;
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 验证 UIGF v4.0 数据
|
||||
* @since Beta v0.5.0
|
||||
* @param {object} data - UIGF 数据
|
||||
* @returns {boolean} 是否验证通过
|
||||
*/
|
||||
function validateUigf4Data(data: object): boolean {
|
||||
const ajv = new Ajv();
|
||||
const validate4 = ajv.compile(Uigf4Schema);
|
||||
if (!validate4(data)) {
|
||||
if (!validate4.errors || validate4.errors.length === 0) return false;
|
||||
const error: ErrorObject = validate4.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 读取 UIGF 数据
|
||||
* @since Beta v0.5.0
|
||||
* @param {string} userPath - UIGF 数据路径
|
||||
* @returns {Promise<TGApp.Plugins.UIGF.FullData>} UIGF 数据
|
||||
* @returns {Promise<TGApp.Plugins.UIGF.Schema>} UIGF 数据
|
||||
*/
|
||||
export async function readUigfData(userPath: string): Promise<TGApp.Plugins.UIGF.FullData> {
|
||||
const fileData = await readTextFile(userPath);
|
||||
return <TGApp.Plugins.UIGF.FullData>JSON.parse(fileData);
|
||||
export async function readUigfData(userPath: string): Promise<TGApp.Plugins.UIGF.Schema> {
|
||||
const fileData: string = await readTextFile(userPath);
|
||||
return JSON.parse(fileData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 读取 UIGF 4.0 数据
|
||||
* @since Beta v0.5.0
|
||||
* @param {string} userPath - UIGF 数据路径
|
||||
* @returns {Promise<TGApp.Plugins.UIGF.Schema4>} UIGF 数据
|
||||
*/
|
||||
export async function readUigf4Data(userPath: string): Promise<TGApp.Plugins.UIGF.Schema4> {
|
||||
const fileData: string = await readTextFile(userPath);
|
||||
return JSON.parse(fileData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,18 +194,31 @@ export async function exportUigfData(
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 备份 UIGF 数据
|
||||
* @description 导出 UIGF v4.0 数据
|
||||
* @since Beta v0.5.0
|
||||
* @param {string} dirPath - 备份路径
|
||||
* @param {string} uid - UID
|
||||
* @param {TGApp.Sqlite.GachaRecords.SingleTable[]} gachaList - 祈愿列表
|
||||
* @param {string} filePath - 保存路径
|
||||
* @param {string} uid - UID,如果为空表示导出所有数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function backupUigfData(
|
||||
dirPath: string,
|
||||
uid: string,
|
||||
gachaList: TGApp.Sqlite.GachaRecords.SingleTable[],
|
||||
): Promise<void> {
|
||||
if (!(await exists(dirPath))) await mkdir(dirPath, { recursive: true });
|
||||
await exportUigfData(uid, gachaList, `${dirPath}${path.sep()}UIGF_${uid}.json`);
|
||||
export async function exportUigf4Data(filePath: string, uid?: string): Promise<void> {
|
||||
const UigfData: TGApp.Plugins.UIGF.Schema4 = {
|
||||
info: await getUigf4Header(),
|
||||
hk4e: [],
|
||||
};
|
||||
let uidList: string[] = [];
|
||||
if (uid) {
|
||||
uidList.push(uid);
|
||||
} else {
|
||||
uidList = await TSUserGacha.getUidList();
|
||||
}
|
||||
for (const uid of uidList) {
|
||||
const gachaList = await TSUserGacha.getGachaRecords(uid);
|
||||
const data: TGApp.Plugins.UIGF.GachaHk4e = {
|
||||
uid: uid,
|
||||
timezone: getUigfTimeZone(uid),
|
||||
list: convertDataToUigf(gachaList),
|
||||
};
|
||||
UigfData.hk4e.push(data);
|
||||
}
|
||||
await writeTextFile(filePath, JSON.stringify(UigfData));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user