采用 ajv 验证 UIGF #109

This commit is contained in:
目棃
2024-05-05 15:47:31 +08:00
parent 5de9c0a76f
commit 7cd66ffb2d
6 changed files with 536 additions and 409 deletions

View File

@@ -0,0 +1,96 @@
{
"type": "object",
"properties": {
"info": {
"type": "object",
"properties": {
"uid": {
"type": "string",
"title": "UID of the export record"
},
"lang": {
"type": "string",
"title": "language in the format of languagecode2-country/regioncode2"
},
"export_timestamp": {
"type": "number",
"title": "Export UNIX timestamp (accurate to the second)"
},
"export_time": {
"type": "string",
"title": "Export time",
"description": "yyyy-MM-dd HH:mm:ss"
},
"export_app": {
"type": "string",
"title": "Name of the export application"
},
"export_app_version": {
"type": "string",
"title": "Version of the export application"
},
"uigf_version": {
"type": "string",
"title": "UIGF version; follow the regular expression pattern",
"pattern": "v\\d+\\.\\d+"
},
"region_time_zone": {
"type": "number",
"title": "Region timezone offset"
}
},
"required": ["uid", "uigf_version"],
"title": "UIGF Export Information"
},
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"uigf_gacha_type": {
"type": "string",
"title": "UIGF gacha type",
"description": "Used to differentiate different gacha types with the same pity calculation for items"
},
"gacha_type": {
"type": "string",
"title": "Gacha type"
},
"item_id": {
"type": "string",
"title": "Internal ID of the item"
},
"count": {
"type": "string",
"title": "Count, usually 1"
},
"time": {
"type": "string",
"title": "Time when the item was obtained"
},
"name": {
"type": "string",
"title": "Item name"
},
"item_type": {
"type": "string",
"title": "Item type"
},
"rank_type": {
"type": "string",
"title": "Item rank"
},
"id": {
"type": "string",
"title": "Internal ID of the record"
}
},
"required": ["uigf_gacha_type", "gacha_type", "id", "item_id", "time"],
"title": "UIGF Item"
},
"title": "Item List"
}
},
"required": ["info", "list"],
"title": "UIGF Root Object"
}

View File

@@ -1,9 +1,11 @@
/**
* @file src/data/index.ts
* @description 数据文件入口
* @since Beta v0.4.4
* @since Beta v0.4.7
*/
import type { SchemaType } from "ajv/lib/types/index.js";
// 应用数据
import achievements from "./app/achievements.json";
import achievementSeries from "./app/achievementSeries.json";
@@ -12,6 +14,7 @@ import character from "./app/character.json";
import gacha from "./app/gacha.json";
import GCG from "./app/GCG.json";
import nameCards from "./app/namecard.json";
import uigfSchema from "./app/uigf-schema.json";
import weapon from "./app/weapon.json";
// 存档数据
import arcBirCalendar from "./archive/birth_calendar.json";
@@ -30,6 +33,7 @@ export const AppGachaData: TGApp.App.Gacha.PoolItem[] = gacha;
export const AppGCGData: TGApp.App.GCG.WikiBriefInfo[] = GCG;
export const AppNameCardsData: TGApp.App.NameCard.Item[] = nameCards;
export const AppWeaponData: TGApp.App.Weapon.WikiBriefInfo[] = weapon;
export const AppUigfSchema: SchemaType = uigfSchema;
export const ArcBirCalendar: TGApp.Archive.Birth.CalendarData = arcBirCalendar;
export const ArcBirDraw: TGApp.Archive.Birth.DrawItem[] = arcBirDraw;
export const ArcBirRole: TGApp.Archive.Birth.RoleItem[] = arcBirRole;

View File

@@ -304,13 +304,7 @@ async function handleImportBtn(savePath?: string): Promise<void> {
return;
}
const check = await verifyUigfData(<string>selectedFile);
if (!check) {
showSnackbar({
color: "error",
text: "读取 UIGF 文件失败,请检查文件是否符合规范",
});
return;
}
if (!check) return;
const remoteData = await readUigfData(<string>selectedFile);
const res = await showConfirm({
title: "是否导入祈愿数据?",
@@ -342,9 +336,9 @@ async function handleImportBtn(savePath?: string): Promise<void> {
await TGLogger.Info(
`[UserGacha][handleImportBtn] 成功导入 ${remoteData.info.uid}${remoteData.list.length} 条祈愿数据`,
);
// setTimeout(() => {
// window.location.reload();
// }, 1000);
setTimeout(() => {
window.location.reload();
}, 1000);
}
// 导出按钮点击事件

View File

@@ -1,10 +1,15 @@
/**
* @file utils/UIGF.ts
* @description UIGF工具类
* @since Beta v0.4.4
* @since Beta v0.4.7
*/
import { app, fs, path } from "@tauri-apps/api";
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 { timestampToDate } from "./toolFunc";
@@ -65,16 +70,30 @@ export function convertDataToUigf(
}
/**
* @description 检测是否存在 UIGF 数据
* @description 粗略检测,不保证数据完整性
* @since Alpha v0.2.3
* @description 检测是否存在 UIGF 数据,采用 ajv 验证 schema
* @since Beta v0.4.7
* @param {string} path - UIGF 数据路径
* @returns {Promise<boolean>} 是否存在 UIGF 数据
*/
export async function verifyUigfData(path: string): Promise<boolean> {
const fileData: string = await fs.readTextFile(path);
const UigfData: TGApp.Plugins.UIGF.Export = JSON.parse(fileData)?.info;
return UigfData?.uigf_version !== undefined;
const ajv = new Ajv();
const validate = ajv.compile(AppUigfSchema);
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",
});
return false;
}
return true;
} catch (e) {
showSnackbar({ text: `UIGF 数据格式错误 ${e}`, color: "error" });
return false;
}
}
/**