From 6792c0ac0a88cfb203884f53b74c6259504a71ed Mon Sep 17 00:00:00 2001 From: BTMuli Date: Wed, 25 Oct 2023 00:20:30 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20=E5=AE=8C=E5=96=84=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.toml | 2 +- src-tauri/src/client.rs | 13 +- src-tauri/tauri.conf.json | 9 +- src/utils/TGClient.ts | 178 +++++++++++++++++++++------- src/web/request/operVerification.ts | 19 +-- src/web/utils/TGUtils.ts | 7 +- src/web/utils/getRequestHeader.ts | 52 +++++++- 7 files changed, 206 insertions(+), 74 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b1002c95..2703f778 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" tauri-build = { version = "1.4", features = [] } [dependencies] -tauri = { version = "1.4", features = [ "os-all", "clipboard-all", "dialog-open", "dialog-save", "fs-create-dir", "fs-remove-dir", "fs-write-file", "fs-remove-file", "fs-read-file", "path-all", "fs-exists", "window-close", "window-set-title", "window-unminimize", "window-show", "window-set-focus", "http-request", "shell-open"] } +tauri = { version = "1.4", features = [ "window-hide", "os-all", "clipboard-all", "dialog-open", "dialog-save", "fs-create-dir", "fs-remove-dir", "fs-write-file", "fs-remove-file", "fs-read-file", "path-all", "fs-exists", "window-close", "window-set-title", "window-unminimize", "window-show", "window-set-focus", "http-request", "shell-open"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" webview2-com = "0.27.0" diff --git a/src-tauri/src/client.rs b/src-tauri/src/client.rs index 970ea645..f0d028c8 100644 --- a/src-tauri/src/client.rs +++ b/src-tauri/src/client.rs @@ -21,20 +21,25 @@ fn get_mhy_client_url(func: String) -> WindowUrl { // 操作米游社客户端 #[tauri::command] -pub async fn create_mhy_client(handle: AppHandle, func: String) { +pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) { let mut mhy_window_config = handle.config().tauri.windows.get(1).unwrap().clone(); - mhy_window_config.url = get_mhy_client_url(func.clone()); + // 如果没有传入 url 参数,则使用默认的米游社客户端入口地址 + if url != "" { + mhy_window_config.url = WindowUrl::External(url.parse().unwrap()); + } else { + mhy_window_config.url = get_mhy_client_url(func.clone()); + } let has_mhy_client = handle.get_window("mhy_client").is_some(); if has_mhy_client { dbg!("mhy_client exists"); return; } + let mhy_client = WindowBuilder::from_config(&handle, mhy_window_config).build().unwrap(); let js_bridge = r#" window.MiHoYoJSInterface = { postMessage: function(arg) { window.__TAURI__.event.emit('post_mhy_client', arg) }, closePage: function() { this.postMessage('{"method":"closePage"}') }, }; "#; - let mhy_client = WindowBuilder::from_config(&handle, mhy_window_config).build(); - mhy_client.unwrap().eval(&js_bridge).ok().unwrap(); + mhy_client.eval(&js_bridge).ok().unwrap(); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index b7477c6a..0595f890 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -49,7 +49,8 @@ "unminimize": true, "show": true, "close": true, - "setFocus": true + "setFocus": true, + "hide": true }, "os": { "all": true @@ -85,7 +86,7 @@ "security": { "dangerousRemoteDomainIpcAccess": [ { - "domain": "api-static.mihoyo.com", + "domain": "act.mihoyo.com", "windows": ["mhy_client"], "enableTauriAPI": true }, @@ -130,8 +131,8 @@ "url": "https://api-static.mihoyo.com/", "userAgent": "Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/2.60.1", "visible": false, - "width": 360, - "height": 780, + "width": 400, + "height": 800, "center": true, "decorations": false } diff --git a/src/utils/TGClient.ts b/src/utils/TGClient.ts index b4e731ee..7187f6b4 100644 --- a/src/utils/TGClient.ts +++ b/src/utils/TGClient.ts @@ -7,13 +7,12 @@ import { event, invoke } from "@tauri-apps/api"; import type { Event } from "@tauri-apps/api/event"; import { WebviewWindow } from "@tauri-apps/api/window"; -import Md5 from "js-md5"; import { getDeviceID } from "./toolFunc"; import { useUserStore } from "../store/modules/user"; import TGConstant from "../web/constant/TGConstant"; import TGRequest from "../web/request/TGRequest"; -import TGUtils from "../web/utils/TGUtils"; +import { getDS4JS } from "../web/utils/getRequestHeader"; // 正常 arg 参数 interface NormalArg { @@ -49,6 +48,14 @@ class TGClient { */ private window: WebviewWindow | null; + /** + * @private 模拟路由 + * @since Beta v0.3.4 + * @type {string[]} + * @memberof TGClient + */ + private route: string[] = []; + /** * @constructor * @since Beta v0.3.4 @@ -61,6 +68,8 @@ class TGClient { } catch (error) { this.window = null; } + this.route = []; + this.listener = undefined; } /** @@ -93,18 +102,44 @@ class TGClient { await invoke("execute_js", { label: "mhy_client", js: executeJS }); } + /** + * @func getUrl + * @since Beta v0.3.4 + * @desc 获取 url + * @param {string} func - 方法名 + * @returns {string} - url + */ + getUrl(func: string): string { + switch (func) { + case "sign_in": + return "https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?act_id=e202009291139501"; + case "game_record": + return "https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen"; + default: + return ""; + } + } + /** * @func open * @since Beta v0.3.4 * @desc 打开米游社客户端 * @param {string} func - 方法名 + * @param {string} url - url * @returns {void} - 无返回值 */ - async open(func: string): Promise { + async open(func: string, url?: string): Promise { if (this.window !== null) { await this.window.close(); } - await invoke("create_mhy_client", { func }); + if (url === undefined) { + url = this.getUrl(func); + this.route = [url]; + } else if (func !== "closePage") { + this.route.push(url); + } + console.log(`[open] ${url}`); + await invoke("create_mhy_client", { func, url }); this.window = WebviewWindow.getByLabel("mhy_client"); await this.window?.show(); } @@ -126,6 +161,9 @@ class TGClient { case "getCookieInfo": await this.getCookieInfo(payload, callback); break; + case "getCookieToken": + await this.getCookieToken(callback); + break; case "getActionTicket": await this.getActionTicket(payload, callback); await this.hideSideBar(); @@ -134,16 +172,25 @@ class TGClient { await this.getHTTPRequestHeaders(callback); break; case "getDS": - await this.getDS(callback); + await this.getDS(1, callback, payload); break; case "getDS2": - await this.getDS2(payload, callback); + await this.getDS(2, callback, payload); break; case "getUserInfo": await this.getUserInfo(callback); break; case "configure_share": break; + case "pushPage": + await this.pushPage(payload); + break; + case "closePage": + await this.closePage(); + break; + case "login": + await this.nullCallback(arg); + break; // getNotificationSettings default: console.warn(`[${arg.windowLabel}] ${arg.payload}`); @@ -167,7 +214,7 @@ class TGClient { }; const js = `javascript:mhyWebBridge("${callback}", ${JSON.stringify(response)});`; console.info(`[callback] ${js}`); - await invoke("create_mhy_client", { func: "execute_js" }); + await invoke("create_mhy_client", { func: "execute_js", url: "" }); await invoke("execute_js", { label: "mhy_client", js }); } @@ -203,6 +250,35 @@ class TGClient { await this.callback(callback, data); } + /** + * @func getCookieToken + * @since Beta v0.3.4 + * @todo 待完善 + * @desc 获取米游社客户端的 cookie_token + * @param {string} callback - 回调函数名 + * @returns {void} - 无返回值 + */ + async getCookieToken(callback: string): Promise { + const user = useUserStore(); + const executeJS = + "javascript:(function(){" + + `document.cookie = "account_id=${user.cookie.account_id};domain=.mihoyo.com;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;";` + + `document.cookie = "cookie_token=${user.cookie.cookie_token};domain=.mihoyo.com;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;";` + + `document.cookie = "ltoken=${user.cookie.ltoken};domain=.mihoyo.com;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;";` + + `document.cookie = "ltuid=${user.cookie.ltuid};domain=.mihoyo.com;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;";` + + `document.cookie = "stuid=${user.cookie.stuid};domain=.mihoyo.com;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;";` + + `document.cookie = "stoken=${user.cookie.stoken};domain=.mihoyo.com;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;";` + + `document.cookie = "mid=${user.cookie.mid};domain=.mihoyo.com;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;";` + + "})();"; + console.info(`[getCookieToken] ${executeJS}`); + await invoke("execute_js", { label: "mhy_client", js: executeJS }); + // callback + const data = { + cookie_token: user.cookie.cookie_token, + }; + await this.callback(callback, data); + } + /** * @func getActionTicket * @since Beta v0.3.4 @@ -246,38 +322,24 @@ class TGClient { * @func getDS * @since Beta v0.3.4 * @desc 获取米游社客户端的 DS 参数 + * @param {number} dsType - DS 类型 * @param {string} callback - 回调函数名 - * @returns {void} - 无返回值 - */ - async getDS(callback: string): Promise { - const salt = TGConstant.Salt.LK2; - const time = Math.floor(Date.now() / 1000).toString(); - const random = TGUtils.Tools.getRandomString(6); - const check = Md5.md5.update(`salt=${salt}&t=${time}&r=${random}`).hex(); - const data = { - DS: `${time},${random},${check}`, - }; - await this.callback(callback, data); - } - - /** - * @func getDS2 - * @since Beta v0.3.4 - * @desc 获取米游社客户端的 DS 参数 * @param {unknown} payload - 请求参数 - * @param {string} callback - 回调函数名 * @returns {void} - 无返回值 */ - async getDS2(payload: any, callback: string): Promise { - const { query, body } = payload; - const salt = TGConstant.Salt.X4; - const time = Math.floor(Date.now() / 1000).toString(); - const random = TGUtils.Tools.getRandomNumber(100001, 200000).toString(); - const dataB = TGUtils.Tools.transParams(body); - const dataQ = TGUtils.Tools.transParams(query); - const check = Md5.md5.update(`salt=${salt}&t=${time}&r=${random}&b=${dataB}&q=${dataQ}`).hex(); + 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 = ""; + if (dsType === 2) { + const { body, query } = payload; + ds = getDS4JS(saltType, dsType, body, query); + } else { + ds = getDS4JS(saltType, dsType, undefined, undefined); + } const data = { - DS: `${time},${random},${check}`, + DS: ds, }; await this.callback(callback, data); } @@ -298,15 +360,49 @@ class TGClient { console.error(`[${callback}] ${userInfo.message}`); return; } - // const data = { - // id: userInfo.uid, - // gender: userInfo.gender, - // nickname: userInfo.nickname, - // introduce: userInfo.introduce, - // avatar_url: userInfo.avatar_url, - // }; await this.callback(callback, userInfo); } + + /** + * @func pushPage + * @since Beta v0.3.4 + * @desc 打开米游社客户端的页面 + * @param {unknown} payload - 请求参数 + * @returns {void} - 无返回值 + */ + async pushPage(payload: any): Promise { + const url = payload.page; + await this.open("pushPage", url); + } + + /** + * @func closePage + * @since Beta v0.3.4 + * @desc 关闭米游社客户端的页面 + * @returns {void} - 无返回值 + */ + async closePage(): Promise { + this.route.pop(); + if (this.route.length === 0) { + await this.window?.hide(); + return; + } + const url = this.route[this.route.length - 1]; + await this.open("closePage", url); + } + + /** + * @func nullCallback + * @since Beta v0.3.4 + * @desc 空回调函数 + * @param {Event} arg - 回调参数 + * @returns {void} - 无返回值 + */ + async nullCallback(arg: Event): Promise { + console.warn(`[${arg.windowLabel}] ${arg.payload}`); + const { callback } = JSON.parse(arg.payload); + await this.callback(callback, {}); + } } const mhyClient = new TGClient(); diff --git a/src/web/request/operVerification.ts b/src/web/request/operVerification.ts index ce7525d4..2252dbdb 100644 --- a/src/web/request/operVerification.ts +++ b/src/web/request/operVerification.ts @@ -1,27 +1,14 @@ /** * @file src web request operVerification.ts * @description 验证码操作请求函数 - * @since Beta v0.3.3 + * @since Beta v0.3.4 */ import { http } from "@tauri-apps/api"; -import { v4 } from "uuid"; +import { getDeviceID } from "../../utils/toolFunc"; import TGUtils from "../utils/TGUtils"; -/** - * @description 获取 deviceId - * @since Beta v0.3.3 - * @return {string} deviceId - */ -function getDeviceId(): string { - const local = localStorage.getItem("deviceId"); - if (local) return local; - const id = v4(); - localStorage.setItem("deviceId", id); - return id; -} - /** * @description 发起验证请求 * @since Beta v0.3.3 @@ -79,7 +66,7 @@ export async function submitVerification( "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", - "x-rpc-device_id": getDeviceId(), + "x-rpc-device_id": getDeviceID(), "x-rpc-page": "3.1.3_#/ys", }; console.log(reqHeader); diff --git a/src/web/utils/TGUtils.ts b/src/web/utils/TGUtils.ts index 6f7e28c9..d48bf6d3 100644 --- a/src/web/utils/TGUtils.ts +++ b/src/web/utils/TGUtils.ts @@ -5,9 +5,9 @@ */ import { getAnnoCard } from "./getAnnoCard"; -import { getRequestHeader, getRandomString, getRandomNumber } from "./getRequestHeader"; +import { getRequestHeader } from "./getRequestHeader"; import { parseAnnoContent } from "./parseAnno"; -import { getServerByUid, transCookie, transParams } from "./tools"; +import { getServerByUid, transCookie } from "./tools"; const TGUtils = { Anno: { @@ -20,9 +20,6 @@ const TGUtils = { Tools: { getServerByUid, transCookie, - getRandomString, - getRandomNumber, - transParams, }, }; diff --git a/src/web/utils/getRequestHeader.ts b/src/web/utils/getRequestHeader.ts index 6ab3b3d4..566bd020 100644 --- a/src/web/utils/getRequestHeader.ts +++ b/src/web/utils/getRequestHeader.ts @@ -1,7 +1,7 @@ /** * @file web utils getRequestHeader.ts * @description 获取请求头 - * @since Beta v0.3.3 + * @since Beta v0.3.4 */ import Md5 from "js-md5"; @@ -36,7 +36,7 @@ function getSalt(saltType: string): string { * @param {number} max 最大值 * @returns {number} 随机数 */ -export function getRandomNumber(min: number, max: number): number { +function getRandomNumber(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1) + min); } @@ -46,7 +46,7 @@ export function getRandomNumber(min: number, max: number): number { * @param {number} length 字符串长度 * @returns {string} 随机字符串 */ -export function getRandomString(length: number): string { +function getRandomString(length: number): string { const str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let res = ""; for (let i = 0; i < length; i++) { @@ -111,3 +111,49 @@ export function getRequestHeader( cookie: transCookie(cookie), }; } + +/** + * @description 获取 DS + * @since Beta v0.3.4 + * @param {string} saltType salt 类型 + * @param {number} dsType ds 类型 + * @param {Record|string} body + * @param {Record|string} query + * @returns {string} DS + */ +export function getDS4JS( + saltType: string, + dsType: 1 | 2, + body: undefined, + query: undefined, +): string; +export function getDS4JS( + saltType: string, + dsType: 2, + body: Record | string, + query: Record | string, +): string; +export function getDS4JS( + saltType: string, + dsType: 1 | 2, + body?: Record | string, + query?: Record | string, +): string { + const salt = getSalt(saltType); + const time = Math.floor(Date.now() / 1000).toString(); + let random = getRandomNumber(100000, 200000).toString(); + let hashStr = ""; + let md5Str = ""; + if (dsType === 1) { + random = getRandomString(6); + hashStr = `salt=${salt}&t=${time}&r=${random}`; + } else { + body = body ?? ""; + query = query ?? ""; + const bodyStr = typeof body === "string" ? body : transParams(body); + const queryStr = typeof query === "string" ? query : transParams(query); + hashStr = `salt=${salt}&t=${time}&r=${random}&b=${bodyStr}&q=${queryStr}`; + } + md5Str = Md5.md5.update(hashStr).hex(); + return `${time},${random},${md5Str}`; +}