diff --git a/src/types/Plugins/JSBridge.d.ts b/src/types/Plugins/JSBridge.d.ts new file mode 100644 index 00000000..d73d04c8 --- /dev/null +++ b/src/types/Plugins/JSBridge.d.ts @@ -0,0 +1,233 @@ +/** + * @file types/Plugins/JSBridge.d.ts + * @description JSBridge 插件相关类型定义文件 + * @since Beta v0.3.9 + */ + +/** + * @description JSBridge 插件相关类型命名 + * @since Beta v0.3.9 + * @namespace TGApp.Plugins.JSBridge + * @memberof TGApp.Plugins + */ +declare namespace TGApp.Plugins.JSBridge { + /** + * @description JSBridge 通用 arg 参数 + * @since Beta v0.3.9 + * @interface Arg + * @template T + * @property {string} method - 方法名 + * @property {T} payload - 参数 + * @property {string} callback - 回调函数名 + * @return Arg + */ + interface Arg { + method: string; + payload: T; + callback: string; + } + + /** + * @description 通用 arg 参数-无参数 + * @since Beta v0.3.9 + * @interface NullArg + * @return NullArg + */ + type NullArg = Arg; + + /** + * @description 通用 arg 参数-未知参数 + * @since Beta v0.3.9 + * @interface UnknownArg + * @return UnknownArg + */ + type UnknownArg = Arg; + + /** + * @description eventTrack 方法参数 + * @since Beta v0.3.9 + * @interface EventTrackPayload + * @property {object} pageInfo - 页面信息 + * @property {string} pageInfo.page_path - 页面路径 + * @property {string} pageInfo.page_name - 页面名称 + * @property {string} pageInfo.sub_page_path - 子页面路径 + * @property {string} pageInfo.sub_page_name - 子页面名称 + * @property {string} pageInfo.source_path - 来源页面路径 + * @property {string} pageInfo.source_name - 来源页面名称 + * @property {string} pageInfo.page_id - 页面 ID + * @property {string} pageInfo.page_type - 页面类型 + * @property {string} pageInfo.source_id - 来源 ID + * @property {unknown} pageInfo.extra_info - 额外信息 + * @property {object} eventInfo - 事件信息 + * @property {string} eventInfo.time - 事件时间 + * @property {number} eventInfo.action_id - 事件 ID + * @property {string} eventInfo.btn_name - 按钮名称 + * @property {string} eventInfo.module_id - 模块 ID + * @property {string} eventInfo.module_name - 模块名称 + * @property {unknown} eventInfo.extra_info - 额外信息 + * @property {object} commonInfo - 公共信息 + * @property {object} commonInfo.extra_info - 额外信息 + * @property {string} commonInfo.extra_info.game_id - 游戏 ID + * @property {string} commonInfo.extra_info.view_type - 视图类型 + * @return EventTrackPayload + */ + interface EventTrackPayload { + pageInfo: { + page_path: string; + page_name: string; + sub_page_path: string; + sub_page_name: string; + source_path: string; + source_name: string; + page_id: string; + page_type: string; + source_id: string; + extra_info: unknown; + }; + eventInfo: { + time: string; + action_id: number; + btn_name: string; + module_id: string; + module_name: string; + extra_info: unknown; + }; + commonInfo: { + extra_info: { + game_id: string; + view_type: string; + }; + }; + } + + /** + * @description getActionTicket 方法参数 + * @since Beta v0.3.9 + * @interface GetActionTicketPayload + * @property {string} action_type + * @return GetActionTicketPayload + */ + interface GetActionTicketPayload { + action_type: string; + } + + /** + * @description genAuthkey 方法参数 + * @since Beta v0.3.9 + * @interface GenAuthkeyPayload + * @return GenAuthkeyPayload + */ + type GenAuthkeyPayload = Record; + + /** + * @description getCookieToken 方法参数 + * @since Beta v0.3.9 + * @interface GetCookieTokenPayload + * @property {boolean} forceRefresh - 是否强制刷新 + * @return GetCookieTokenPayload + */ + interface GetCookieTokenPayload { + forceRefresh: boolean; + } + + /** + * @description getDS2 方法参数 + * @since Beta v0.3.9 + * @interface GetDS2Payload + * @property {Record|string} query - 查询参数 + * @property {Record|string} body - 请求体 + * @return GetDS2Payload + */ + interface GetDS2Payload { + query: Record | string; + body: Record | string; + } + + /** + * @description onClickImg 方法参数 + * @since Beta v0.3.9 + * @interface OnClickImgPayload + * @property {Array} image_list - 图片列表 + * @property {string} image_list[].url - 图片链接 + * @property {string} image_list[].format - 图片格式 + * @return OnClickImgPayload + */ + interface OnClickImgPayload { + image_list: Array<{ + url: string; + format: string; + }>; + } + + /** + * @description openApplication 方法参数 + * @since Beta v0.3.9 + * @interface OpenApplicationPayload + * @property {number} gameCenterId - 游戏中心对应 id + * @return OpenApplicationPayload + */ + interface OpenApplicationPayload { + gameCenterId: number; + } + + /** + * @description pushPage 方法参数 + * @since Beta v0.3.9 + * @interface PushPagePayload + * @property {string} page - 页面地址 + * @return PushPagePayload + */ + interface PushPagePayload { + page: string; + } + + /** + * @description setPresentationStyle 方法参数 + * @since Beta v0.3.9 + * @interface SetPresentationStylePayload + * @property {string} style - 窗口样式 + * @property {unknown} navigationBar - 导航栏 + * @property {string} statusBar.style - 状态栏模式 + * @return SetPresentationStylePayload + */ + interface SetPresentationStylePayload { + style: string; + navigationBar: unknown; + statusBar: { + style: string; + }; + } + + /** + * @description share 方法参数 + * @since Beta v0.3.9 + * @interface SharePayload + * @property {string} type - 分享类型 // screenshot + * @property {object} content - 分享内容 + * @property {boolean} content?.preview - 是否预览 + * @return SharePayload + */ + type SharePayload = + | { + type: "default"; + content: { + title: string; + description: string; + link: string; + image_url: string; + }; + } + | { + type: "screenshot"; + content: { + preview: boolean; + }; + } + | { + type: "image"; + content: { + image_url?: string; + image_base64?: string; + }; + }; +} diff --git a/src/utils/TGClient.ts b/src/utils/TGClient.ts index 4819360a..fac8c291 100644 --- a/src/utils/TGClient.ts +++ b/src/utils/TGClient.ts @@ -1,7 +1,7 @@ /** * @file utils/TGClient.ts * @desc 负责米游社客户端的 callback 处理 - * @since Beta v0.3.8 + * @since Beta v0.3.9 */ import { event, invoke } from "@tauri-apps/api"; @@ -20,13 +20,6 @@ import { getCookieTokenBySToken } from "../web/request/getCookieToken"; import TGRequest from "../web/request/TGRequest"; import { getDS4JS } from "../web/utils/getRequestHeader"; -// 正常 arg 参数 -interface NormalArg { - method: string; - payload: any; - callback: string; -} - // invoke 参数 interface InvokeArg { func: string; @@ -34,7 +27,7 @@ interface InvokeArg { /** * @class TGClient - * @since Beta v0.3.8 + * @since Beta v0.3.9 * @description 米游社客户端 */ class TGClient { @@ -92,211 +85,7 @@ class TGClient { } } - /** - * @func loadJSBridge - * @since Beta v0.3.8 - * @desc 加载 JSBridge - * @returns {void} - 无返回值 - */ - async loadJSBridge(): Promise { - const executeJS = `javascript:(function() { - if(window.MiHoYoJSInterface) return; - window.MiHoYoJSInterface = { - postMessage: function(arg) { window.__TAURI__.event.emit('post_mhy_client', arg) }, - closePage: function() { this.postMessage('{"method":"closePage"}') }, - }; - })();`; - await invoke("execute_js", { label: "mhy_client", js: executeJS }); - } - - /** - * @func hideSideBar - * @since Beta v0.3.5 - * @desc 隐藏侧边栏 - * @returns {void} - 无返回值 - */ - async hideSideBar(): Promise { - const executeJS = `javascript:(function(){ - if (document.getElementById('teyvat_style')) { - return; - } else { - let style = document.createElement('style'); - style.innerHTML = '::-webkit-scrollbar{display:none}'; - style.id = 'teyvat_style'; - document.querySelector('body').appendChild(style); - } - })();`; - await invoke("execute_js", { label: "mhy_client", js: executeJS }); - } - - /** - * @func hideOverlay - * @since Beta v0.3.7 - * @desc 隐藏遮罩 - * @returns {void} - */ - async hideOverlay(): Promise { - const executeJS = `javascript:(function(){ - if (document.getElementById('mihoyo_landscape') !== null) { - let box = document.getElementById('mihoyo_landscape'); - box.remove(); - } - })();`; - await invoke("execute_js", { label: "mhy_client", js: executeJS }); - } - - /** - * @func getUrl - * @since Beta v0.3.8 - * @desc 获取 url - * @param {string} func - 方法名 - * @returns {string} - url - */ - getUrl(func: string): string { - switch (func) { - case "sign_in": - return "https://act.mihoyo.com/bbs/event/signin/hk4e/index.html?act_id=e202311201442471&bbs_auth_required=true&bbs_presentation_style=fullscreen&mhy_presentation_style=fullscreen&utm_source=bbs&utm_medium=ys&utm_campaign=icon"; - case "game_record": - return "https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen"; - case "daily_note": - return "https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/"; - case "tavern": - return "https://m.miyoushe.com/ys/#/home/26"; - case "birthday": - return "https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html?activity_id=20220301153521"; - case "toolbox": - return "https://webstatic.mihoyo.com/bbs/event/e20200511toolbox/index.html?game_biz=ys_cn"; - default: - return "https://api-static.mihoyo.com/"; - } - } - - /** - * @func open - * @since Beta v0.3.7 - * @desc 打开米游社客户端 - * @param {string} func - 方法名 - * @param {string} url - url - * @returns {void} - 无返回值 - */ - async open(func: string, url?: string): Promise { - if (this.window !== null) { - try { - await this.window.close(); - } catch (e) { - console.error(e); - await invoke("create_mhy_client", { - func: "default", - url: "https://api-static.mihoyo.com/", - }); - await this.open(func, url); - } - } - if (url === undefined) url = this.getUrl(func); - this.route = [url]; - console.log(`[open] ${url}`); - await invoke("create_mhy_client", { func, url }); - this.window = WebviewWindow.getByLabel("mhy_client"); - await this.window?.show(); - await this.window?.setFocus(); - await this.loadJSBridge(); - } - - /** - * @func handleCustomCallback - * @since Beta v0.3.8 - * @desc 处理自定义的 callback - * @param {Event} arg - 事件参数 - * @returns {any} - 返回值 - */ - async handleCustomCallback(arg: Event): Promise { - const { method, payload } = JSON.parse(arg.payload); - switch (method) { - case "teyvat_open": - createPost(payload); - break; - default: - console.warn(`[${arg.windowLabel}] ${arg.payload}`); - } - } - - /** - * @func handleCallback - * @since Beta v0.3.8 - * @desc 处理米游社客户端的 callback - * @param {Event} arg - 事件参数 - * @returns {any} - 返回值 - */ - async handleCallback(arg: Event): Promise { - const { method, payload, callback } = JSON.parse(arg.payload); - if (method.startsWith("teyvat")) { - await this.handleCustomCallback(arg); - return; - } - console.log(`[${arg.windowLabel}] ${arg.payload}`); - await this.hideSideBar(); - await this.hideOverlay(); - switch (method) { - case "closePage": - await this.closePage(); - break; - case "configure_share": - break; - case "eventTrack": - await this.eventTrack(payload); - break; - case "getStatusBarHeight": - await this.getStatusBarHeight(callback); - break; - case "genAuthKey": - await this.genAuthKey(payload, callback); - break; - case "getCookieInfo": - await this.getCookieInfo(payload, callback); - break; - case "getCookieToken": - await this.getCookieToken(payload, callback); - break; - case "getActionTicket": - await this.getActionTicket(payload, callback); - break; - case "getHTTPRequestHeaders": - await this.getHTTPRequestHeaders(callback); - break; - case "getDS": - await this.getDS(1, callback, payload); - break; - case "getDS2": - await this.getDS(2, callback, payload); - break; - case "getUserInfo": - await this.getUserInfo(callback); - break; - case "login": - await this.nullCallback(arg); - break; - case "onBeginDragging": - await this.nullCallback(arg); - break; - case "onClickImg": - await this.onClickImg(payload); - break; - case "openApplication": - await this.openApplication(payload); - break; - case "pushPage": - await this.pushPage(payload); - break; - case "share": - await this.share(payload, callback); - break; - case "share2": - await this.nullCallback(arg); - break; - default: - console.warn(`[${arg.windowLabel}] ${arg.payload}`); - } - } + /* 内置函数 */ /** * @func callback @@ -318,270 +107,6 @@ class TGClient { await invoke("execute_js", { label: "mhy_client", js }); } - /** - * @func getStatusBarHeight - * @since Beta v0.3.4 - * @desc 获取状态栏高度 - * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 - */ - async getStatusBarHeight(callback: string): Promise { - const data = { - statusBarHeight: 0, - }; - await this.callback(callback, data); - } - - /** - * @func genAuthKey - * @since Beta v0.3.8 - * @desc 获取米游社客户端的 authkey - * @param {Record} payload - 请求参数 - * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 - */ - async genAuthKey(payload: Record, callback: string): Promise { - const userStore = useUserStore(); - if (!userStore.cookie) return; - const cookie = { - mid: userStore.cookie.mid, - stoken: userStore.cookie.stoken, - }; - const res = await TGRequest.User.getAuthkey2(cookie, payload); - await this.callback(callback, res.data); - } - - /** - * @func getCookieInfo - * @since Beta v0.3.8 - * @desc 获取米游社客户端的 cookie - * @param {unknown} payload - 请求参数 - * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 - */ - async getCookieInfo(payload: unknown, callback: string): Promise { - const user = useUserStore(); - if (!user.cookie) return; - const data = { - ltoken: user.cookie.ltoken, - ltuid: user.cookie.ltuid, - login_ticket: "", - }; - await this.callback(callback, data); - } - - /** - * @func getCookieToken - * @since Beta v0.3.8 - * @desc 获取米游社客户端的 cookie_token - * @param {getCookieTokenPayload} payload - 请求参数 - * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 - */ - async getCookieToken(payload: unknown, callback: string): Promise { - interface getCookieTokenPayload { - forceRefresh: boolean; - } - const ckPayload = payload; - const user = useUserStore(); - if (!user.cookie) return; - if (ckPayload.forceRefresh) { - const res = await getCookieTokenBySToken(user.cookie.mid, user.cookie.stoken); - if (typeof res !== "string") { - return; - } - user.cookie.cookie_token = res; - await TGSqlite.saveAppData("cookie", JSON.stringify(user.cookie)); - } - const executeJS = `javascript:(function(){ - var domain = window.location.host; - document.cookie = "cookie_token=${user.cookie.cookie_token};domain=." + domain + ";path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT; - document.cookie = "ltoken=${user.cookie.ltoken};domain=." + domain + ";path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT; - })();`; - console.info(`[getCookieToken] ${executeJS}`); - await invoke("execute_js", { label: "mhy_client", js: executeJS }); - const data = { - cookie_token: user.cookie.cookie_token, - }; - await this.callback(callback, data); - } - - /** - * @func getActionTicket - * @since Beta v0.3.8 - * @desc 获取米游社客户端的 action_ticket\ - * @param {unknown} payload - 请求参数 - * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 - */ - async getActionTicket(payload: any, callback: string): Promise { - const actionType = payload.action_type; - const user = useUserStore(); - if (!user.cookie) return; - const uid = user.getCurAccount().gameUid; - const mid = user.cookie.mid; - const stoken = user.cookie.stoken; - const ActionTicket = await TGRequest.User.bySToken.getActionTicket( - actionType, - stoken, - mid, - uid, - ); - await this.callback(callback, ActionTicket.data); - } - - /** - * @func getHTTPRequestHeaders - * @since Beta v0.3.6 - * @desc 获取米游社客户端的 HTTP 请求头 - * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 - */ - async getHTTPRequestHeaders(callback: string): Promise { - const localFp = getDeviceInfo("device_fp"); - let deviceInfo = useAppStore().deviceInfo; - if (localFp === "0000000000000") { - deviceInfo = await TGRequest.Device.getFp(deviceInfo); - } - const data = { - "user-agent": TGConstant.BBS.UA_MOBILE, - "x-rpc-client_type": "5", - "x-rpc-device_id": deviceInfo.device_id, - "x-rpc-app_version": TGConstant.BBS.VERSION, - "x-rpc-device_fp": deviceInfo.device_fp, - }; - await this.callback(callback, data); - } - - /** - * @func getDS - * @since Beta v0.3.4 - * @desc 获取米游社客户端的 DS 参数 - * @param {number} dsType - DS 类型 - * @param {string} callback - 回调函数名 - * @param {unknown} payload - 请求参数 - * @returns {void} - 无返回值 - */ - async getDS(dsType: 1, callback: string, payload: undefined): Promise; - async getDS(dsType: 2, callback: string, payload: any): Promise; - async getDS(dsType: 1 | 2, callback: string, payload?: any): Promise { - const saltType = dsType === 1 ? "lk2" : "common"; - let ds: string; - if (dsType === 2) { - const { body, query } = payload; - ds = getDS4JS(saltType, dsType, body, query); - } else { - ds = getDS4JS(saltType, dsType, undefined, undefined); - } - const data = { - DS: ds, - }; - await this.callback(callback, data); - } - - /** - * @func getUserInfo - * @since Beta v0.3.8 - * @desc 获取米游社客户端的用户信息 - * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 - */ - async getUserInfo(callback: string): Promise { - const user = useUserStore(); - if (!user.cookie) return; - const cookieToken = user.cookie.cookie_token; - const accountId = user.cookie.account_id; - const userInfo = await TGRequest.User.byCookie.getUserInfo(cookieToken, accountId); - if ("retcode" in userInfo) { - console.error(`[${callback}] ${userInfo.message}`); - return; - } - await this.callback(callback, userInfo); - } - - /** - * @func pushPage - * @since Beta v0.3.8 - * @desc 打开米游社客户端的页面 - * @param {unknown} payload - 请求参数 - * @returns {void} - 无返回值 - */ - async pushPage(payload: any): Promise { - const url = payload.page; - const res = await parseLink(url, true); - if (!res) { - await appWindow.setFocus(); - showSnackbar({ - text: `未知链接:${url}`, - color: "error", - timeout: 3000, - }); - await new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 3000); - }); - await this.window?.setFocus(); - return; - } - if (typeof res !== "string") return; - this.route.push(res); - console.log(`[pushPage] ${res}`); - const executeJS = `javascript:(function(){ - window.location.href = '${res}'; - })();`; - await invoke("execute_js", { label: "mhy_client", js: executeJS }); - await this.loadJSBridge(); - await this.hideSideBar(); - await this.hideOverlay(); - await this.window?.setFocus(); - } - - /** - * @func closePage - * @since Beta v0.3.7 - * @desc 关闭米游社客户端的页面 - * @returns {void} - 无返回值 - */ - async closePage(): Promise { - this.route.pop(); - if (this.route.length === 0) { - await this.window?.close(); - return; - } - const url = this.route[this.route.length - 1]; - const executeJS = `javascript:(function(){ - window.location.href = '${url}'; - })();`; - await invoke("execute_js", { label: "mhy_client", js: executeJS }); - await this.loadJSBridge(); - } - - /** - * @func nullCallback - * @since Beta v0.3.4 - * @desc 空回调函数 - * @param {Event} arg - 回调参数 - * @returns {void} - 无返回值 - */ - async nullCallback(arg: Event): Promise { - const { callback } = JSON.parse(arg.payload); - await this.callback(callback, {}); - } - - /** - * @func onClickImg - * @since Beta v0.3.7 - * @desc 点击图片,下载到本地 - * @param {unknown} payload - 请求参数 - * @returns {void} - 无返回值 - */ - async onClickImg(payload: any): Promise { - const image = payload.image_list[0]; - const executeJS = this.getSaveImgJS(image.url, image.format); - await invoke("execute_js", { label: "mhy_client", js: executeJS }); - } - /** * @func getSaveImgJS * @since Beta v0.3.7 @@ -615,24 +140,553 @@ class TGClient { } /** - * @func share + * @func getUrl * @since Beta v0.3.8 - * @desc 分享 - * @param {unknown} payload - 请求参数 - * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 + * @desc 获取 url + * @param {string} func - 方法名 + * @returns {string} - url */ - async share(payload: any, callback: string): Promise { - // 如果有数据 - if (payload?.content.image_url !== undefined) { - const image = payload.content.image_url; - const format = image.split(".").pop(); - const executeJS = this.getSaveImgJS(image, format); - await invoke("execute_js", { label: "mhy_client", js: executeJS }); - await this.callback(callback, {}); + getUrl(func: string): string { + switch (func) { + case "sign_in": + return "https://act.mihoyo.com/bbs/event/signin/hk4e/index.html?act_id=e202311201442471&bbs_auth_required=true&bbs_presentation_style=fullscreen&mhy_presentation_style=fullscreen&utm_source=bbs&utm_medium=ys&utm_campaign=icon"; + case "game_record": + return "https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen"; + case "daily_note": + return "https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/"; + case "tavern": + return "https://m.miyoushe.com/ys/#/home/26"; + case "birthday": + return "https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html?activity_id=20220301153521"; + case "toolbox": + return "https://webstatic.mihoyo.com/bbs/event/e20200511toolbox/index.html?game_biz=ys_cn"; + default: + return "https://api-static.mihoyo.com/"; + } + } + + /** + * @func handleCallback + * @since Beta v0.3.9 + * @desc 处理米游社客户端的 callback + * @param {Event} arg - 事件参数 + * @returns {Promise} - 返回值 + */ + async handleCallback(arg: Event): Promise { + const argParse: TGApp.Plugins.JSBridge.UnknownArg = JSON.parse(arg.payload); + if (argParse.method.startsWith("teyvat")) { + await this.handleCustomCallback(argParse); return; } - if (payload?.type === "screenshot") { + console.warn(`[${argParse.method}] ${JSON.stringify(argParse.payload)}`); + await this.hideSideBar(); + await this.hideOverlay(); + switch (argParse.method) { + case "closePage": + await this.closePage(argParse); + break; + case "configure_share": + break; + case "eventTrack": + await this.eventTrack( + >argParse, + ); + break; + case "getStatusBarHeight": + await this.getStatusBarHeight(argParse); + break; + case "genAuthKey": + await this.genAuthKey( + >argParse, + ); + break; + case "getCookieInfo": + await this.getCookieInfo(argParse); + break; + case "getCookieToken": + await this.getCookieToken( + >argParse, + ); + break; + case "getActionTicket": + await this.getActionTicket( + >argParse, + ); + break; + case "getHTTPRequestHeaders": + await this.getHTTPRequestHeaders(argParse); + break; + case "getDS": + await this.getDS(argParse); + break; + case "getDS2": + await this.getDS2( + >argParse, + ); + break; + case "getUserInfo": + await this.getUserInfo(argParse); + break; + case "login": + await this.nullCallback(argParse); + break; + case "onBeginDragging": + await this.nullCallback(argParse); + break; + case "onClickImg": + await this.onClickImg( + >argParse, + ); + break; + case "openApplication": + await this.openApplication( + >argParse, + ); + break; + case "pushPage": + await this.pushPage( + >argParse, + ); + break; + case "share": + await this.share(>argParse); + break; + case "share2": + await this.nullCallback(argParse); + break; + default: + console.warn(`[${arg.windowLabel}] ${JSON.stringify(argParse)}`); + } + } + + /** + * @func handleCustomCallback + * @since Beta v0.3.9 + * @desc 处理自定义的 callback + * @param {TGApp.Plugins.JSBridge.UnknownArg} arg - 事件参数 + * @returns {Promise} - 返回值 + */ + async handleCustomCallback(arg: TGApp.Plugins.JSBridge.UnknownArg): Promise { + switch (arg.method) { + case "teyvat_open": + createPost(arg.payload); + break; + default: + console.warn(`[customCallback] ${arg.payload}`); + } + } + + /** + * @func hideOverlay + * @since Beta v0.3.7 + * @desc 隐藏遮罩 + * @returns {Promise} + */ + async hideOverlay(): Promise { + const executeJS = `javascript:(function(){ + if (document.getElementById('mihoyo_landscape') !== null) { + let box = document.getElementById('mihoyo_landscape'); + box.remove(); + } + })();`; + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + } + + /** + * @func hideSideBar + * @since Beta v0.3.5 + * @desc 隐藏侧边栏 + * @returns {void} - 无返回值 + */ + async hideSideBar(): Promise { + const executeJS = `javascript:(function(){ + if (document.getElementById('teyvat_style')) { + return; + } else { + let style = document.createElement('style'); + style.innerHTML = '::-webkit-scrollbar{display:none}'; + style.id = 'teyvat_style'; + document.querySelector('body').appendChild(style); + } + })();`; + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + } + + /** + * @func loadJSBridge + * @since Beta v0.3.8 + * @desc 加载 JSBridge + * @returns {void} - 无返回值 + */ + async loadJSBridge(): Promise { + const executeJS = `javascript:(function() { + if(window.MiHoYoJSInterface) return; + window.MiHoYoJSInterface = { + postMessage: function(arg) { window.__TAURI__.event.emit('post_mhy_client', arg) }, + closePage: function() { this.postMessage('{"method":"closePage"}') }, + }; + })();`; + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + } + + /** + * @func nullCallback + * @since Beta v0.3.9 + * @desc 空回调函数 + * @param {TGApp.Plugins.JSBridge.NullArg} arg - 回调参数 + * @returns {void} - 无返回值 + */ + async nullCallback(arg: TGApp.Plugins.JSBridge.NullArg): Promise { + await this.callback(arg.callback, {}); + } + + /** + * @func open + * @since Beta v0.3.7 + * @desc 打开米游社客户端 + * @param {string} func - 方法名 + * @param {string} url - url + * @returns {void} - 无返回值 + */ + async open(func: string, url?: string): Promise { + if (this.window !== null) { + try { + await this.window.close(); + } catch (e) { + console.error(e); + await invoke("create_mhy_client", { + func: "default", + url: "https://api-static.mihoyo.com/", + }); + await this.open(func, url); + } + } + if (url === undefined) url = this.getUrl(func); + this.route = [url]; + console.log(`[open] ${url}`); + await invoke("create_mhy_client", { func, url }); + this.window = WebviewWindow.getByLabel("mhy_client"); + await this.window?.show(); + await this.window?.setFocus(); + await this.loadJSBridge(); + } + + /* JSBridge 回调处理 */ + /** + * @func closePage + * @since Beta v0.3.9 + * @desc 关闭米游社客户端的页面 + * @param {TGApp.Plugins.JSBridge.NullArg} arg - 请求参数 + * @returns {void} - 无返回值 + */ + async closePage(arg: TGApp.Plugins.JSBridge.NullArg): Promise { + this.route.pop(); + if (this.route.length === 0) { + await this.window?.close(); + return; + } + const url = this.route[this.route.length - 1]; + console.log(`[closePage] ${JSON.stringify(arg)}`); + const executeJS = `javascript:(function(){ + window.location.href = '${url}'; + })();`; + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + await this.loadJSBridge(); + } + + /** + * @func eventTrack + * @since Beta v0.3.9 + * @desc 事件跟踪 + * @param {TGApp.Plugins.Arg} arg - 请求参数 + * @returns {void} - 无返回值 + */ + async eventTrack( + arg: TGApp.Plugins.JSBridge.Arg, + ): Promise { + console.log(`[eventTrack] ${JSON.stringify(arg.payload)}`); + await this.loadJSBridge(); + } + + /** + * @func genAuthKey + * @since Beta v0.3.9 + * @desc 获取米游社客户端的 authkey + * @param {TGApp.Plugins.JSBridge.Arg} arg - 请求参数 + * @returns {void} - 无返回值 + */ + async genAuthKey( + arg: TGApp.Plugins.JSBridge.Arg, + ): Promise { + const userStore = useUserStore(); + if (!userStore.cookie) return; + const cookie = { + mid: userStore.cookie.mid, + stoken: userStore.cookie.stoken, + }; + const res = await TGRequest.User.getAuthkey2(cookie, arg.payload); + await this.callback(arg.callback, res.data); + } + + /** + * @func getActionTicket + * @since Beta v0.3.9 + * @desc 获取米游社客户端的 action_ticket + * @param {TGApp.Plugins.JSBridge.Arg} arg - 请求参数 + * @returns {void} - 无返回值 + */ + async getActionTicket( + arg: TGApp.Plugins.JSBridge.Arg, + ): Promise { + const user = useUserStore(); + if (!user.cookie) return; + const uid = user.account.gameUid; + const mid = user.cookie.mid; + const stoken = user.cookie.stoken; + const ActionTicket = await TGRequest.User.bySToken.getActionTicket( + arg.payload.action_type, + stoken, + mid, + uid, + ); + await this.callback(arg.callback, ActionTicket.data); + } + + /** + * @func getCookieInfo + * @since Beta v0.3.9 + * @desc 获取米游社客户端的 cookie + * @param {TGApp.Plugins.JSBridge.NullArg} arg - 请求参数 + * @returns {void} - 无返回值 + */ + async getCookieInfo(arg: TGApp.Plugins.JSBridge.NullArg): Promise { + const user = useUserStore(); + if (!user.cookie) return; + const data = { + ltoken: user.cookie.ltoken, + ltuid: user.cookie.ltuid, + login_ticket: "", + }; + await this.callback(arg.callback, data); + } + + /** + * @func getCookieToken + * @since Beta v0.3.9 + * @desc 获取米游社客户端的 cookie_token + * @param {TGApp.Plugins.JSBridge.Arg} arg - 请求参数 + * @returns {void} - 无返回值 + */ + async getCookieToken( + arg: TGApp.Plugins.JSBridge.Arg, + ): Promise { + const user = useUserStore(); + if (!user.cookie) return; + if (arg.payload.forceRefresh) { + const res = await getCookieTokenBySToken(user.cookie.mid, user.cookie.stoken); + if (typeof res !== "string") { + return; + } + user.cookie.cookie_token = res; + await TGSqlite.saveAppData("cookie", JSON.stringify(user.cookie)); + } + const executeJS = `javascript:(function(){ + var domain = window.location.host; + document.cookie = "cookie_token=${user.cookie.cookie_token};domain=." + domain + ";path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT; + document.cookie = "ltoken=${user.cookie.ltoken};domain=." + domain + ";path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT; + })();`; + console.info(`[getCookieToken] ${executeJS}`); + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + const data = { + cookie_token: user.cookie.cookie_token, + }; + await this.callback(arg.callback, data); + } + + /** + * @func getDS + * @since Beta v0.3.9 + * @desc 获取米游社客户端的 DS 参数 + * @param {TGApp.Plugins.JSBridge.NullArg} arg - 方法参数 + * @returns {void} - 无返回值 + */ + async getDS(arg: TGApp.Plugins.JSBridge.NullArg): Promise { + const data = { + DS: getDS4JS("lk2", 1, undefined, undefined), + }; + await this.callback(arg.callback, data); + } + + /** + * @func getDS2 + * @since Beta v0.3.9 + * @desc 获取米游社客户端的 DS 参数 + * @param {TGApp.Plugins.JSBridge.Arg} arg + * @returns {Promise} + */ + async getDS2( + arg: TGApp.Plugins.JSBridge.Arg, + ): Promise { + const data = { + DS: getDS4JS("common", 2, arg.payload.body, arg.payload.query), + }; + await this.callback(arg.callback, data); + } + + /** + * @func getHTTPRequestHeaders + * @since Beta v0.3.9 + * @desc 获取米游社客户端的 HTTP 请求头 + * @param {TGApp.Plugins.JSBridge.NullArg} arg - 请求参数 + * @returns {void} - 无返回值 + */ + async getHTTPRequestHeaders(arg: TGApp.Plugins.JSBridge.NullArg): Promise { + const localFp = getDeviceInfo("device_fp"); + let deviceInfo = useAppStore().deviceInfo; + if (localFp === "0000000000000") { + deviceInfo = await TGRequest.Device.getFp(deviceInfo); + } + const data = { + "user-agent": TGConstant.BBS.UA_MOBILE, + "x-rpc-client_type": "5", + "x-rpc-device_id": deviceInfo.device_id, + "x-rpc-app_version": TGConstant.BBS.VERSION, + "x-rpc-device_fp": deviceInfo.device_fp, + }; + await this.callback(arg.callback, data); + } + + /** + * @func getStatusBarHeight + * @since Beta v0.3.9 + * @desc 获取状态栏高度 + * @param {TGApp.Plugins.JSBridge.NullArg} arg - 请求参数 + * @returns {void} - 无返回值 + */ + async getStatusBarHeight(arg: TGApp.Plugins.JSBridge.NullArg): Promise { + const data = { + statusBarHeight: 0, + }; + await this.callback(arg.callback, data); + } + + /** + * @func getUserInfo + * @since Beta v0.3.9 + * @desc 获取米游社客户端的用户信息 + * @param {TGApp.Plugins.JSBridge.NullArg} arg - 类型参数 + * @returns {void} - 无返回值 + */ + async getUserInfo(arg: TGApp.Plugins.JSBridge.NullArg): Promise { + const user = useUserStore(); + if (!user.cookie) return; + const cookieToken = user.cookie.cookie_token; + const accountId = user.cookie.account_id; + const userInfo = await TGRequest.User.byCookie.getUserInfo(cookieToken, accountId); + if ("retcode" in userInfo) { + console.error(`[${arg.callback}] ${userInfo.message}`); + return; + } + await this.callback(arg.callback, userInfo); + } + + /** + * @func onClickImg + * @since Beta v0.3.9 + * @desc 点击图片,下载到本地 + * @param {TGApp.Plugins.JSBridge.Arg} arg - 方法参数 + * @returns {void} - 无返回值 + */ + async onClickImg( + arg: TGApp.Plugins.JSBridge.Arg, + ): Promise { + const { image_list } = arg.payload; + const image = image_list[0]; + const executeJS = this.getSaveImgJS(image.url, image.format); + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + } + + /** + * @func openApplication + * @since Beta v0.3.9 + * @desc 打开应用 + * @param {TGApp.Plugins.JSBridge.Arg} arg - 方法参数 + * @returns {void} - 无返回值 + */ + async openApplication( + arg: TGApp.Plugins.JSBridge.Arg, + ): Promise { + console.log(`[openApplication] ${JSON.stringify(arg.payload)}`); + await appWindow.setFocus(); + showSnackbar({ + text: `不支持的操作:OpenApplication(${JSON.stringify(arg.payload)})`, + color: "error", + }); + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 1500); + }); + await this.window?.setFocus(); + } + + /** + * @func pushPage + * @since Beta v0.3.9 + * @desc 打开米游社客户端的页面 + * @param {TGApp.Plugins.JSBridge.Arg} arg - 方法参数 + * @returns {void} - 无返回值 + */ + async pushPage( + arg: TGApp.Plugins.JSBridge.Arg, + ): Promise { + const res = await parseLink(arg.payload.page, true); + if (!res) { + await appWindow.setFocus(); + showSnackbar({ + text: `未知链接:${arg.payload.page}`, + color: "error", + timeout: 3000, + }); + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 3000); + }); + await this.window?.setFocus(); + return; + } + if (typeof res !== "string") return; + this.route.push(res); + console.log(`[pushPage] ${res}`); + const executeJS = `javascript:(function(){ + window.location.href = '${res}'; + })();`; + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + await this.loadJSBridge(); + await this.hideSideBar(); + await this.hideOverlay(); + await this.window?.setFocus(); + } + + /** + * @func share + * @since Beta v0.3.9 + * @desc 分享 + * @param {TGApp.Plugins.JSBridge.Arg} arg - 方法参数 + * @returns {void} - 无返回值 + */ + async share(arg: TGApp.Plugins.JSBridge.Arg): Promise { + // 如果有数据 + if (arg.payload.type === "image" && arg.payload.content?.image_url !== undefined) { + const image = arg.payload.content.image_url; + const format = image.split(".").pop(); + const executeJS = this.getSaveImgJS(image, format ?? "png"); + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + await this.callback(arg.callback, {}); + return; + } + if (arg.payload.type === "screenshot") { const executeJS = `javascript:(async function() { // 查找 id 为 dom2img 的 script var hasDom2img = false; @@ -657,9 +711,9 @@ class TGClient { }); } var shareDom; - if(${JSON.stringify(payload?.content)} === '{}') { + if(${JSON.stringify(arg.payload.content)} === '{}') { shareDom = document.querySelector('.share'); - } else if (${payload?.content?.preview} === true) { + } else if (${arg.payload.content?.preview} === true) { shareDom = document.querySelector('#root'); } if(shareDom === undefined || shareDom === null) { @@ -692,14 +746,14 @@ class TGClient { }); alert('保存成功'); } - mhyWebBridge('${callback}', {}); + mhyWebBridge('${arg.callback}', {}); })();`; await invoke("execute_js", { label: "mhy_client", js: executeJS }); return; } - if (payload?.type === "image") { - if (payload?.content.image_base64 !== undefined) { - let image = payload.content.image_base64; + if (arg.payload.type === "image") { + if (arg.payload.content?.image_base64 !== undefined) { + let image = arg.payload.content.image_base64; image = `data:image/png;base64,${image}`; const executeJS = `javascript:(async function() { // 转换成 blob @@ -720,52 +774,18 @@ class TGClient { }); alert('保存成功'); } - mhyWebBridge('${callback}', {}); + mhyWebBridge('${arg.callback}', {}); })();`; await invoke("execute_js", { label: "mhy_client", js: executeJS }); return; } } - console.warn("[share]", payload); + console.warn("[share]", arg.payload); // 延时 3s setTimeout(async () => { - await this.callback(callback, {}); + await this.callback(arg.callback, {}); }, 10000); } - - /** - * @func eventTrack - * @since Beta v0.3.8 - * @desc 事件跟踪 - * @param {unknown} payload - 请求参数 - * @returns {void} - 无返回值 - */ - async eventTrack(payload: unknown): Promise { - console.log(`[eventTrack] ${JSON.stringify(payload)}`); - await this.loadJSBridge(); - } - - /** - * @func openApplication - * @since Beta v0.3.8 - * @desc 打开应用 - * @param {unknown} payload - 请求参数 - * @returns {void} - 无返回值 - */ - async openApplication(payload: unknown): Promise { - console.log(`[openApplication] ${JSON.stringify(payload)}`); - await appWindow.setFocus(); - showSnackbar({ - text: `不支持的操作:OpenApplication(${JSON.stringify(payload)})`, - color: "error", - }); - await new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 1500); - }); - await this.window?.setFocus(); - } } const mhyClient = new TGClient();