diff --git a/src/data/index.ts b/src/data/index.ts index 65d47e2b..a3630042 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -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; diff --git a/src/data/schema/uigf4-schema.json b/src/data/schema/uigf4-schema.json new file mode 100644 index 00000000..b875eec7 --- /dev/null +++ b/src/data/schema/uigf4-schema.json @@ -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"] +} diff --git a/src/pages/User/Gacha.vue b/src/pages/User/Gacha.vue index c4fd91be..ab25580b 100644 --- a/src/pages/User/Gacha.vue +++ b/src/pages/User/Gacha.vue @@ -10,15 +10,17 @@ 全量刷新 - 导入 - 导出 - - 备份 + 导入 - 删除 - - 恢复 + 导入(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)); }