UIGFv4支持

This commit is contained in:
目棃
2024-07-13 14:41:20 +08:00
parent 5d054c309a
commit cf66a7e128
6 changed files with 637 additions and 141 deletions

View File

@@ -1,7 +1,7 @@
/** /**
* @file src/data/index.ts * @file src/data/index.ts
* @description 数据文件入口 * @description 数据文件入口
* @since Beta v0.4.7 * @since Beta v0.5.0
*/ */
import type { Schema } from "ajv"; import type { Schema } from "ajv";
@@ -19,6 +19,7 @@ import arcBirDraw from "./archive/birth_draw.json";
import arcBirRole from "./archive/birth_role.json"; import arcBirRole from "./archive/birth_role.json";
import schemaUiaf from "./schema/uiaf-schema.json"; import schemaUiaf from "./schema/uiaf-schema.json";
import schemaUigf from "./schema/uigf-schema.json"; import schemaUigf from "./schema/uigf-schema.json";
import scheamUigf4 from "./schema/uigf4-schema.json";
import wikiCharacter from "./WIKI/character.json"; import wikiCharacter from "./WIKI/character.json";
import wikiMaterial from "./WIKI/material.json"; import wikiMaterial from "./WIKI/material.json";
import wikiWeapon from "./WIKI/weapon.json"; import wikiWeapon from "./WIKI/weapon.json";
@@ -35,6 +36,7 @@ export const AppWeaponData: TGApp.App.Weapon.WikiBriefInfo[] = weapon;
// Schema // Schema
export const UiafSchema: Schema = schemaUiaf; export const UiafSchema: Schema = schemaUiaf;
export const UigfSchema: Schema = schemaUigf; export const UigfSchema: Schema = schemaUigf;
export const Uigf4Schema: Schema = scheamUigf4;
// Archive // Archive
export const ArcBirCalendar: TGApp.Archive.Birth.CalendarData = arcBirCalendar; export const ArcBirCalendar: TGApp.Archive.Birth.CalendarData = arcBirCalendar;
export const ArcBirDraw: TGApp.Archive.Birth.DrawItem[] = arcBirDraw; export const ArcBirDraw: TGApp.Archive.Birth.DrawItem[] = arcBirDraw;

View 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"]
}

View File

