扫码登录

This commit is contained in:
目棃
2025-01-08 14:58:00 +08:00
parent f801363440
commit d5f40a5775
18 changed files with 1293 additions and 970 deletions

View File

@@ -3,7 +3,7 @@
"version": "0.6.7",
"description": "Game Tool for GenshinImpact player",
"private": true,
"packageManager": "pnpm@9.15.2",
"packageManager": "pnpm@10.0.0",
"type": "module",
"scripts": {
"build": "tauri build",
@@ -67,7 +67,7 @@
},
"dependencies": {
"@mdi/font": "7.4.47",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/api": "^2.2.0",
"@tauri-apps/plugin-deep-link": "^2.2.0",
"@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
@@ -87,9 +87,10 @@
"jsencrypt": "^3.3.2",
"pinia": "^2.3.0",
"pinia-plugin-persistedstate": "^4.2.0",
"sass": "^1.83.0",
"qrcode.vue": "^3.6.0",
"sass": "^1.83.1",
"sass-loader": "^16.0.4",
"uuid": "^11.0.3",
"uuid": "^11.0.4",
"vue": "^3.5.13",
"vue-echarts": "^7.0.3",
"vue-json-viewer": "^3.0.4",
@@ -101,12 +102,12 @@
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.17.0",
"@tauri-apps/cli": "2.1.0",
"@tauri-apps/cli": "2.2.2",
"@types/color-convert": "^2.0.4",
"@types/js-md5": "^0.7.2",
"@types/node": "^22.10.2",
"@types/node": "^22.10.5",
"@types/uuid": "^10.0.0",
"@typescript-eslint/parser": "^8.19.0",
"@typescript-eslint/parser": "^8.19.1",
"@vitejs/plugin-vue": "^5.2.1",
"concurrently": "^9.1.2",
"eslint": "^9.17.0",
@@ -119,7 +120,7 @@
"husky": "^9.1.7",
"jsonc-eslint-parser": "^2.4.0",
"lint-staged": "^15.3.0",
"oxlint": "^0.15.4",
"oxlint": "^0.15.5",
"prettier": "3.4.2",
"stylelint": "^16.12.0",
"stylelint-config-idiomatic-order": "^10.0.0",
@@ -130,9 +131,9 @@
"stylelint-prettier": "^5.0.2",
"stylelint-scss": "^6.10.0",
"typescript": "^5.7.2",
"typescript-eslint": "^8.19.0",
"vite": "^6.0.6",
"vite-plugin-vue-devtools": "^7.6.8",
"typescript-eslint": "^8.19.1",
"vite": "^6.0.7",
"vite-plugin-vue-devtools": "^7.7.0",
"vite-plugin-vuetify": "^2.0.4",
"vue-eslint-parser": "^9.4.3",
"yaml-eslint-parser": "^1.2.3"

874
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

486
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,15 +10,15 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2.0.3", features = [] }
tauri-build = { version = "2.0.4", features = [] }
[dependencies]
chrono = "0.4.39"
log = "0.4.22"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.134"
tauri = { version = "2.1.1", features = [] }
tauri-utils = "2.1.0"
serde_json = "1.0.135"
tauri = { version = "2.2.0", features = [] }
tauri-utils = "2.1.1"
url = "2.5.4"
walkdir = "2.5.0"

View File

@@ -37,97 +37,28 @@
"core:window:allow-set-focus",
"core:window:allow-show",
"core:window:allow-unminimize",
{
"identifier": "fs:allow-exists",
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-mkdir",
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-read-dir",
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-read-text-file",
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-remove",
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-write-file",
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-write-text-file",
"allow": [
{
"path": "**"
}
]
},
{ "identifier": "fs:allow-exists", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-mkdir", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-read-dir", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-read-text-file", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-remove", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-write-file", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-write-text-file", "allow": [{ "path": "**" }] },
{
"identifier": "http:default",
"allow": [
{
"url": "https://*.miyoushe.com/*"
},
{
"url": "https://*.mihoyo.com/*"
},
{
"url": "https://homa.snapgenshin.com/*"
},
{
"url": "https://*.hoyoverse.com/*"
}
{ "url": "https://*.miyoushe.com/*" },
{ "url": "https://*.mihoyo.com/*" },
{ "url": "https://homa.snapgenshin.com/*" },
{ "url": "https://*.hoyoverse.com/*" }
]
},
{
"identifier": "shell:allow-execute",
"allow": [
{
"name": "win_open",
"cmd": "explorer",
"args": true
},
{
"name": "mac_open",
"cmd": "open",
"args": true
},
{
"name": "exec-sh",
"cmd": "powershell",
"args": true
}
{ "name": "win_open", "cmd": "explorer", "args": true },
{ "name": "mac_open", "cmd": "open", "args": true },
{ "name": "exec-sh", "cmd": "powershell", "args": true }
]
}
],

