From 62052d126f1feabddd651ed2cb8de7faaa4ab7ac Mon Sep 17 00:00:00 2001 From: BTMuli Date: Mon, 12 Jan 2026 01:05:11 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E4=B8=8B=E8=BD=BD=E7=A5=88?= =?UTF-8?q?=E6=84=BF=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #202 --- package.json | 2 + pnpm-lock.yaml | 23 ++++ src/components/userGacha/ugo-hutao-du.vue | 29 ++-- src/pages/User/Gacha.vue | 158 ++++++++++++++++++++-- src/plugins/Hutao/index.ts | 10 +- src/plugins/Hutao/request/gachaReq.ts | 38 ++++-- src/plugins/Hutao/types/Gacha.d.ts | 8 +- src/store/modules/hutao.ts | 5 +- src/types/Plugins/Hakushi.d.ts | 7 +- src/utils/Hakushi.ts | 6 +- src/utils/TGHttp.ts | 9 +- 11 files changed, 258 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index f42e470a..666fee2b 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "html2canvas": "^1.4.1", "js-md5": "^0.8.3", "jsencrypt": "^3.5.4", + "json-bigint": "^1.0.0", "pinia": "^3.0.4", "pinia-plugin-persistedstate": "^4.7.1", "qrcode.vue": "^3.6.0", @@ -118,6 +119,7 @@ "@tauri-apps/cli": "2.9.6", "@types/fs-extra": "^11.0.4", "@types/js-md5": "^0.8.0", + "@types/json-bigint": "^1.0.4", "@types/node": "^25.0.6", "@typescript-eslint/parser": "^8.52.0", "@typescript/native-preview": "7.0.0-dev.20260109.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9bb460ec..e9ca651b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: jsencrypt: specifier: ^3.5.4 version: 3.5.4 + json-bigint: + specifier: ^1.0.0 + version: 1.0.0 pinia: specifier: ^3.0.4 version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) @@ -147,6 +150,9 @@ importers: '@types/js-md5': specifier: ^0.8.0 version: 0.8.0 + '@types/json-bigint': + specifier: ^1.0.4 + version: 1.0.4 '@types/node': specifier: ^25.0.6 version: 25.0.6 @@ -1461,6 +1467,9 @@ packages: '@types/js-md5@0.8.0': resolution: {integrity: sha512-gQkc1Felhyj+aB9jmz/ICLm1fDPQx7l/60JIBSSEC+j09JeaINlzd0Wj9LZlQkHnV5rJYkroOHE5wdbDgADJrw==} + '@types/json-bigint@1.0.4': + resolution: {integrity: sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1826,6 +1835,9 @@ packages: resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} hasBin: true + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -2815,6 +2827,9 @@ packages: engines: {node: '>=6'} hasBin: true + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -5496,6 +5511,8 @@ snapshots: '@types/js-md5@0.8.0': {} + '@types/json-bigint@1.0.4': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -5986,6 +6003,8 @@ snapshots: baseline-browser-mapping@2.9.14: {} + bignumber.js@9.3.1: {} + binary-extensions@2.3.0: {} birpc@2.9.0: {} @@ -7096,6 +7115,10 @@ snapshots: jsesc@3.1.0: {} + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} diff --git a/src/components/userGacha/ugo-hutao-du.vue b/src/components/userGacha/ugo-hutao-du.vue index 13f6f91c..11a0fbe6 100644 --- a/src/components/userGacha/ugo-hutao-du.vue +++ b/src/components/userGacha/ugo-hutao-du.vue @@ -2,7 +2,7 @@ @@ -156,7 +165,7 @@ import GroHistory from "@comp/userGacha/gro-history.vue"; import GroIframe from "@comp/userGacha/gro-iframe.vue"; import GroOverview from "@comp/userGacha/gro-overview.vue"; import GroTable from "@comp/userGacha/gro-table.vue"; -import UgoHutaoDu from "@comp/userGacha/ugo-hutao-du.vue"; +import UgoHutaoDu, { type UgoHutaoMode } from "@comp/userGacha/ugo-hutao-du.vue"; import UgoUid from "@comp/userGacha/ugo-uid.vue"; import hutao from "@Hutao/index.js"; import hk4eReq from "@req/hk4eReq.js"; @@ -175,7 +184,7 @@ import { storeToRefs } from "pinia"; import { onMounted, ref, shallowRef, watch } from "vue"; import { useRouter } from "vue-router"; -import { AppCharacterData, AppWeaponData } from "@/data/index.js"; +import { AppCalendarData, AppCharacterData, AppWeaponData } from "@/data/index.js"; const router = useRouter(); const hutaoStore = useHutaoStore(); @@ -190,7 +199,7 @@ const tab = ref("overview"); const ovShow = ref(false); const hutaoShow = ref(false); const ovMode = ref<"export" | "import">("import"); -const htMode = ref<"download" | "upload">("download"); +const htMode = ref("download"); const selectItem = shallowRef>([]); const gachaListCur = shallowRef>([]); const hakushiData = shallowRef>([]); @@ -243,9 +252,24 @@ async function tryDownloadGacha(): Promise { hutaoShow.value = true; } -async function handleHutaoDu(uids: Array, isUpload: boolean): Promise { - if (isUpload) await handleHutaoUpload(uids); - else await handleHutaoDownload(uids); +async function tryDeleteGacha(): Promise { + if (!isLoginHutao.value) return; + htMode.value = "delete"; + hutaoShow.value = true; +} + +async function handleHutaoDu(uids: Array, mode: UgoHutaoMode): Promise { + switch (mode) { + case "download": + await handleHutaoDownload(uids); + break; + case "upload": + await handleHutaoUpload(uids); + break; + case "delete": + await handleHutaoDelete(uids); + break; + } } async function handleHutaoUpload(uids: Array): Promise { @@ -284,7 +308,7 @@ async function handleHutaoUpload(uids: Array): Promise { QueryType: Number(i.uigfType), ItemId: Number(i.itemId), Time: str2timeStr(i.time), - Id: BigInt(i.id), + Id: i.id.toString(), })), }; const resp = await hutao.Gacha.upload(accessToken.value!, data); @@ -298,8 +322,124 @@ async function handleHutaoUpload(uids: Array): Promise { } async function handleHutaoDownload(uids: Array): Promise { - console.log(uids); - // TODO:implement download gacha logs + if (uids.length === 0) { + showSnackbar.warn("没有选中的UID"); + return; + } + if (!isLoginHutao.value) { + showSnackbar.warn("未登录胡桃云账号"); + return; + } + if (!userInfo.value) { + await hutaoStore.tryRefreshInfo(); + if (!userInfo.value) { + showSnackbar.warn("未检测到胡桃云用户信息"); + return; + } + } + await showLoading.start("正在下载胡桃云祈愿记录...", "正在刷新Token"); + for (const u of uids) { + await showLoading.start(`正在下载UID:${u}的祈愿记录`, "正在获取EndIds"); + const endIdResp = await hutao.Gacha.endIds(accessToken.value!, u); + if ("retcode" in endIdResp) { + showSnackbar.warn(`[${endIdResp.retcode}] ${endIdResp.message}`); + continue; + } + for (const [p, i] of Object.entries(endIdResp)) { + if (i === 0) continue; + let endId: string | undefined = undefined; + let flag = true; + const pageSize = 200; + await showLoading.start(`正在下载卡池 ${p}`); + const uigfList: Array = []; + while (flag) { + await showLoading.update(`EndId:${endId ?? "无"}`); + await hutaoStore.tryRefreshToken(); + const gachaResp = await hutao.Gacha.logs(accessToken.value!, u, Number(p), pageSize, endId); + if (gachaResp.retcode !== 0) { + showSnackbar.warn(`[${gachaResp.retcode}] ${gachaResp.message}`); + break; + } + const data: TGApp.Plugins.Hutao.Gacha.GachaLogRes = gachaResp.data ?? []; + if (data.length === pageSize) { + endId = data[data.length - 1].Id.toString(); + } else flag = false; + for (const item of data) { + const tempItem: TGApp.Plugins.UIGF.GachaItem = { + gacha_type: item.GachaType.toString(), + item_id: item.ItemId.toString(), + count: "1", + time: item.Time, + name: "", + item_type: "", + rank_type: "", + id: BigInt(item.Id).toString(), + uigf_gacha_type: item.QueryType.toString(), + }; + const find = AppCalendarData.find((i) => i.id.toString() === item.ItemId.toString()); + if (find) { + tempItem.name = find.name; + tempItem.item_type = find.itemType; + tempItem.rank_type = find.star.toString(); + } else { + if (hakushiData.value.length === 0) { + await showLoading.update( + `未查找到 ${tempItem.item_id} 的 信息,正在获取 Hakushi 数据`, + ); + await loadHakushi(); + } + const findH = hakushiData.value.find((i) => i.id.toString() === item.ItemId.toString()); + if (findH) { + tempItem.name = findH.name; + tempItem.item_type = findH.type; + tempItem.rank_type = findH.star.toString(); + } else { + showSnackbar.warn(`无法搜索到 ${item.ItemId} 的信息,请等待元数据更新`); + continue; + } + } + uigfList.push(tempItem); + } + } + await showLoading.start(`正在写入卡池 ${p}-${uigfList.length}`); + await TSUserGacha.mergeUIGF(u, uigfList, true); + } + } + await showLoading.end(); + showSnackbar.success("成功下载,即将刷新页面"); + await new Promise((resolve) => setTimeout(resolve, 1000)); + window.location.reload(); +} + +async function handleHutaoDelete(uids: Array): Promise { + if (uids.length === 0) { + showSnackbar.warn("没有选中的UID"); + return; + } + if (!isLoginHutao.value) { + showSnackbar.warn("未登录胡桃云账号"); + return; + } + if (!userInfo.value) { + await hutaoStore.tryRefreshInfo(); + if (!userInfo.value) { + showSnackbar.warn("未检测到胡桃云用户信息"); + return; + } + } + const check = await showDialog.check("确定删除?", uids.join("、")); + if (!check) return; + await showLoading.start("正在删除胡桃云祈愿记录"); + for (const u of uids) { + await showLoading.update(`UID:${u}`); + const deleteResp = await hutao.Gacha.delete(accessToken.value!, u); + if (deleteResp.retcode === 0) { + showSnackbar.success(`删除记录成功:${deleteResp.message}`); + } else { + showSnackbar.warn(`[${deleteResp.retcode}] ${deleteResp.message}`); + } + } + await showLoading.end(); } async function reloadUid(): Promise { diff --git a/src/plugins/Hutao/index.ts b/src/plugins/Hutao/index.ts index 58c3c50c..a5f5468b 100644 --- a/src/plugins/Hutao/index.ts +++ b/src/plugins/Hutao/index.ts @@ -14,7 +14,13 @@ import { } from "./request/abyssReq.js"; import { getUserInfo, loginPassport, refreshToken } from "./request/accountReq.js"; import { getCombatStatistic, uploadCombatData } from "./request/combatReq.js"; -import { getEndIds, getEntries, getGachaLogs, uploadGachaLogs } from "./request/gachaReq.js"; +import { + deleteGachaLogs, + getEndIds, + getEntries, + getGachaLogs, + uploadGachaLogs, +} from "./request/gachaReq.js"; import { transAbyssAvatars, transAbyssLocal } from "./utils/abyssUtil.js"; import { transCombatLocal } from "./utils/combatUtil.js"; @@ -62,7 +68,7 @@ const Hutao = { endIds: getEndIds, logs: getGachaLogs, upload: uploadGachaLogs, - delete: _, + delete: deleteGachaLogs, }, }; diff --git a/src/plugins/Hutao/request/gachaReq.ts b/src/plugins/Hutao/request/gachaReq.ts index 8baa4ee5..ce3604f4 100644 --- a/src/plugins/Hutao/request/gachaReq.ts +++ b/src/plugins/Hutao/request/gachaReq.ts @@ -41,6 +41,7 @@ export async function getEndIds( method: "GET", headers: header, query: { Uid: uid }, + hasBigInt: true, }); if (resp.retcode !== 0) return resp; return resp.data; @@ -59,24 +60,23 @@ export async function getGachaLogs( tk: string, uid: string, gType: number, - endId: number, count: number, -): Promise { + endId: string | undefined = undefined, +): Promise { const url = `${GachaUrl}LimitedRetrieve`; const header = await getReqHeader(tk); - const params = { + const params: Record = { uid: uid, configType: gType, - endId: endId, count: count, }; - const resp = await TGHttp(url, { + if (endId) params.endId = endId; + return await TGHttp(url, { method: "GET", headers: header, query: params, + hasBigInt: true, }); - if (resp.retcode !== 0) return resp; - return resp.data; } /** @@ -89,12 +89,32 @@ export async function getGachaLogs( export async function uploadGachaLogs( tk: string, data: TGApp.Plugins.Hutao.Gacha.UploadData, -): Promise { +): Promise { const url = `${GachaUrl}Upload`; const header = await getReqHeader(tk); return await TGHttp(url, { method: "POST", headers: header, - body: JSON.stringify(data, (_, v) => (typeof v === "bigint" ? v.toString() : v)), + body: JSON.stringify(data), + }); +} + +/** + * 删除祈愿记录 + * @since Beta v0.9.1 + * @param tk - token + * @param uid - uid + * @returns 删除结果 + */ +export async function deleteGachaLogs( + tk: string, + uid: string, +): Promise { + const url = `${GachaUrl}Delete`; + const header = await getReqHeader(tk); + return await TGHttp(url, { + method: "GET", + headers: header, + query: { Uid: uid }, }); } diff --git a/src/plugins/Hutao/types/Gacha.d.ts b/src/plugins/Hutao/types/Gacha.d.ts index 968ec5da..8758a125 100644 --- a/src/plugins/Hutao/types/Gacha.d.ts +++ b/src/plugins/Hutao/types/Gacha.d.ts @@ -83,7 +83,7 @@ declare namespace TGApp.Plugins.Hutao.Gacha { */ Time: string; /** Id */ - Id: number | bigint; + Id: string | bigint; }; /** @@ -102,4 +102,10 @@ declare namespace TGApp.Plugins.Hutao.Gacha { /** 数据 */ Items: Array; }; + + /** + * 删除响应 + * @since Beta v0.9.1 + */ + type DeleteResp = TGApp.Plugins.Hutao.Base.Resp; } diff --git a/src/store/modules/hutao.ts b/src/store/modules/hutao.ts index b31c2260..dfafe474 100644 --- a/src/store/modules/hutao.ts +++ b/src/store/modules/hutao.ts @@ -79,9 +79,7 @@ const useHutaoStore = defineStore( } async function tryRefreshInfo(): Promise { - if (!checkIsValid()) { - await tryRefreshToken(); - } + await tryRefreshToken(); const resp = await hutao.Account.info(accessToken.value!); if ("retcode" in resp) { showSnackbar.warn(`刷新用户信息失败:${resp.retcode}-${resp.message}`); @@ -96,6 +94,7 @@ const useHutaoStore = defineStore( showSnackbar.warn("未找到胡桃云RefreshToken"); return; } + if (checkIsValid()) return; try { const resp = await hutao.Token.refresh(refreshToken.value); if ("retcode" in resp) { diff --git a/src/types/Plugins/Hakushi.d.ts b/src/types/Plugins/Hakushi.d.ts index a3f0941b..ee6ee87a 100644 --- a/src/types/Plugins/Hakushi.d.ts +++ b/src/types/Plugins/Hakushi.d.ts @@ -82,11 +82,16 @@ declare namespace TGApp.Plugins.Hakushi { /** * 转换后的数据 - * @since Beta v0.9.0 + * @since Beta v0.9.1 */ type ConvertData = { + /** ID */ id: string; + /** 名称 */ name: string; + /** 类型 */ type: "武器" | "角色"; + /** 星级 */ + star: number; }; } diff --git a/src/utils/Hakushi.ts b/src/utils/Hakushi.ts index 774d4a61..1b444636 100644 --- a/src/utils/Hakushi.ts +++ b/src/utils/Hakushi.ts @@ -30,17 +30,17 @@ async function fetchWeapon(): Promise { /** * 转换数据 - * @since Beta v0.9.0 + * @since Beta v0.9.1 */ async function fetchJson(): Promise> { const jsonW = await fetchWeapon(); const jsonA = await fetchAvatar(); const res: Array = []; for (const [id, data] of Object.entries(jsonW)) { - res.push({ id: id.toString(), name: data.CHS, type: "武器" }); + res.push({ id: id.toString(), name: data.CHS, type: "武器", star: data.rank }); } for (const [id, data] of Object.entries(jsonA)) { - res.push({ id: id.toString(), name: data.CHS, type: "角色" }); + res.push({ id: id.toString(), name: data.CHS, type: "角色", star: data.rank }); } return res; } diff --git a/src/utils/TGHttp.ts b/src/utils/TGHttp.ts index df492773..be517cf6 100644 --- a/src/utils/TGHttp.ts +++ b/src/utils/TGHttp.ts @@ -4,6 +4,7 @@ */ import { type ClientOptions, fetch } from "@tauri-apps/plugin-http"; +import JSONBig from "json-bigint"; import TGLogger from "./TGLogger.js"; @@ -22,6 +23,8 @@ type TGHttpParams = { body?: string; /** 是否是Blob */ isBlob?: boolean; + /** 是否有BigInt */ + hasBigInt?: boolean; }; /** @@ -65,7 +68,11 @@ async function TGHttp( return await fetch(url, fetchOptions) .then(async (res) => { if (res.ok) { - const data = options.isBlob ? await res.arrayBuffer() : await res.json(); + const data = options.isBlob + ? await res.arrayBuffer() + : options.hasBigInt + ? JSONBig.parse(await res.text()) + : await res.json(); if (fullResponse) return { data, resp: res }; return data; }