@@ -10,15 +10,17 @@
<v-btn prepend-icon="mdi-refresh" class="gacha-top-btn" @click="confirmRefresh(true)" <v-btn prepend-icon="mdi-refresh" class="gacha-top-btn" @click="confirmRefresh(true)"
>全量刷新 >全量刷新
</v-btn> </v-btn>
<v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn()">导入</v-btn> <v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn(false)"
<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> </v-btn>
<v-btn prepend-icon="mdi-delete" class="gacha-top-btn" @click="deleteGacha">删除</v-btn> <v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn(true)"
<v-btn prepend-icon="mdi-cloud-upload" class="gacha-top-btn" @click="restoreGacha"> >导入(v4)
恢复
</v-btn> </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> </div>
<div class="gacha-container"> <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 ToLoading from "../../components/overlay/to-loading.vue";
import { AppCharacterData, AppWeaponData } from "../../data/index.js"; import { AppCharacterData, AppWeaponData } from "../../data/index.js";
import TSUserGacha from "../../plugins/Sqlite/modules/userGacha.js"; import TSUserGacha from "../../plugins/Sqlite/modules/userGacha.js";
import { useAppStore } from "../../store/modules/app.js";
import { useUserStore } from "../../store/modules/user.js"; import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.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"; import TGRequest from "../../web/request/TGRequest.js";
// store // store
const appStore = useAppStore();
const userStore = storeToRefs(useUserStore()); const userStore = storeToRefs(useUserStore());
const account = userStore.account.value; const account = userStore.account.value;
const authkey = ref<string>(""); const authkey = ref<string>("");
@@ -267,36 +273,24 @@ async function getGachaLogs(
} }
// 导入按钮点击事件 // 导入按钮点击事件
async function handleImportBtn(savePath?: string): Promise<void> { async function handleImportBtn(isV4: boolean): Promise<void> {
await TGLogger.Info("[UserGacha][handleImportBtn] 导入祈愿数据"); if (isV4) {
let selectedFile; await TGLogger.Info("[UserGacha][handleImportBtn] 导入祈愿数据(v4)");
if (savePath) {
selectedFile = await open({
multiple: false,
title: "选择要导入的祈愿数据文件",
filters: [
{
name: "UIGF JSON",
extensions: ["json"],
},
],
defaultPath: savePath,
directory: false,
});
} else { } else {
selectedFile = await open({ await TGLogger.Info("[UserGacha][handleImportBtn] 导入祈愿数据");
multiple: false,
title: "选择要导入的祈愿数据文件",
filters: [
{
name: "UIGF JSON",
extensions: ["json"],
},
],
defaultPath: `${await path.downloadDir()}`,
directory: false,
});
} }
const selectedFile = await open({
multiple: false,
title: "选择要导入的祈愿数据文件",
filters: [
{
name: "UIGF JSON",
extensions: ["json"],
},
],
defaultPath: await path.downloadDir(),
directory: false,
});
if (!selectedFile) { if (!selectedFile) {
showSnackbar({ showSnackbar({
color: "cancel", color: "cancel",
@@ -304,14 +298,24 @@ async function handleImportBtn(savePath?: string): Promise<void> {
}); });
return; return;
} }
const check = await verifyUigfData(selectedFile.path); const check = await verifyUigfData(selectedFile.path, isV4);
if (!check) return; 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({ const res = await showConfirm({
title: "是否导入祈愿数据?", title: "是否导入祈愿数据?",
text: `UID${remoteData.info.uid}${remoteData.list.length} 条数据`, text: `${uidCount} 个 UID${dataCount} 条数据`,
}); });
await TGLogger.Info(`[UserGacha][${account.gameUid}][handleImportBtn] 确认导入祈愿数据`);
if (!res) { if (!res) {
showSnackbar({ showSnackbar({
color: "cancel", color: "cancel",
@@ -319,6 +323,37 @@ async function handleImportBtn(savePath?: string): Promise<void> {
}); });
return; 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 = "正在导入祈愿数据"; loadingTitle.value = "正在导入祈愿数据";
loading.value = true; loading.value = true;
if (remoteData.list.length === 0) { if (remoteData.list.length === 0) {
@@ -335,15 +370,16 @@ async function handleImportBtn(savePath?: string): Promise<void> {
text: `成功导入 ${remoteData.list.length} 条祈愿数据`, text: `成功导入 ${remoteData.list.length} 条祈愿数据`,
}); });
await TGLogger.Info( await TGLogger.Info(
`[UserGacha][handleImportBtn] 成功导入 ${remoteData.info.uid}${remoteData.list.length} 条祈愿数据`, `[UserGacha][importUigf] 成功导入 ${remoteData.info.uid}${remoteData.list.length} 条祈愿数据`,
); );
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 1000); }, 1000);
} }
// 导出按钮点击事件 // 导出当前UID的祈愿数据
async function handleExportBtn(): Promise<void> { async function exportUigf(): Promise<void> {
await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf] 导出祈愿数据`);
const gachaList = await TSUserGacha.getGachaRecords(uidCur.value); const gachaList = await TSUserGacha.getGachaRecords(uidCur.value);
if (gachaList.length === 0) { if (gachaList.length === 0) {
showSnackbar({ showSnackbar({
@@ -352,15 +388,14 @@ async function handleExportBtn(): Promise<void> {
}); });
return; return;
} }
const res = await showConfirm({ const res = showConfirm({
title: "是否导出祈愿数据?", title: "是否导出祈愿数据?",
text: `UID${uidCur.value},共 ${gachaList.length} 条数据`, text: `UID${uidCur.value},共 ${gachaList.length} 条数据`,
}); });
await TGLogger.Info(`[UserGacha][${account.gameUid}][handleExportBtn] 导出祈愿数据`);
if (!res) { if (!res) {
showSnackbar({ showSnackbar({
color: "cancel", color: "cancel",
text: "已取消祈愿数据导出", text: `已取消 UID ${uidCur.value}祈愿数据导出`,
}); });
return; return;
} }
@@ -372,7 +407,7 @@ async function handleExportBtn(): Promise<void> {
extensions: ["json"], extensions: ["json"],
}, },
], ],
defaultPath: `${await path.downloadDir()}${path.sep()}UIGF${uidCur.value}.json`, defaultPath: `${await path.downloadDir()}${path.sep()}UIGF_${uidCur.value}.json`,
}); });
if (!file) { if (!file) {
showSnackbar({ showSnackbar({
@@ -382,55 +417,69 @@ async function handleExportBtn(): Promise<void> {
return; return;
} }
await TGLogger.Info( 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; loading.value = true;
await exportUigfData(uidCur.value, gachaList, file); await exportUigfData(uidCur.value, gachaList, file);
loading.value = false; 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({ showSnackbar({
text: "祈愿数据已成功导出", text: "祈愿数据已成功导出",
}); });
await TGLogger.Info(`[UserGacha][${account.gameUid}][handleExportBtn] 导出祈愿数据完成`); await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf4] 导出祈愿数据完成`);
}
// 恢复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} 条祈愿数据`,
);
} }
// 删除当前 UID 的祈愿数据 // 删除当前 UID 的祈愿数据