View File

@@ -773,6 +773,11 @@
"description": "Enables the reparent command without any pre-configured scope.",
"commands": { "allow": ["reparent"], "deny": [] }
},
"allow-set-webview-background-color": {
"identifier": "allow-set-webview-background-color",
"description": "Enables the set_webview_background_color command without any pre-configured scope.",
"commands": { "allow": ["set_webview_background_color"], "deny": [] }
},
"allow-set-webview-focus": {
"identifier": "allow-set-webview-focus",
"description": "Enables the set_webview_focus command without any pre-configured scope.",
@@ -853,6 +858,11 @@
"description": "Denies the reparent command without any pre-configured scope.",
"commands": { "allow": [], "deny": ["reparent"] }
},
"deny-set-webview-background-color": {
"identifier": "deny-set-webview-background-color",
"description": "Denies the set_webview_background_color command without any pre-configured scope.",
"commands": { "allow": [], "deny": ["set_webview_background_color"] }
},
"deny-set-webview-focus": {
"identifier": "deny-set-webview-focus",
"description": "Denies the set_webview_focus command without any pre-configured scope.",
@@ -1100,6 +1110,21 @@
"description": "Enables the set_always_on_top command without any pre-configured scope.",
"commands": { "allow": ["set_always_on_top"], "deny": [] }
},
"allow-set-background-color": {
"identifier": "allow-set-background-color",
"description": "Enables the set_background_color command without any pre-configured scope.",
"commands": { "allow": ["set_background_color"], "deny": [] }
},
"allow-set-badge-count": {
"identifier": "allow-set-badge-count",
"description": "Enables the set_badge_count command without any pre-configured scope.",
"commands": { "allow": ["set_badge_count"], "deny": [] }
},
"allow-set-badge-label": {
"identifier": "allow-set-badge-label",
"description": "Enables the set_badge_label command without any pre-configured scope.",
"commands": { "allow": ["set_badge_label"], "deny": [] }
},
"allow-set-closable": {
"identifier": "allow-set-closable",
"description": "Enables the set_closable command without any pre-configured scope.",
@@ -1185,6 +1210,11 @@
"description": "Enables the set_minimizable command without any pre-configured scope.",
"commands": { "allow": ["set_minimizable"], "deny": [] }
},
"allow-set-overlay-icon": {
"identifier": "allow-set-overlay-icon",
"description": "Enables the set_overlay_icon command without any pre-configured scope.",
"commands": { "allow": ["set_overlay_icon"], "deny": [] }
},
"allow-set-position": {
"identifier": "allow-set-position",
"description": "Enables the set_position command without any pre-configured scope.",
@@ -1445,6 +1475,21 @@
"description": "Denies the set_always_on_top command without any pre-configured scope.",
"commands": { "allow": [], "deny": ["set_always_on_top"] }
},
"deny-set-background-color": {
"identifier": "deny-set-background-color",
"description": "Denies the set_background_color command without any pre-configured scope.",
"commands": { "allow": [], "deny": ["set_background_color"] }
},
"deny-set-badge-count": {
"identifier": "deny-set-badge-count",
"description": "Denies the set_badge_count command without any pre-configured scope.",
"commands": { "allow": [], "deny": ["set_badge_count"] }
},
"deny-set-badge-label": {
"identifier": "deny-set-badge-label",
"description": "Denies the set_badge_label command without any pre-configured scope.",
"commands": { "allow": [], "deny": ["set_badge_label"] }
},
"deny-set-closable": {
"identifier": "deny-set-closable",
"description": "Denies the set_closable command without any pre-configured scope.",
@@ -1530,6 +1575,11 @@
"description": "Denies the set_minimizable command without any pre-configured scope.",
"commands": { "allow": [], "deny": ["set_minimizable"] }
},
"deny-set-overlay-icon": {
"identifier": "deny-set-overlay-icon",
"description": "Denies the set_overlay_icon command without any pre-configured scope.",
"commands": { "allow": [], "deny": ["set_overlay_icon"] }
},
"deny-set-position": {
"identifier": "deny-set-position",
"description": "Denies the set_position command without any pre-configured scope.",

View File

@@ -2626,6 +2626,11 @@
"type": "string",
"const": "core:webview:allow-reparent"
},
{
"description": "Enables the set_webview_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-set-webview-background-color"
},
{
"description": "Enables the set_webview_focus command without any pre-configured scope.",
"type": "string",
@@ -2706,6 +2711,11 @@
"type": "string",
"const": "core:webview:deny-reparent"
},
{
"description": "Denies the set_webview_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-set-webview-background-color"
},
{
"description": "Denies the set_webview_focus command without any pre-configured scope.",
"type": "string",
@@ -2921,6 +2931,21 @@
"type": "string",
"const": "core:window:allow-set-always-on-top"
},
{
"description": "Enables the set_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-background-color"
},
{
"description": "Enables the set_badge_count command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-badge-count"
},
{
"description": "Enables the set_badge_label command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-badge-label"
},
{
"description": "Enables the set_closable command without any pre-configured scope.",
"type": "string",
@@ -3006,6 +3031,11 @@
"type": "string",
"const": "core:window:allow-set-minimizable"
},
{
"description": "Enables the set_overlay_icon command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-overlay-icon"
},
{
"description": "Enables the set_position command without any pre-configured scope.",
"type": "string",
@@ -3266,6 +3296,21 @@
"type": "string",
"const": "core:window:deny-set-always-on-top"
},
{
"description": "Denies the set_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-background-color"
},
{
"description": "Denies the set_badge_count command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-badge-count"
},
{
"description": "Denies the set_badge_label command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-badge-label"
},
{
"description": "Denies the set_closable command without any pre-configured scope.",
"type": "string",
@@ -3351,6 +3396,11 @@
"type": "string",
"const": "core:window:deny-set-minimizable"
},
{
"description": "Denies the set_overlay_icon command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-overlay-icon"
},
{
"description": "Denies the set_position command without any pre-configured scope.",
"type": "string",

View File

@@ -2626,6 +2626,11 @@
"type": "string",
"const": "core:webview:allow-reparent"
},
{
"description": "Enables the set_webview_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-set-webview-background-color"
},
{
"description": "Enables the set_webview_focus command without any pre-configured scope.",
"type": "string",
@@ -2706,6 +2711,11 @@
"type": "string",
"const": "core:webview:deny-reparent"
},
{
"description": "Denies the set_webview_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-set-webview-background-color"
},
{
"description": "Denies the set_webview_focus command without any pre-configured scope.",
"type": "string",
@@ -2921,6 +2931,21 @@
"type": "string",
"const": "core:window:allow-set-always-on-top"
},
{
"description": "Enables the set_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-background-color"
},
{
"description": "Enables the set_badge_count command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-badge-count"
},
{
"description": "Enables the set_badge_label command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-badge-label"
},
{
"description": "Enables the set_closable command without any pre-configured scope.",
"type": "string",
@@ -3006,6 +3031,11 @@
"type": "string",
"const": "core:window:allow-set-minimizable"
},
{
"description": "Enables the set_overlay_icon command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-overlay-icon"
},
{
"description": "Enables the set_position command without any pre-configured scope.",
"type": "string",
@@ -3266,6 +3296,21 @@
"type": "string",
"const": "core:window:deny-set-always-on-top"
},
{
"description": "Denies the set_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-background-color"
},
{
"description": "Denies the set_badge_count command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-badge-count"
},
{
"description": "Denies the set_badge_label command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-badge-label"
},
{
"description": "Denies the set_closable command without any pre-configured scope.",
"type": "string",
@@ -3351,6 +3396,11 @@
"type": "string",
"const": "core:window:deny-set-minimizable"
},
{
"description": "Denies the set_overlay_icon command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-overlay-icon"
},
{
"description": "Denies the set_position command without any pre-configured scope.",
"type": "string",

View File

@@ -1,6 +1,6 @@
//! @file src/client/mod.rs
//! @desc 客户端模块,负责操作米游社客户端
//! @since Beta v0.6.7
//! @since Beta v0.6.8
mod menu;
mod utils;
@@ -8,7 +8,7 @@ mod utils;
use tauri::{AppHandle, Manager, WebviewWindowBuilder};
use tauri_utils::config::WebviewUrl;
static BBS_VERSION: &'static str = "2.79.1";
static BBS_VERSION: &'static str = "2.80.1";
#[tauri::command]
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {

View File

@@ -1,4 +1,5 @@
<template>
<ToGameLogin v-model="showLoginQr" @success="tryGetTokens" />
<v-card class="tcu-box">
<template #prepend>
<v-avatar :image="userInfo.avatar" />
@@ -36,12 +37,18 @@
</template>
<template #actions>
<v-spacer />
<v-btn
variant="outlined"
@click="tryCaptchaLogin()"
icon="mdi-cellphone"
title="验证码登录"
/>
<!-- <v-btn-->
<!-- variant="outlined"-->
<!-- @click="showLoginQr = true"-->
<!-- icon="mdi-qrcode-scan"-->
<!-- title="扫码登录"-->
<!-- />-->
<!-- <v-btn-->
<!-- variant="outlined"-->
<!-- @click="tryCaptchaLogin()"-->
<!-- icon="mdi-cellphone"-->
<!-- title="验证码登录"-->
<!-- />-->
<v-btn
variant="outlined"
@click="confirmRefreshUser(uid!)"
@@ -62,6 +69,7 @@
variant="outlined"
icon="mdi-account-switch"
title="切换账户"
:disabled="accounts.length === 0"
@click="showMenu"
v-bind="props"
/>
@@ -90,10 +98,25 @@
/>
</template>
</v-list-item>
</v-list>
</v-menu>
<v-menu location="start">
<template v-slot:activator="{ props }">
<v-btn variant="outlined" icon="mdi-login" title="登录" v-bind="props" />
</template>
<v-list>
<v-list-item @click="addByCookie()" append-icon="mdi-account-plus">
<v-list-item-title>手动添加</v-list-item-title>
<v-list-item-subtitle>手动输入Cookie</v-list-item-subtitle>
</v-list-item>
<v-list-item @click="tryCaptchaLogin()" append-icon="mdi-cellphone">
<v-list-item-title>验证码登录</v-list-item-title>
<v-list-item-subtitle>使用手机号登录</v-list-item-subtitle>
</v-list-item>
<v-list-item @click="showLoginQr = true" append-icon="mdi-qrcode-scan">
<v-list-item-title>扫码登录</v-list-item-title>
<v-list-item-subtitle>使用米游社扫码登录</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-menu>
</template>
@@ -104,10 +127,11 @@ import showDialog from "@comp/func/dialog.js";
import showGeetest from "@comp/func/geetest.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import ToGameLogin from "@comp/pageConfig/tco-gameLogin.vue";
import Mys from "@Mys/index.js";
import TSUserAccount from "@Sqlite/modules/userAccount.js";
import { storeToRefs } from "pinia";
import { computed, shallowRef } from "vue";
import { computed, ref, shallowRef } from "vue";
import { useAppStore } from "@/store/modules/app.js";
import { useUserStore } from "@/store/modules/user.js";
@@ -119,6 +143,7 @@ import TakumiApi from "@/web/request/takumiReq.js";
const { isLogin } = storeToRefs(useAppStore());
const { uid, briefInfo, cookie, account } = storeToRefs(useUserStore());
const showLoginQr = ref<boolean>(false);
const accounts = shallowRef<Array<TGApp.App.Account.User>>([]);
const gameAccounts = shallowRef<Array<TGApp.Sqlite.Account.Game>>([]);
const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
@@ -131,37 +156,7 @@ const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
};
});
async function tryCaptchaLogin(): Promise<void> {
const phone = await showDialog.input("请输入手机号", "+86");
if (!phone) {
showSnackbar.cancel("已取消验证码登录");
return;
}
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(phone)) {
showSnackbar.warn("请输入正确的手机号");
return;
}
const actionType = await tryGetCaptcha(phone);
if (!actionType) return;
showSnackbar.success(`已发送验证码到 ${phone}`);
const captcha = await showDialog.input("请输入验证码", "验证码:", undefined, false);
if (!captcha) {
showSnackbar.warn("输入验证码为空");
return;
}
const loginResp = await tryLoginByCaptcha(phone, captcha, actionType);
if (!loginResp) return;
await showLoading.start("正在尝试登录...");
const ck: TGApp.App.Account.Cookie = {
account_id: loginResp.user_info.aid,
ltuid: loginResp.user_info.aid,
stuid: loginResp.user_info.aid,
mid: loginResp.user_info.mid,
cookie_token: "",
stoken: loginResp.token.token,
ltoken: "",
};
async function tryGetTokens(ck: TGApp.App.Account.Cookie): Promise<void> {
await showLoading.update("正在获取 LToken");
const ltokenRes = await PassportApi.lToken.get(ck);
if (typeof ltokenRes !== "string") {
@@ -231,6 +226,40 @@ async function tryCaptchaLogin(): Promise<void> {
showSnackbar.success("成功登录!");
}
async function tryCaptchaLogin(): Promise<void> {
const phone = await showDialog.input("请输入手机号", "+86");
if (!phone) {
showSnackbar.cancel("已取消验证码登录");
return;
}
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(phone)) {
showSnackbar.warn("请输入正确的手机号");
return;
}
const actionType = await tryGetCaptcha(phone);
if (!actionType) return;
showSnackbar.success(`已发送验证码到 ${phone}`);
const captcha = await showDialog.input("请输入验证码", "验证码:", undefined, false);
if (!captcha) {
showSnackbar.warn("输入验证码为空");
return;
}
const loginResp = await tryLoginByCaptcha(phone, captcha, actionType);
if (!loginResp) return;
await showLoading.start("正在尝试登录...");
const ck: TGApp.App.Account.Cookie = {
account_id: loginResp.user_info.aid,
ltuid: loginResp.user_info.aid,
stuid: loginResp.user_info.aid,
mid: loginResp.user_info.mid,
cookie_token: "",
stoken: loginResp.token.token,
ltoken: "",
};
await tryGetTokens(ck);
}
async function refreshUser(uid: string) {
let account = await TSUserAccount.account.getAccount(uid);
if (!account) {
@@ -375,6 +404,7 @@ async function tryGetCaptcha(phone: string, aigis?: string): Promise<string | fa
showSnackbar.error(`[${captchaResp.retcode}] ${captchaResp.message}`);
return false;
}
// @ts-expect-error type {} is not assignable to type string
const aigisResp: TGApp.Plugins.Mys.CaptchaLogin.CaptchaAigis = JSON.parse(captchaResp.data);
const resp = await showGeetest(JSON.parse(aigisResp.data));
const aigisStr = `${aigisResp.session_id};${btoa(JSON.stringify(resp))}`;

View File

@@ -0,0 +1,129 @@
<template>
<TOverlay v-model="model" hide blur-val="20px">
<div class="tog-box">
<div class="tog-top">
<div class="tog-title">请使用米游社进行扫码操作</div>
</div>
<div class="tog-mid">
<qrcode-vue
v-if="codeData"
class="tog-qr"
:value="codeData.url"
render-as="svg"
:background="'var(--box-bg-1)'"
foreground="var(--box-text-1)"
/>
</div>
</div>
</TOverlay>
</template>
<script setup lang="ts">
import TOverlay from "@comp/app/t-overlay.vue";
import showSnackbar from "@comp/func/snackbar.js";
import QrcodeVue from "qrcode.vue";
import { onUnmounted, shallowRef, watch } from "vue";
import PassportReq from "@/web/request/passportReq.js";
type ToGameLoginEmits = (e: "success", data: TGApp.App.Account.Cookie) => void;
// eslint-disable-next-line no-undef
let cycleTimer: NodeJS.Timeout | null = null;
const model = defineModel<boolean>({ default: false });
const emits = defineEmits<ToGameLoginEmits>();
const codeData = shallowRef<TGApp.BBS.GameLogin.GetLoginQrData>();
watch(model, async (value) => {
if (value) {
await freshQr();
cycleTimer = setInterval(cycleGetData, 1000);
}
});
async function freshQr(): Promise<void> {
const res = await PassportReq.qrLogin.create();
if ("retcode" in res) {
showSnackbar.error(`[${res.retcode}] ${res.message}`);
return;
}
codeData.value = res;
}
async function cycleGetData() {
if (cycleTimer === null || !codeData.value) return;
const res = await PassportReq.qrLogin.query(codeData.value.ticket);
console.log(res);
if ("retcode" in res) {
showSnackbar.error(`[${res.retcode}] ${res.message}`);
if (res.retcode === -106) {
await freshQr();
} else {
clearInterval(cycleTimer);
cycleTimer = null;
model.value = false;
}
return;
}
if (res.status === "Created" || res.status === "Scanned") return;
if (res.status === "Confirmed") {
clearInterval(cycleTimer);
cycleTimer = null;
const ck: TGApp.App.Account.Cookie = {
mid: res.user_info.mid,
stoken: res.tokens[0].token,
stuid: res.user_info.aid,
account_id: res.user_info.aid,
cookie_token: "",
ltoken: "",
ltuid: "",
};
emits("success", ck);
model.value = false;
}
}
onUnmounted(() => {
if (cycleTimer !== null) clearInterval(cycleTimer);
cycleTimer = null;
});
</script>
<style lang="css" scoped>
.tog-box {
display: flex;
flex-direction: column;
padding: 10px;
border-radius: 5px;
background-color: var(--box-bg-1);
color: var(--app-page-content);
gap: 10px;
}
.tog-top {
border-bottom: 1px solid var(--common-shadow-4);
font-family: var(--font-title);
text-align: center;
}
.tog-title {
color: var(--common-text-title);
font-size: 20px;
}
.tog-mid {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
aspect-ratio: 1;
background: var(--box-bg-2);
}
.tog-qr {
width: 256px;
height: 256px;
}
</style>

View File

@@ -1,12 +1,11 @@
/**
* @file plugins/Mys/index.ts
* @description Mys plugin index
* @since Beta v0.6.3
* @since Beta v0.6.8
*/
import * as ApiHub from "./request/apiHubReq.js";
import { doCaptchaLogin, getCaptcha } from "./request/doCaptchaLogin.js";
import { getLoginQr, getLoginStatus } from "./request/doGameLogin.js";
import { getGachaData, getPositionData } from "./request/obcReq.js";
import * as Painter from "./request/painterReq.js";
import * as Post from "./request/postReq.js";
@@ -21,7 +20,7 @@ const Mys = {
Gacha: { get: getGachaData, card: getGachaCard },
Position: { get: getPositionData, card: getPositionCard },
Lottery: { get: Painter.lotteryUserShow, card: getLotteryCard },
User: { getQr: getLoginQr, getData: getLoginStatus, getCaptcha, login: doCaptchaLogin },
User: { getCaptcha, login: doCaptchaLogin },
};
export default Mys;

View File

@@ -1,59 +0,0 @@
/**
* @file plugins/Mys/request/doGameLogin
* @description 获取 gameToken曲线获取 stoken
* @since Beta v0.5.0
*/
import TGHttp from "@/utils/TGHttp.js";
import { getDeviceInfo } from "@/utils/toolFunc.js";
import { getRequestHeader } from "@/web/utils/getRequestHeader.js";
const APP_ID = 8;
/**
* @description 获取登录二维码
* @since Beta v0.5.0
* @returns {Promise<TGApp.Plugins.Mys.GameLogin.GetLoginQrData|TGApp.BBS.Response.Base>}
*/
export async function getLoginQr(): Promise<
TGApp.Plugins.Mys.GameLogin.GetLoginQrData | TGApp.BBS.Response.Base
> {
const data: Record<string, string | number> = {
app_id: APP_ID,
device: getDeviceInfo("device_id"),
};
const resp = await TGHttp<
TGApp.Plugins.Mys.GameLogin.GetLoginQrResponse | TGApp.BBS.Response.Base
>("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/fetch", {
method: "POST",
headers: getRequestHeader({}, "POST", data),
body: JSON.stringify(data),
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
/**
* @description 获取登录状态
* @since Beta v0.5.0
* @param {string} ticket 二维码 ticket
* @returns {Promise<TGApp.Plugins.Mys.GameLogin.GetLoginStatusData | TGApp.BBS.Response.Base>}
*/
export async function getLoginStatus(
ticket: string,
): Promise<TGApp.Plugins.Mys.GameLogin.GetLoginStatusData | TGApp.BBS.Response.Base> {
const data: Record<string, string | number> = {
app_id: APP_ID,
device: getDeviceInfo("device_id"),
ticket,
};
const resp = await TGHttp<
TGApp.Plugins.Mys.GameLogin.GetLoginStatusResponse | TGApp.BBS.Response.Base
>("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/query", {
method: "POST",
headers: getRequestHeader({}, "POST", data),
body: JSON.stringify(data),
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}

View File

@@ -1,102 +0,0 @@
/**
* @file plugins/Mys/types/GameLogin.d.ts
* @description Mys 插件 Game 登录类型定义文件
* @since Beta v0.4.4
*/
/**
* @description Mys 插件 Game 登录类型
* @since Beta v0.4.4
* @namespace TGApp.Plugins.Mys.GameLogin
* @memberof TGApp.Plugins.Mys
*/
declare namespace TGApp.Plugins.Mys.GameLogin {
/**
* @description 获取登录二维码返回数据
* @since Beta v0.3.0
* @interface GetLoginQrResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {GetLoginQrData} data 数据
* @return GetLoginQrResponse
*/
interface GetLoginQrResponse extends TGApp.BBS.Response.BaseWithData {
data: GetLoginQrData;
}
/**
* @description 获取登录二维码返回数据
* @since Beta v0.3.0
* @interface GetLoginQrData
* @property {string} url 二维码链接
* @return GetLoginQrData
*/
interface GetLoginQrData {
url: string;
}
/**
* @description 获取登录状态返回数据
* @since Beta v0.3.0
* @interface GetLoginStatusResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {GetLoginStatusData} data 数据
* @return GetLoginStatusResponse
*/
interface GetLoginStatusResponse extends TGApp.BBS.Response.BaseWithData {
data: GetLoginStatusData;
}
/**
* @description 获取登录状态返回数据
* @since Beta v0.3.0
* @interface GetLoginStatusData
* @property {string} stat 状态 // Init: 未扫码Scanned: 已扫码Confirmed: 已确认
* @property {StatusPayload} payload 状态数据
* @return GetLoginStatusData
*/
interface GetLoginStatusData {
stat: string;
payload: StatusPayload;
}
/**
* @description 获取登录状态返回数据
* @since Beta v0.3.0
* @interface StatusPayload
* @property {string} ext 未知
* @property {string} proto 未知
* @property {string} raw 序列化数据,反序列化后是 {uid: string, token: string}
* @return StatusPayload
*/
interface StatusPayload {
ext: string;
proto: string;
raw: string;
}
/**
* @description 反序列化后的登录状态数据
* @since Beta v0.4.4
* @interface StatusPayloadRawOpen
* @property {string} uid 用户 UID
* @property {string} token 用户 token
* @return StatusPayloadRawOpen
*/
interface StatusPayloadRawOpen {
open_id: string;
open_token: string;
}
/**
* @description 反序列化后的登录状态数据
* @since Beta v0.4.4
* @interface StatusPayloadRawAccount
* @property {string} uid 用户 UID
* @property {string} token 用户 token
* @return StatusPayloadRawOpen
*/
interface StatusPayloadRawAccount {
uid: string;
token: string;
}
}

152
src/types/BBS/GameLogin.d.ts vendored Normal file
View File

@@ -0,0 +1,152 @@
/**
* @file types/BBS/GameLogin.d.ts
* @description Mys 插件 Game 登录类型定义文件
* @since Beta v0.6.8
*/
declare namespace TGApp.BBS.GameLogin {
/**
* @description 获取登录二维码返回数据
* @since Beta v0.3.0
* @interface GetLoginQrResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {GetLoginQrData} data 数据
* @return GetLoginQrResponse
*/
type GetLoginQrResponse = TGApp.BBS.Response.BaseWithData & { data: GetLoginQrData };
/**
* @description 获取登录二维码返回数据
* @since Beta v0.6.8
* @interface GetLoginQrData
* @property {string} ticket 二维码 ticket用于查询登录状态
* @property {string} url 二维码链接
* @return GetLoginQrData
*/
type GetLoginQrData = { ticket: string; url: string };
/**
* @description 获取登录状态返回数据
* @since Beta v0.3.0
* @interface GetLoginStatusResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {GetLoginStatusData} data 数据
* @return GetLoginStatusResponse
*/
type GetLoginStatusResponse = TGApp.BBS.Response.BaseWithData & { data: GetLoginStatusData };
/**
* @description 获取登录状态返回数据-通用
* @since Beta v0.6.8
* @interface GetLoginStatusDataCommon
* @property {string} app_id 应用 ID
* @property {number} client_type 客户端类型
* @property {string} created_at 创建时间
* @property {string} ext 未知
* @property {boolean} need_realperson 是否需要真人验证
* @property {unknown} realname_info 未知
* @property {string} scan_game_biz 未知
* @property {string} scanned_at 扫描时间
* @return GetLoginStatusDataCommon
*/
type GetLoginStatusDataCommon = {
app_id: string;
client_type: number;
created_at: string;
ext: string;
need_realperson: boolean;
realname_info: unknown;
scan_game_biz: string;
scanned_at: string;
};
/**
* @description 获取登录状态返回数据-未确认
* @since Beta v0.6.8
* @interface GetLoginStatusDataUnconfirmed
* @extends GetLoginStatusDataCommon
* @property {"Created" | "Scanned"} status 状态
* @return GetLoginStatusDataUnconfirmed
*/
type GetLoginStatusDataUnconfirmed = GetLoginStatusDataCommon & {
status: "Created" | "Scanned";
};
/**
* @description 获取登录状态返回数据-已确认
* @since Beta v0.6.8
* @interface GetLoginStatusDataConfirmed
* @extends GetLoginStatusDataCommon
*/
type GetLoginStatusDataConfirmed = GetLoginStatusDataCommon & {
status: "Confirmed";
tokens: Array<GetLoginStatusDataToken>;
user_info: GetLoginStatusDataUserInfo;
};
/**
* @description 获取登录状态返回数据-Token
* @since Beta v0.6.8
* @interface GetLoginStatusDataToken
* @property {string} token token
* @property {number} token_type token 类型
* @return GetLoginStatusDataToken
*/
type GetLoginStatusDataToken = { token: string; token_type: number };
/**
* @description 获取登录状态返回数据-用户信息
* @since Beta v0.6.8
* @interface GetLoginStatusDataUserInfo
* @property {string} account_name 账号名称
* @property {string} aid 账号 ID
* @property {string} area_code 区域代码
* @property {string} country 国家
* @property {string} email 邮箱
* @property {string} identity_code 身份证号
* @property {number} is_email_verify 是否验证邮箱
* @property {Array<unknown>} links 链接
* @property {string} mid mid
* @property {string} mobile 手机号
* @property {string} password_time 密码时间
* @property {string} realname 真实姓名
* @property {string} rebind_area_code 重新绑定区域代码
* @property {string} rebind_mobile 重新绑定手机号
* @property {string} rebind_mobile_time 重新绑定手机号时间
* @property {string} safe_area_code 安全区域代码
* @property {string} safe_mobile 安全手机号
* @property {string} unmasked_email 未屏蔽邮箱
* @property {number} unmasked_email_type 未屏蔽邮箱类型
* @return GetLoginStatusDataUserInfo
*/
type GetLoginStatusDataUserInfo = {
account_name: string;
aid: string;
area_code: string;
country: string;
email: string;
identity_code: string;
is_email_verify: number;
links: Array<unknown>;
mid: string;
mobile: string;
password_time: string;
realname: string;
rebind_area_code: string;
rebind_mobile: string;
rebind_mobile_time: string;
safe_area_code: string;
safe_mobile: string;
unmasked_email: string;
unmasked_email_type: number;
};
/**
* @description 获取登录状态返回数据
* @since Beta v0.6.8
* @interface GetLoginStatusData
* @property {GetLoginStatusDataUnconfirmed | GetLoginStatusDataConfirmed} data 数据
* @return GetLoginStatusData
*/
type GetLoginStatusData = GetLoginStatusDataUnconfirmed | GetLoginStatusDataConfirmed;
}

View File

@@ -1,10 +1,10 @@
/**
* @file web/constant/bbs.ts
* @description 常量-应用数据
* @since Beta v0.6.7
* @since Beta v0.6.8
*/
export const BBS_VERSION: Readonly<string> = "2.79.1";
export const BBS_VERSION: Readonly<string> = "2.80.1";
export const BBS_UA_MOBILE: Readonly<string> = `Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/${BBS_VERSION}`;
/**

View File

@@ -1,15 +1,18 @@
/**
* @file web/request/passportReq.ts
* @description Passport 相关请求
* @since Beta v0.6.3
* @since Beta v0.6.8
*/
import TGHttp from "@/utils/TGHttp.js";
import { getDeviceInfo } from "@/utils/toolFunc.js";
import { getRequestHeader } from "@/web/utils/getRequestHeader.js";
// PassportApiBaseUrl => pAbu
const pAbu: Readonly<string> = "https://passport-api.mihoyo.com/";
// PassportV4ApiBaseUrl => p4Abu
const p4Abu: Readonly<string> = "https://passport-api-v4.mihoyo.com/";
// HoyoLauncherVersion
const hlv: Readonly<string> = "1.3.3.182";
/**
* @description 获取登录ticket
@@ -40,6 +43,30 @@ async function createAuthTicketByGameBiz(
return resp.data.ticket;
}
/**
* @description 创建登录二维码
* @since Beta v0.6.8
* @returns {Promise<TGApp.BBS.Response.Base|TGApp.BBS.GameLogin.GetLoginQrData>}
*/
async function createQrLogin(): Promise<
TGApp.BBS.Response.Base | TGApp.BBS.GameLogin.GetLoginQrData
> {
const resp = await TGHttp<TGApp.BBS.GameLogin.GetLoginQrResponse>(
`${pAbu}account/ma-cn-passport/app/createQRLogin`,
{
method: "POST",
headers: {
"x-rpc-device_id": getDeviceInfo("device_id"),
"user-agent": `HYPContainer/${hlv}`,
"x-rpc-app_id": "ddxf5dufpuyo",
"x-rpc-client_type": "3",
},
},
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
/**
* @description 根据 stoken 获取 cookie_token
* @since Beta v0.6.3
@@ -78,6 +105,32 @@ async function getLTokenBySToken(
return resp.data.ltoken;
}
/**
* @description 获取登录状态
* @since Beta v0.6.8
* @param {string} ticket - 二维码 ticket
* @returns {Promise<TGApp.BBS.Response.Base|TGApp.BBS.GameLogin.GetLoginStatusData>}
*/
async function queryLoginStatus(
ticket: string,
): Promise<TGApp.BBS.Response.Base | TGApp.BBS.GameLogin.GetLoginStatusData> {
const resp = await TGHttp<TGApp.BBS.GameLogin.GetLoginStatusResponse>(
`${pAbu}account/ma-cn-passport/app/queryQRLoginStatus`,
{
method: "POST",
headers: {
"x-rpc-device_id": getDeviceInfo("device_id"),
"user-agent": `HYPContainer/${hlv}`,
"x-rpc-app_id": "ddxf5dufpuyo",
"x-rpc-client_type": "3",
},
body: JSON.stringify({ ticket }),
},
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
/**
* @description 验证 ltoken 有效性,返回 mid
* @since Beta v0.6.5
@@ -102,6 +155,7 @@ const PassportApi = {
authTicket: createAuthTicketByGameBiz,
cookieToken: getCookieAccountInfoBySToken,
lToken: { get: getLTokenBySToken, verify: verifyLToken },
qrLogin: { create: createQrLogin, query: queryLoginStatus },
};
export default PassportApi;

View File

@@ -1,7 +1,7 @@
/**
* @file web/utils/getRequestHeader.ts
* @description 获取请求头
* @since Beta v0.6.7
* @since Beta v0.6.8
*/
import Md5 from "js-md5";
@@ -24,12 +24,12 @@ const enum SaltType {
/**
* @description salt 值
* @version 2.79.1
* @version 2.80.1
* @since Beta v0.6.7
*/
const Salt: Readonly<Record<keyof typeof SaltType, string>> = {
K2: "eOzxpHXVGBVdFBtkbkTvwyCgRpqRFeBr",
LK2: "AbuxbruiFDIgxLXksUNMAMvDyciznofM",
K2: "G1rXOpMLQS77VPWEGycOSxekCTZe2Q8M",
LK2: "sd1e1gQJuvqBfZxas1oeAACXzbim5cge",
X4: "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
X6: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
PROD: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",