🌱 完成登录请求

#202
This commit is contained in:
BTMuli
2026-01-04 03:38:46 +08:00
parent 1fa1f2b780
commit edb7088846
8 changed files with 195 additions and 13 deletions

View File

@@ -96,6 +96,7 @@
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"qrcode.vue": "^3.6.0",
"rsa-oaep-encryption": "^1.1.0",
"sass-embedded": "^1.97.1",
"swiper": "^12.0.3",
"uuid": "^13.0.0",

8
pnpm-lock.yaml generated
View File

@@ -86,6 +86,9 @@ importers:
qrcode.vue:
specifier: ^3.6.0
version: 3.6.0(vue@3.5.26(typescript@5.9.3))
rsa-oaep-encryption:
specifier: ^1.1.0
version: 1.1.0
sass-embedded:
specifier: ^1.97.1
version: 1.97.1
@@ -3576,6 +3579,9 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
rsa-oaep-encryption@1.1.0:
resolution: {integrity: sha512-GzbbyA//t8Safvj2k4KrGjuA1iHHrvCqkjCG49aOO9tdMV+CG0nNjuUNqRUibz5QoO851u24n/wgbSMi6ZZ01w==}
run-applescript@7.1.0:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
@@ -7864,6 +7870,8 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.53
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.53
rsa-oaep-encryption@1.1.0: {}
run-applescript@7.1.0: {}
run-parallel@1.2.0:

View File

@@ -31,6 +31,8 @@
"http:allow-fetch",
"log:default",
"log:allow-log",
"machine-uid:default",
"machine-uid:allow-get-machine-uid",
"notification:default",
"opener:default",
"process:default",

View File

@@ -19,22 +19,18 @@
</div>
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showSnackbar from "@comp/func/snackbar.js";
import {
isPermissionGranted,
requestPermission,
sendNotification,
} from "@tauri-apps/plugin-notification";
import hutao from "@Hutao/index.js";
async function test() {
const check = await isPermissionGranted();
console.log(check);
if (!check) {
showSnackbar.warn("没有通知权限");
const permission = await requestPermission();
console.log(permission);
const inputN = await showDialog.input("UserName");
const inputP = await showDialog.input("Pwd");
if (!inputN || !inputP) return;
const resp = await hutao.Account.login(inputN, inputP);
if ("retcode" in resp) {
showSnackbar.warn(`${resp.retcode}-${resp.message}`);
}
sendNotification({ title: "New Message", body: "You have a new message" });
}
</script>
<style lang="css" scoped>

View File

@@ -1,6 +1,6 @@
/**
* Hutao 插件入口
* @since Beta v0.6.3
* @since Beta v0.9.1
*/
import {
@@ -12,10 +12,13 @@ import {
getTeamCollect,
uploadAbyssData,
} from "./request/abyssReq.js";
import { loginPassport } from "./request/accountReq.js";
import { getCombatStatistic, uploadCombatData } from "./request/combatReq.js";
import { transAbyssAvatars, transAbyssLocal } from "./utils/abyssUtil.js";
import { transCombatLocal } from "./utils/combatUtil.js";
const _ = "Not Implemented";
const Hutao = {
Abyss: {
avatar: {
@@ -37,6 +40,27 @@ const Hutao = {
data: getCombatStatistic,
trans: transCombatLocal,
},
Account: {
register: _,
login: loginPassport,
verify: _,
cancel: _,
reset: {
username: _,
password: _,
},
token: {
refresh: _,
revoke: _,
revokeAll: _,
},
info: _,
},
Gacha: {
log: _,
upload: _,
delete: _,
},
};
export default Hutao;

View File

@@ -0,0 +1,66 @@
/**
* 账号相关请求
* @since Beta v0.9.1
*/
import TGHttp from "@utils/TGHttp.js";
import { importPublicKey, sha1 } from "rsa-oaep-encryption";
import { getReqHeader } from "../utils/authUtils.js";
/** 加密公钥 */
const HUTAO_PUB_KEY: Readonly<string> = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5W2SEyZSlP2zBI1Sn8Gd
TwbZoXlUGNKyoVrY8SVYu9GMefdGZCrUQNkCG/Np8pWPmSSEFGd5oeug/oIMtCZQ
NOn0drlR+pul/XZ1KQhKmj/arWjN1XNok2qXF7uxhqD0JyNT/Fxy6QvzqIpBsM9S
7ajm8/BOGlPG1SInDPaqTdTRTT30AuN+IhWEEFwT3Ctv1SmDupHs2Oan5qM7Y3uw
b6K1rbnk5YokiV2FzHajGUymmSKXqtG1USZzwPqImpYb4Z0M/StPFWdsKqexBqMM
mkXckI5O98GdlszEmQ0Ejv5Fx9fR2rXRwM76S4iZTfabYpiMbb4bM42mHMauupj6
9QIDAQAB
-----END PUBLIC KEY-----`;
const encrypt = importPublicKey(HUTAO_PUB_KEY);
const PassportUrl = "https://homa.gentle.house/Passport/v2/";
/**
* rsa 加密
* @since Beta v0.9.1
* @param data - 待加密数据
* @returns 加密后数据
*/
function rsaEncrypt(data: string): string {
const res = encrypt.encrypt(data, sha1.create());
const bytes = new Uint8Array(res);
let binary = "";
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
/**
* 登录
* @since Beta v0.9.1
* @param username - 用户名(邮箱)
* @param password - 密码
* @returns
*/
export async function loginPassport(
username: string,
password: string,
): Promise<TGApp.Plugins.Hutao.Account.LoginRes | TGApp.Plugins.Hutao.Base.Resp> {
const url = `${PassportUrl}Login`;
const data = {
UserName: rsaEncrypt(username),
Password: rsaEncrypt(password),
};
const header = await getReqHeader();
const resp = await TGHttp<TGApp.Plugins.Hutao.Account.LoginResp>(url, {
method: "POST",
headers: header,
body: JSON.stringify(data),
});
if (resp.retcode !== 0) return <TGApp.Plugins.Hutao.Base.Resp>resp;
return resp.data;
}

58
src/plugins/Hutao/types/Account.d.ts vendored Normal file
View File

@@ -0,0 +1,58 @@
/**
* 账号请求相关类型
* @since Beta v0.9.1
*/
declare namespace TGApp.Plugins.Hutao.Account {
/**
* Passport 请求参数
* @since Beta v0.9.1
*/
type PassportReqParams = {
/** 设备ID */
DeviceId: string;
/** 邮箱 */
UserName: string | null;
/**
* 新邮箱
* @remarks 用于账号迁移
*/
NewUserName: string | null;
/** 密码 */
Password: string | null;
/** 邮箱验证码 */
VerifyCode: string | null;
/**
* 新邮箱验证码
* @remarks 用于账号迁移
*/
NewVerifyCode: string | null;
/** 重置密码Flag */
IsResetPassword: boolean;
/** 校验邮箱Flag */
IsResetUserName: boolean;
/** 校验新邮箱Flag */
IsResetNewUserName: boolean;
/** 注销账号Flag */
IsCancelRegistration: boolean;
};
/**
* 登录请求响应
* @since Beta v0.9.1
*/
type LoginResp = TGApp.Plugins.Hutao.Base.Resp<LoginRes>;
/**
* 登录请求返回
* @since Beta v0.9.1
*/
type LoginRes = {
/** token */
AccessToken: string;
/** expire */
ExpiresIn: number;
/** refresh */
RefreshToken: string;
};
}

View File

@@ -0,0 +1,27 @@
/**
* 账号认证相关方法
* @since Beta v0.9.1
*/
import { commands } from "@skipperndt/plugin-machine-uid";
import { getVersion } from "@tauri-apps/api/app";
let DEVICE_ID: string | null = null;
/**
* 获取请求头
* @since Beta v0.9.1
*/
export async function getReqHeader(tk?: string): Promise<Record<string, string>> {
const version = await getVersion();
if (DEVICE_ID === null) {
const deviceRes = await commands.getMachineUid();
if (deviceRes.status === "ok") DEVICE_ID = deviceRes.data.id;
}
const device = DEVICE_ID ?? "";
return {
"Content-Type": "application/json",
"x-hutao-device-id": device,
Authorization: tk ? `Bearer ${tk}` : "",
"User-Agent": `TeyvatGuide/${version}`,
};
}