View File

@@ -1,7 +1,7 @@
/** /**
* @file plugins/Sqlite/modules/userGacha.ts * @file plugins/Sqlite/modules/userGacha.ts
* @description 用户祈愿模块 * @description 用户祈愿模块
* @since Beta v0.4.7 * @since Beta v0.5.0
*/ */
import { AppCharacterData, AppWeaponData } from "../../../data/index.js"; 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 = { const TSUserGacha = {
getUidList, getUidList,
getGachaCheck, getGachaCheck,
@@ -142,6 +157,7 @@ const TSUserGacha = {
getGachaItemType, getGachaItemType,
deleteGachaRecords, deleteGachaRecords,
mergeUIGF, mergeUIGF,
mergeUIGF4,
}; };
export default TSUserGacha; export default TSUserGacha;

View File

@@ -1,28 +1,41 @@
/** /**
* @file types/Plugins/UIGF.d.ts * @file types/Plugins/UIGF.d.ts
* @description UIGF 插件类型定义文件 * @description UIGF 插件类型定义文件
* @since Beta v0.3.5 * @since Beta v0.5.0
* @version UIGF v2.4 * @version UIGF v3.0 | UIGF v4.0
*/ */
declare namespace TGApp.Plugins.UIGF { declare namespace TGApp.Plugins.UIGF {
/** /**
* @description UIGF 数据 * @description UIGF 数据
* @since Alpha v0.2.3 * @since Beta v0.5.0
* @interface FullData * @interface Schema
* @property {Export} info - UIGF 头部信息 * @property {Info} info - UIGF 头部信息
* @property {GachaItem[]} list - UIGF 祈愿列表 * @property {GachaItem[]} list - UIGF 祈愿列表
* @return FullData * @return Schema
*/ */
interface FullData { interface Schema {
info: Export; info: Info;
list: GachaItem[]; 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 头部信息 * @description UIGF 头部信息
* @since Beta v0.3.5 * @since Beta v0.5.0
* @interface Export * @interface Info
* @see docs\UIGF.md * @see docs\UIGF.md
* @property {string} uid - UID * @property {string} uid - UID
* @property {string} lang - 语言 * @property {string} lang - 语言
@@ -32,9 +45,9 @@ declare namespace TGApp.Plugins.UIGF {
* @property {string} export_app - 导出应用 * @property {string} export_app - 导出应用
* @property {string} export_app_version - 导出应用版本 * @property {string} export_app_version - 导出应用版本
* @property {number} region_time_zone - 时区 * @property {number} region_time_zone - 时区
* @return Export * @return Info
*/ */
interface Export { interface Info {
uid: string; uid: string;
lang: string; lang: string;
uigf_version: string; uigf_version: string;
@@ -45,6 +58,24 @@ declare namespace TGApp.Plugins.UIGF {
region_time_zone?: number; 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 祈愿类型 * @description 祈愿类型
* @since Alpha v0.2.3 * @since Alpha v0.2.3
@@ -97,6 +128,7 @@ declare namespace TGApp.Plugins.UIGF {
* @return GachaItem * @return GachaItem
*/ */
interface GachaItem { interface GachaItem {
uigf_gacha_type: string;
gacha_type: string; gacha_type: string;
item_id?: string; item_id?: string;
count?: string; count?: string;
@@ -105,6 +137,22 @@ declare namespace TGApp.Plugins.UIGF {
item_type?: string; item_type?: string;
rank_type?: string; rank_type?: string;
id: 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[];
} }
} }

View File

@@ -5,12 +5,13 @@
*/ */
import { app, path } from "@tauri-apps/api"; 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 Ajv from "ajv";
import { ErrorObject } from "ajv/lib/types/index.js"; import { ErrorObject } from "ajv/lib/types/index.js";
import showSnackbar from "../components/func/snackbar.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 TGLogger from "./TGLogger.js";
import { timestampToDate } from "./toolFunc.js"; import { timestampToDate } from "./toolFunc.js";
@@ -31,9 +32,9 @@ function getUigfTimeZone(uid: string): number {
* @description 获取 UIGF 头部信息 * @description 获取 UIGF 头部信息
* @since Beta v0.4.4 * @since Beta v0.4.4
* @param {string} uid - UID * @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(); const stamp = Date.now();
return { return {
uid, 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 * @description 数据转换-数据库到 UIGF
* @since Alpha v0.2.3 * @since Alpha v0.2.3
* @param {TGApp.Sqlite.GachaRecords.SingleTable[]} data - 数据库数据 * @param {TGApp.Sqlite.GachaRecords.SingleTable[]} data - 数据库数据
* @returns {TGApp.Plugins.UIGF.GachaItem[]} UIGF 数据 * @returns {TGApp.Plugins.UIGF.GachaItem[]} UIGF 数据
*/ */
export function convertDataToUigf( function convertDataToUigf(
data: TGApp.Sqlite.GachaRecords.SingleTable[], data: TGApp.Sqlite.GachaRecords.SingleTable[],
): TGApp.Plugins.UIGF.GachaItem[] { ): TGApp.Plugins.UIGF.GachaItem[] {
return data.map((gacha) => { return data.map((gacha) => {
@@ -75,26 +91,15 @@ export function convertDataToUigf(
* @description 检测是否存在 UIGF 数据,采用 ajv 验证 schema * @description 检测是否存在 UIGF 数据,采用 ajv 验证 schema
* @since Beta v0.5.0 * @since Beta v0.5.0
* @param {string} path - UIGF 数据路径 * @param {string} path - UIGF 数据路径
* @param {boolean} isVersion4 - 是否为 UIGF v4.0
* @returns {Promise<boolean>} 是否存在 UIGF 数据 * @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 fileData: string = await readTextFile(path);
const ajv = new Ajv();
const validate = ajv.compile(UigfSchema);
try { try {
const fileJson = JSON.parse(fileData); const fileJson = JSON.parse(fileData);
if (!validate(fileJson)) { if (isVersion4) return validateUigf4Data(fileJson);
if (!validate.errors || validate.errors.length === 0) return false; return validateUigfData(fileJson);
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;
} catch (e) { } catch (e) {
showSnackbar({ text: `UIGF 数据格式错误 ${e}`, color: "error" }); showSnackbar({ text: `UIGF 数据格式错误 ${e}`, color: "error" });
await TGLogger.Error(`UIGF 数据格式错误,文件路径:${path}`); 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 数据 * @description 读取 UIGF 数据
* @since Beta v0.5.0 * @since Beta v0.5.0
* @param {string} userPath - UIGF 数据路径 * @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> { export async function readUigfData(userPath: string): Promise<TGApp.Plugins.UIGF.Schema> {
const fileData = await readTextFile(userPath); const fileData: string = await readTextFile(userPath);
return <TGApp.Plugins.UIGF.FullData>JSON.parse(fileData); 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 * @since Beta v0.5.0
* @param {string} dirPath - 备份路径 * @param {string} filePath - 保存路径
* @param {string} uid - UID * @param {string} uid - UID,如果为空表示导出所有数据
* @param {TGApp.Sqlite.GachaRecords.SingleTable[]} gachaList - 祈愿列表
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export async function backupUigfData( export async function exportUigf4Data(filePath: string, uid?: string): Promise<void> {
dirPath: string, const UigfData: TGApp.Plugins.UIGF.Schema4 = {
uid: string, info: await getUigf4Header(),
gachaList: TGApp.Sqlite.GachaRecords.SingleTable[], hk4e: [],
): Promise<void> { };
if (!(await exists(dirPath))) await mkdir(dirPath, { recursive: true }); let uidList: string[] = [];
await exportUigfData(uid, gachaList, `${dirPath}${path.sep()}UIGF_${uid}.json`); 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));
} }