- 恢复
+ 导入(v4)
+ 导出
+ 导出(v4)
+
+ 删除
@@ -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("");
@@ -267,36 +273,24 @@ async function getGachaLogs(
}
// 导入按钮点击事件
-async function handleImportBtn(savePath?: string): Promise {
- 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 {
+ 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 {
});
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 {
+ 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 {
});
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 {
+ 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 {
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 {
+// 导出当前UID的祈愿数据
+async function exportUigf(): Promise {
+ 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 {
});
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 {
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 {
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: "祈愿数据已成功导出",
- });
- await TGLogger.Info(`[UserGacha][${account.gameUid}][handleExportBtn] 导出祈愿数据完成`);
+ showSnackbar({ text: `成功导出 ${uidCur.value} 的祈愿数据` });
+ await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf] 导出祈愿数据完成`);
}
-// 恢复UID祈愿数据,相当于导入祈愿数据,不过目录固定
-async function restoreGacha(): Promise {
- await handleImportBtn(appStore.userDir);
-}
-
-// 备份当前 UID 的祈愿数据
-async function backupGacha(): Promise {
- if (gachaListCur.value.length === 0) {
+// 导出 UIGF v4 版本的祈愿数据
+async function exportUigf4(): Promise {
+ await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf4] 导出祈愿数据(v4)`);
+ const allConfirm = await showConfirm({
+ title: "是否导出所有 UID 的祈愿数据?",
+ text: "取消则只导出当前 UID 的祈愿数据",
+ });
+ if (allConfirm === undefined) {
showSnackbar({
- color: "error",
- text: "暂无祈愿数据",
+ color: "cancel",
+ text: "已取消祈愿数据导出",
});
return;
}
- await TGLogger.Info(`[UserGacha][${uidCur.value}][backupGacha] 备份祈愿数据`);
- const res = await showConfirm({
- title: "是否备份祈愿数据?",
- text: `UID:${uidCur.value},共 ${gachaListCur.value.length} 条数据`,
+ 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 (!res) {
+ if (!file) {
showSnackbar({
color: "cancel",
- text: "已取消祈愿数据备份",
+ text: "已取消文件保存",
});
- await TGLogger.Warn(`[UserGacha][${uidCur.value}][backupGacha] 已取消祈愿数据备份`);
return;
}
- loadingTitle.value = "正在备份祈愿数据";
+ loadingTitle.value = "正在导出祈愿数据";
loading.value = true;
- await backupUigfData(appStore.userDir, uidCur.value, gachaListCur.value);
+ if (allConfirm === false) {
+ await exportUigf4Data(file, uidCur.value);
+ } else {
+ await exportUigf4Data(file);
+ }
loading.value = false;
showSnackbar({
- text: `已成功备份 ${uidCur.value} 的祈愿数据`,
+ text: "祈愿数据已成功导出",
});
- await TGLogger.Info(
- `[UserGacha][${uidCur.value}][backupGacha] 成功备份 ${gachaListCur.value.length} 条祈愿数据`,
- );
+ await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf4] 导出祈愿数据完成`);
}
// 删除当前 UID 的祈愿数据
diff --git a/src/plugins/Sqlite/modules/userGacha.ts b/src/plugins/Sqlite/modules/userGacha.ts
index cb7df47c..784522a8 100644
--- a/src/plugins/Sqlite/modules/userGacha.ts
+++ b/src/plugins/Sqlite/modules/userGacha.ts
@@ -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}
+ */
+async function mergeUIGF4(data: TGApp.Plugins.UIGF.GachaHk4e): Promise {
+ 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;
diff --git a/src/types/Plugins/UIGF.d.ts b/src/types/Plugins/UIGF.d.ts
index 4fff6474..a2db3373 100644
--- a/src/types/Plugins/UIGF.d.ts
+++ b/src/types/Plugins/UIGF.d.ts
@@ -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[];
}
}
diff --git a/src/utils/UIGF.ts b/src/utils/UIGF.ts
index bcec600c..2c493c2c 100644
--- a/src/utils/UIGF.ts
+++ b/src/utils/UIGF.ts
@@ -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}
+ * @returns {Promise}
*/
-export async function getUigfHeader(uid: string): Promise {
+async function getUigfHeader(uid: string): Promise {
const stamp = Date.now();
return {
uid,
@@ -47,13 +48,28 @@ export async function getUigfHeader(uid: string): Promise {
+ 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} 是否存在 UIGF 数据
*/
-export async function verifyUigfData(path: string): Promise {
+export async function verifyUigfData(path: string, isVersion4: boolean = false): Promise {
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 {
}
}
+/**
+ * @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} UIGF 数据
+ * @returns {Promise} UIGF 数据
+ */
+export async function readUigfData(userPath: string): Promise {
+ 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} UIGF 数据
*/
-export async function readUigfData(userPath: string): Promise {
- const fileData = await readTextFile(userPath);
- return JSON.parse(fileData);
+export async function readUigf4Data(userPath: string): Promise {
+ 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}
*/
-export async function backupUigfData(
- dirPath: string,
- uid: string,
- gachaList: TGApp.Sqlite.GachaRecords.SingleTable[],
-): Promise {
- 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 {
+ 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));
}