mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-12 09:18:14 +08:00
@@ -1,10 +1,17 @@
|
||||
/**
|
||||
* @file utils/UIAF.ts
|
||||
* @description UIAF工具类
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { app, fs } from "@tauri-apps/api";
|
||||
import Ajv from "ajv";
|
||||
import { ErrorObject } from "ajv/lib/types/index.js";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar.js";
|
||||
import { UiafSchema } from "../data/index.js";
|
||||
|
||||
import TGLogger from "./TGLogger.js";
|
||||
|
||||
/**
|
||||
* @description 根据 completed 跟 progress 获取 status
|
||||
@@ -40,16 +47,64 @@ export async function getUiafHeader(): Promise<TGApp.Plugins.UIAF.Export> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测是否存在 UIAF 数据
|
||||
* @description 粗略检测,不保证数据完整性
|
||||
* @since Alpha v0.2.3
|
||||
* @description 检测是否存在 UIAF 数据,采用 ajv 验证 schema
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} path - UIAF 数据路径
|
||||
* @returns {Promise<boolean>} 是否存在 UIAF 数据
|
||||
*/
|
||||
export async function verifyUiafData(path: string): Promise<boolean> {
|
||||
const fileData: string = await fs.readTextFile(path);
|
||||
const UiafData: TGApp.Plugins.UIAF.Export = JSON.parse(fileData)?.info;
|
||||
return UiafData?.uiaf_version !== undefined;
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UiafSchema);
|
||||
try {
|
||||
const fileJson = JSON.parse(fileData);
|
||||
if (!validate(fileJson)) {
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIAF 数据验证失败,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIAF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIAF 数据格式错误,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 验证UIAF数据-剪贴板
|
||||
* @since Beta v0.4.7
|
||||
* @returns {boolean} 是否验证通过
|
||||
*/
|
||||
export async function verifyUiafDataClipboard(): Promise<boolean> {
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UiafSchema);
|
||||
const data = await window.navigator.clipboard.readText();
|
||||
try {
|
||||
const fileJson = JSON.parse(data);
|
||||
if (!validate(fileJson)) {
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIAF 数据验证失败,剪贴板数据:${data}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIAF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIAF 数据格式错误,剪贴板数据:${data}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,8 +9,9 @@ import Ajv from "ajv";
|
||||
import { ErrorObject } from "ajv/lib/types/index.js";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar.js";
|
||||
import { AppUigfSchema } from "../data/index.js";
|
||||
import { UigfSchema } from "../data/index.js";
|
||||
|
||||
import TGLogger from "./TGLogger.js";
|
||||
import { timestampToDate } from "./toolFunc";
|
||||
|
||||
/**
|
||||
@@ -78,7 +79,7 @@ export function convertDataToUigf(
|
||||
export async function verifyUigfData(path: string): Promise<boolean> {
|
||||
const fileData: string = await fs.readTextFile(path);
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(AppUigfSchema);
|
||||
const validate = ajv.compile(UigfSchema);
|
||||
try {
|
||||
const fileJson = JSON.parse(fileData);
|
||||
if (!validate(fileJson)) {
|
||||
@@ -87,11 +88,15 @@ export async function verifyUigfData(path: string): Promise<boolean> {
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIGF 数据验证失败,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIGF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIGF 数据格式错误,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
/**
|
||||
* @file utils/dataBS.ts
|
||||
* @description 用户数据的备份、恢复、迁移
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { fs, path } from "@tauri-apps/api";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar";
|
||||
import TGSqlite from "../plugins/Sqlite";
|
||||
import TSUserAchi from "../plugins/Sqlite/modules/userAchi.js";
|
||||
import TSUserGacha from "../plugins/Sqlite/modules/userGacha.js";
|
||||
|
||||
import { exportUigfData, readUigfData, verifyUigfData } from "./UIGF.js";
|
||||
|
||||
/**
|
||||
* @description 备份用户数据
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} dir 备份目录路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -20,8 +24,7 @@ export async function backUpUserData(dir: string): Promise<void> {
|
||||
console.log("备份目录不存在,创建备份目录");
|
||||
await fs.createDir(dir, { recursive: true });
|
||||
}
|
||||
// 备份成就数据
|
||||
const dataAchi = await TGSqlite.getUIAF();
|
||||
const dataAchi = await TSUserAchi.getUIAF();
|
||||
await fs.writeTextFile(`${dir}${path.sep}UIAF.json`, JSON.stringify(dataAchi));
|
||||
// 备份 ck
|
||||
const dataCK = await TGSqlite.getCookie();
|
||||
@@ -29,12 +32,17 @@ export async function backUpUserData(dir: string): Promise<void> {
|
||||
// 备份深渊数据
|
||||
const dataAbyss = await TGSqlite.getAbyss();
|
||||
await fs.writeTextFile(`${dir}${path.sep}abyss.json`, JSON.stringify(dataAbyss));
|
||||
// todo 添加祈愿数据备份支持
|
||||
// 备份祈愿数据
|
||||
const uidList = await TSUserGacha.getUidList();
|
||||
for (const uid of uidList) {
|
||||
const dataGacha = await TSUserGacha.getGachaRecords(uid);
|
||||
await exportUigfData(uid, dataGacha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 恢复用户数据
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} dir 备份目录路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -47,17 +55,18 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const files = (await fs.readDir(dir)).filter((item) => item.type === "File");
|
||||
// 恢复成就数据
|
||||
const dataAchiPath = `${dir}${path.sep}UIAF.json`;
|
||||
if (await fs.exists(dataAchiPath)) {
|
||||
const achiFind = files.find((item) => item.name === "UIAF.json");
|
||||
if (achiFind) {
|
||||
try {
|
||||
const dataAchi: TGApp.Plugins.UIAF.Achievement[] = JSON.parse(
|
||||
await fs.readTextFile(dataAchiPath),
|
||||
await fs.readTextFile(achiFind.path),
|
||||
);
|
||||
await TGSqlite.mergeUIAF(dataAchi);
|
||||
await TSUserAchi.mergeUIAF(dataAchi);
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: "成就数据恢复失败",
|
||||
text: `成就数据恢复失败 ${e}`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
@@ -69,14 +78,14 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
}
|
||||
// 恢复 ck
|
||||
const dataCKPath = `${dir}${path.sep}cookie.json`;
|
||||
if (await fs.exists(dataCKPath)) {
|
||||
const ckFind = files.find((item) => item.name === "cookie.json");
|
||||
if (ckFind) {
|
||||
try {
|
||||
const dataCK = await fs.readTextFile(dataCKPath);
|
||||
const dataCK = await fs.readTextFile(ckFind.path);
|
||||
await TGSqlite.saveAppData("cookie", JSON.stringify(JSON.parse(dataCK)));
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: "Cookie 数据恢复失败",
|
||||
text: `Cookie 数据恢复失败 ${e}`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
@@ -88,11 +97,11 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
}
|
||||
// 恢复深渊数据
|
||||
const dataAbyssPath = `${dir}${path.sep}abyss.json`;
|
||||
if (await fs.exists(dataAbyssPath)) {
|
||||
const abyssFind = files.find((item) => item.name === "abyss.json");
|
||||
if (abyssFind) {
|
||||
try {
|
||||
const dataAbyss: TGApp.Sqlite.Abyss.SingleTable[] = JSON.parse(
|
||||
await fs.readTextFile(dataAbyssPath),
|
||||
await fs.readTextFile(abyssFind.path),
|
||||
);
|
||||
await TGSqlite.restoreAbyss(dataAbyss);
|
||||
} catch (e) {
|
||||
@@ -108,6 +117,29 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
color: "warn",
|
||||
});
|
||||
}
|
||||
// 恢复祈愿数据
|
||||
const reg = /UIGF_(\d+).json/;
|
||||
const dataGachaList = files.filter((item) => reg.test(item.name));
|
||||
for (const item of dataGachaList) {
|
||||
const check = await verifyUigfData(item.path);
|
||||
if (!check) {
|
||||
errNum++;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const data = await readUigfData(item.path);
|
||||
const uid = data.info.uid;
|
||||
for (const item of data.list) {
|
||||
await TSUserGacha.mergeUIGF(uid, item);
|
||||
}
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: `UID: ${uid} 祈愿数据恢复失败`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
}
|
||||
}
|
||||
if (errNum === 0) {
|
||||
showSnackbar({
|
||||
text: "数据恢复成功",
|
||||
|
||||
Reference in New Issue
Block a user