重构UIGF导入导出备份恢复,支持UIGF4.2

This commit is contained in:
BTMuli
2026-02-07 21:24:35 +08:00
parent b5562a0fce
commit 9be8c78deb
10 changed files with 549 additions and 298 deletions

View File

@@ -1,63 +1,59 @@
/**
* 用户祈愿模块
* @since Beta v0.9.1
* @since Beta v0.9.5
*/
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import { path } from "@tauri-apps/api";
import { exists, mkdir, readDir } from "@tauri-apps/plugin-fs";
import TGLogger from "@utils/TGLogger.js";
import { getWikiBrief, timestampToDate } from "@utils/toolFunc.js";
import { exportUigf4Data, readUigf4Data, readUigfData, verifyUigfData } from "@utils/UIGF.js";
import { getUtc8Time, getWikiBrief, timestampToDate } from "@utils/toolFunc.js";
import TGSqlite from "../index.js";
/**
* 获取导入 Sql
* @since Beta v.6.0
* 导入物品
* @since Beta v0.9.5
* @param uid - UID
* @param gacha - UIGF数据
* @returns sql
* @param item - UIGF数据
* @returns Promise<void>
*/
function getInsertSql(uid: string, gacha: TGApp.Plugins.UIGF.GachaItem): string {
return `
INSERT INTO GachaRecords (uid, gachaType, itemId, count, time, name, type, rank, id, uigfType, updated)
VALUES ('${uid}', '${gacha.gacha_type}', '${gacha.item_id ?? null}', '${gacha.count ?? null}', '${gacha.time}',
'${gacha.name}', '${gacha.item_type ?? null}', '${gacha.rank_type ?? null}', '${gacha.id}',
'${gacha.uigf_gacha_type}', datetime('now', 'localtime')) ON CONFLICT (id)
DO
UPDATE
SET uid = '${uid}',
gachaType = '${gacha.gacha_type}',
uigfType = '${gacha.uigf_gacha_type}',
time = '${gacha.time}',
itemId = '${gacha.item_id ?? null}',
count = '${gacha.count ?? null}',
name = '${gacha.name}',
type = '${gacha.item_type ?? null}',
rank = '${gacha.rank_type ?? null}',
updated = datetime('now', 'localtime');
`;
}
/**
* 传入时间字符串跟对应时区转成utc8时间字符串
* @since Beta v0.7.5
* @param time - 时间字符串
* @param timezone - 时区
* @returns 转换后的时间戳
*/
function getUtc8Time(time: string, timezone: number): string {
const date = new Date(time);
const diffTimezone = -timezone + 8;
const realDate = new Date(date.getTime() + diffTimezone * 60 * 60 * 1000);
return timestampToDate(realDate.getTime());
async function insertGachaItem(uid: string, item: TGApp.Plugins.UIGF.GachaItem): Promise<void> {
const db = await TGSqlite.getDB();
const updateTime = timestampToDate(Date.now());
await db.execute(
`INSERT INTO GachaRecords(uid, gachaType, itemId, count, time,
name, type, rank, id, uigfType, updated)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
ON CONFLICT DO UPDATE
SET uid = $1,
gachaType = $2,
itemId = $3,
count = $4,
time = $5,
name = $6,
type = $7,
rank = $8,
uigfType = $10,
updated = $11;
`,
[
uid,
item.gacha_type,
item.item_id ?? null,
item.count ?? 1,
item.time,
item.name,
item.item_type ?? null,
item.rank_type ?? null,
item.id,
item.uigf_gacha_type,
updateTime,
],
);
}
/**
* 转换祈愿数据,防止多语言
* @since Beta v0.7.5
* @since Beta v0.9.5
* @param gacha - UIGF数据
* @param timezone - 时区
* @returns 转换后的数据
@@ -67,7 +63,7 @@ function transGacha(
timezone: number = 8,
): TGApp.Plugins.UIGF.GachaItem {
const find = getWikiBrief(gacha.item_id);
if (!find) return gacha;
if (!find) return { ...gacha, time: getUtc8Time(gacha.time, timezone) };
return {
gacha_type: gacha.gacha_type,
item_id: gacha.item_id,
@@ -246,7 +242,7 @@ async function cleanGachaRecords(
/**
* 合并祈愿数据
* @since Beta v0.9.0
* @since Beta v0.9.5
* @param uid - UID
* @param data - UIGF数据
* @param showProgress - 是否显示进度
@@ -257,7 +253,6 @@ async function mergeUIGF(
data: Array<TGApp.Plugins.UIGF.GachaItem>,
showProgress: boolean = false,
): Promise<void> {
const db = await TGSqlite.getDB();
let cnt = 0;
const len = data.length;
let progress = 0;
@@ -272,9 +267,7 @@ async function mergeUIGF(
}, 1000);
}
for (const gacha of data) {
const trans = transGacha(gacha);
const sql = getInsertSql(uid, trans);
await db.execute(sql);
await insertGachaItem(uid, transGacha(gacha));
cnt++;
}
if (timer) {
@@ -285,8 +278,8 @@ async function mergeUIGF(
}
/**
* 合并祈愿数据v4.0
* @since Beta v0.9.0
* 合并祈愿数据v4.x
* @since Beta v0.9.5
* @param data - UIGF数据
* @param showProgress - 是否显示进度
* @returns 无返回值
@@ -295,7 +288,6 @@ async function mergeUIGF4(
data: TGApp.Plugins.UIGF.GachaHk4e,
showProgress: boolean = false,
): Promise<void> {
const db = await TGSqlite.getDB();
let cnt: number = 0;
const len = data.list.length;
let progress: number = 0;
@@ -304,15 +296,13 @@ async function mergeUIGF4(
timer = setInterval(async () => {
progress = Math.round((cnt / len) * 100 * 100) / 100;
const current = data.list[cnt]?.time ?? "";
const name = data.list[cnt]?.name ?? "";
const name = data.list[cnt]?.name ?? data.list[cnt]?.item_id;
const rank = data.list[cnt]?.rank_type ?? "0";
await showLoading.update(`[${progress}%][${current}] ${"⭐".repeat(Number(rank))}-${name}`);
}, 1000);
}
for (const gacha of data.list) {
const trans = transGacha(gacha, data.timezone);
const sql = getInsertSql(data.uid.toString(), trans);
await db.execute(sql);
await insertGachaItem(data.uid.toString(), transGacha(gacha, data.timezone));
cnt++;
}
if (timer) {
@@ -322,85 +312,6 @@ async function mergeUIGF4(
}
}
/**
* 备份祈愿数据
* @since Beta v0.9.0
* @param dir - 备份目录
* @returns 无返回值
*/
async function backUpUigf(dir: string): Promise<void> {
if (!(await exists(dir))) {
await TGLogger.Warn("不存在指定的祈愿备份目录,即将创建");
await mkdir(dir, { recursive: true });
}
const uidList = await getUidList();
const savePath = `${dir}${path.sep()}UIGF4.json`;
await exportUigf4Data(uidList, savePath);
await TGLogger.Info("祈愿数据备份完成");
}
/**
* 恢复祈愿数据
* @since Beta v0.9.0
* @param dir - 备份目录
* @returns 是否恢复成功
*/
async function restoreUigf(dir: string): Promise<boolean> {
if (!(await exists(dir))) {
await TGLogger.Warn("不存在指定的祈愿备份目录");
return false;
}
let cnt = 0;
// 旧版备份文件 UIGF_{{uid}}
const legacyReg = /^UIGF_\d+\.json$/;
const legacyFiles = (await readDir(dir)).filter(
(item) => item.isFile && legacyReg.test(item.name),
);
if (legacyFiles.length > 0) {
try {
for (const file of legacyFiles) {
await showLoading.update(`祈愿文件:${file.name}`);
const filePath = `${dir}${path.sep()}${file.name}`;
const check = await verifyUigfData(filePath);
if (!check) {
await showLoading.update(`UIGF数据校验失败`);
await TGLogger.Warn(`UIGF数据校验失败${filePath}`);
continue;
}
const data = await readUigfData(filePath);
const uid = data.info.uid;
await showLoading.update(`正在导入${data.info.uid}${data.list.length}条祈愿数据`);
await mergeUIGF(uid, data.list, true);
cnt++;
}
} catch (e) {
await TGLogger.Error(`恢复祈愿数据失败${dir}`);
await TGLogger.Error(typeof e === "string" ? e : JSON.stringify(e));
}
}
const filePath = `${dir}${path.sep()}UIGF4.json`;
if (!(await exists(filePath))) {
await TGLogger.Warn(`未检测到UIGF4备份文件`);
} else {
try {
const check = await verifyUigfData(filePath, true);
if (!check) {
await TGLogger.Warn(`UIGF数据校验失败${filePath}`);
} else {
const data = await readUigf4Data(filePath);
for (const uidData of data.hk4e) {
await showLoading.update(`正在导入${uidData.uid}${uidData.list.length}条祈愿记录`);
await mergeUIGF4(uidData, true);
}
}
} catch (e) {
await TGLogger.Error(`恢复祈愿数据失败${dir}`);
await TGLogger.Error(typeof e === "string" ? e : JSON.stringify(e));
}
}
return cnt > 0;
}
/**
* 更新单条数据ID
* @since Beta v0.9.0
@@ -420,8 +331,6 @@ const TSUserGacha = {
cleanGachaRecords,
mergeUIGF,
mergeUIGF4,
backUpUigf,
restoreUigf,
update: {
itemId: updateItemIdById,
},

View File

@@ -1,56 +1,132 @@
/**
* 千星奇域祈愿模块
* @since Beta v0.8.4
* @since Beta v0.9.5
*/
import showSnackbar from "@comp/func/snackbar.js";
import showLoading from "@comp/func/loading.js";
import TGSqlite from "@Sql/index.js";
import { exists, mkdir } from "@tauri-apps/plugin-fs";
import TGLogger from "@utils/TGLogger.js";
import { getUtc8Time, timestampToDate } from "@utils/toolFunc.js";
import { AppGachaBData } from "@/data/index.js";
/**
* 获取导入 Sql
* @since Beta v0.8.4
* @param gacha - 抽卡记录数据
* @returns sql
* 插入颂愿数据
* @since Beta v0.9.5
* @param uid - UID
* @param item - 颂愿数据
* @returns 无返回值
*/
function getInsertSql(gacha: TGApp.Game.Gacha.GachaBItem): string {
return `
INSERT INTO GachaBRecords(id, uid, region, scheduleId, gachaType,
opGachaType, time, itemId, name, type,
rank, isUp, updated)
VALUES ('${gacha.id}', '${gacha.uid}', '${gacha.region}', '${gacha.schedule_id}',
'${gacha.op_gacha_type === "1000" ? "1000" : "2000"}', '${gacha.op_gacha_type}', '${gacha.time}',
'${gacha.item_id}', '${gacha.item_name}', '${gacha.item_type}',
'${gacha.rank_type}', '${gacha.is_up}', datetime('now', 'localtime'))
ON CONFLICT (id)
DO UPDATE
SET uid = '${gacha.uid}',
region = '${gacha.region}',
scheduleId = '${gacha.schedule_id}',
gachaType = '${gacha.op_gacha_type === "1000" ? "1000" : "2000"}',
opGachaType = '${gacha.op_gacha_type}',
time = '${gacha.time}',
itemId = '${gacha.item_id}',
name = '${gacha.item_name}',
type = '${gacha.item_type}',
rank = '${gacha.rank_type}',
isUp = '${gacha.is_up}',
updated = datetime('now', 'localtime');
`;
async function insertGachaItem(uid: string, item: TGApp.Plugins.UIGF.GachaItemB): Promise<void> {
const db = await TGSqlite.getDB();
const gachaType = item.op_gacha_type === "1000" ? "1000" : "2000";
const updateTime = timestampToDate(Date.now());
await db.execute(
`
INSERT INTO GachaBRecords(id, uid, scheduleId, gachaType, opGachaType, time,
itemId, name, type, rank, updated)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
ON CONFLICT (id)
DO UPDATE
SET uid = $2,
scheduleId = $3,
gachaType = $4,
opGachaType = $5,
time = $6,
itemId = $7,
name = $8,
type = $9,
rank = $10,
updated = $11;
`,
[
item.id,
uid,
item.schedule_id,
gachaType,
item.op_gacha_type,
item.time,
item.item_id,
item.item_name,
item.item_type,
item.rank_type,
updateTime,
],
);
}
/**
* 插入列表数据
* @since Beta v0.8.4
* @since Beta v0.9.5
* @param uid - UID
* @param list - 抽卡记录列表
* @returns 无返回值
*/
async function insertGachaList(list: Array<TGApp.Game.Gacha.GachaBItem>): Promise<void> {
const db = await TGSqlite.getDB();
async function insertGachaList(
uid: string,
list: Array<TGApp.Plugins.UIGF.GachaItemB>,
): Promise<void> {
for (const gacha of list) {
const sql = getInsertSql(gacha);
await db.execute(sql);
await insertGachaItem(uid, gacha);
}
}
/**
* 转换颂愿数据,防止多语言
* @since Beta v0.9.5
* @param gacha - 颂愿数据
* @param timezone - 时区
* @returns 转换后的数据
*/
function transGacha(
gacha: TGApp.Plugins.UIGF.GachaItemB,
timezone: number = 8,
): TGApp.Plugins.UIGF.GachaItemB {
const find = AppGachaBData.find((i) => i.id === gacha.item_id);
if (!find) return gacha;
return {
id: gacha.id,
item_id: gacha.item_id,
item_name: find.name,
item_type: find.type,
op_gacha_type: gacha.op_gacha_type,
rank_type: find.rank.toString(),
schedule_id: gacha.schedule_id,
time: getUtc8Time(gacha.time, timezone),
};
}
/**
* 插入UIGF4数据
* @since Beta v0.9.5
* @param data - UIGF数据
* @param showProgress - 是否显示进度
* @returns 无返回值
*/
async function mergeUIGF4(
data: TGApp.Plugins.UIGF.GachaUgc,
showProgress: boolean = false,
): Promise<void> {
let cnt: number = 0;
const len = data.list.length;
let progress: number = 0;
let timer: NodeJS.Timeout | null = null;
if (showProgress) {
timer = setInterval(async () => {
progress = Math.round((cnt / len) * 100 * 100) / 100;
const current = data.list[cnt].time ?? "";
const name = data.list[cnt].item_name ?? "";
const rank = data.list[cnt].rank_type ?? "0";
await showLoading.update(`[${progress}%][${current}] ${"⭐".repeat(Number(rank))}-${name}`);
}, 1000);
}
for (const gacha of data.list) {
await insertGachaItem(data.uid.toString(), transGacha(gacha, data.timezone));
cnt++;
}
if (timer) {
clearInterval(timer);
progress = 100;
await showLoading.update(`[${progress}%] 完成`);
}
}
@@ -105,35 +181,6 @@ async function getGachaRecords(
return await db.select("SELECT * FROM GachaBRecords WHERE uid = ?;", [uid]);
}
/**
* 备份祈愿数据
* @since Beta v0.8.4
* @param dir - 备份目录
* @remarks 等UIGF标准最终确定后与TSUserGacha合并
*/
async function backUpUigf(dir: string): Promise<void> {
if (!(await exists(dir))) {
await TGLogger.Warn("不存在指定的祈愿备份目录,即将创建");
await mkdir(dir, { recursive: true });
}
showSnackbar.error(`千星奇域祈愿数据备份功能尚未实现,请耐心等待后续版本更新。`);
}
/**
* 恢复祈愿数据
* @since Beta v0.8.4
* @param dir - 恢复目录
* @remarks 等UIGF标准最终确定后与TSUserGacha合并
* @returns 是否恢复成功
*/
async function restoreUigf(dir: string): Promise<boolean> {
if (!(await exists(dir))) {
await TGLogger.Warn("不存在指定的祈愿备份目录");
return false;
}
return true;
}
/**
* 删除用户祈愿数据
* @since Beta v0.8.4
@@ -145,18 +192,13 @@ async function deleteRecords(uid: string): Promise<void> {
await db.execute("DELETE FROM GachaBRecords WHERE uid = ?;", [uid]);
}
/**
* 千星奇域祈愿模块
* @since Beta v0.8.4
*/
const TSUserGachaB = {
getUidList,
getGachaCheck,
getGachaRecords,
insertGachaList,
backUpUigf,
restoreUigf,
deleteRecords,
mergeUIGF4,
};
export default TSUserGachaB;

View File

@@ -182,12 +182,12 @@ create table if not exists GachaRecords
updated text
);
-- @brief 创建千星奇域祈愿数据表
-- @brief 创建愿数据表
create table if not exists GachaBRecords
(
id text primary key not null,
uid text,
region text,
region text, -- @deprecated
scheduleId text,
gachaType text,
opGachaType text,
@@ -196,7 +196,7 @@ create table if not exists GachaBRecords
name text,
type text,
rank text,
isUp text,
isUp text, -- @deprecated
updated text
);