UNPKG

@yeci226/hoyoapi

Version:

HoYoAPI is an unofficial API Wrapper library developed to facilitate communication with the official HoYoLab API.

321 lines (320 loc) 11.3 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var auth_exports = {}; __export(auth_exports, { AuthClient: () => AuthClient }); module.exports = __toCommonJS(auth_exports); var import_crypto = require("crypto"); var import_routes = require("../../routes"); var import_auth = require("./auth.helper"); class AuthClient { /** * Login to HoyoVerse by account/password securely to fetch stoken_v2. * * @async * @param {ILoginByPasswordOptions} options - Contains account, password and optional aigis headers * @returns {Promise<IAuthLoginResult>} The object dictating the authentication progress */ async loginByPassword(options) { var _a, _b; const payload = { account: (0, import_auth.encryptCredentials)(options.account), password: (0, import_auth.encryptCredentials)(options.password) }; const deviceId = (_a = options.deviceId) != null ? _a : (0, import_crypto.randomUUID)().replace(/-/g, ""); const headers = { "x-rpc-app_id": "c9oqaq3s3gu8", "x-rpc-client_type": "2", // Indicates Mobile App "x-rpc-aigis_v4": "true", "x-rpc-app_version": "4.8.0", "x-rpc-sdk_version": "2.2.0", "x-rpc-device_id": deviceId, ds: (0, import_auth.generateAppLoginDS)(payload), "Content-Type": "application/json" }; if (options.aigisHeaderObject) { headers["x-rpc-aigis"] = options.aigisHeaderObject; } try { const response = await fetch(import_routes.APP_LOGIN_URL, { method: "POST", headers, body: JSON.stringify(payload) }); const responseText = await response.text(); let responseData; try { responseData = JSON.parse(responseText); } catch (e) { throw new Error("Failed to parse API response as JSON: ".concat(e.message)); } const retcode = responseData.retcode; if (retcode === -3101) { const rawAigis = response.headers.get("x-rpc-aigis"); let aigisData = void 0; if (rawAigis) { try { if (rawAigis.trimStart().startsWith("{")) { aigisData = JSON.parse(rawAigis); } else { const split = rawAigis.split(";"); if (split.length > 1) { const decoded = Buffer.from(split[1], "base64").toString("utf8"); aigisData = JSON.parse(decoded); } } } catch (e) { } } const tempCookie = response.headers.get("set-cookie") || void 0; console.log("[Geetest Debug] set-cookie:", tempCookie); let parsedAigisData = void 0; if (aigisData) { const inner = typeof aigisData.data === "string" ? (() => { try { return JSON.parse(aigisData.data); } catch (e) { return {}; } })() : (_b = aigisData.data) != null ? _b : {}; parsedAigisData = { ...inner, session_id: aigisData.session_id, mmt_type: aigisData.mmt_type }; } return { status: "require_geetest", retcode, aigis_header: rawAigis || void 0, aigis_data: parsedAigisData, temp_cookie: tempCookie, deviceId, message: "Geetest Captcha is required to proceed with login." }; } if (retcode === 0 && responseData.data) { const data = responseData.data; const tokenData = data.token ? data.token.token : null; const uid = data.user_info ? data.user_info.aid : null; const mid = data.user_info ? data.user_info.mid : null; if (!tokenData || !uid || !mid) { return { status: "error", retcode, message: "Failed to extract vital token info from response" }; } const exchanged = await this.exchangeTokens(tokenData, mid); const ltokenV2 = (exchanged == null ? void 0 : exchanged.ltokenV2) || ""; const cookieTokenV2 = (exchanged == null ? void 0 : exchanged.cookieTokenV2) || ""; const cookieString = "stoken_v2=".concat(tokenData, "; mid=").concat(mid, "; ltuid_v2=").concat(uid, "; account_id_v2=").concat(uid, "; account_mid_v2=").concat(mid, "; ltoken_v2=").concat(ltokenV2, "; cookie_token_v2=").concat(cookieTokenV2, ";"); return { status: "success", retcode, stokenV2: tokenData, ltokenV2, cookieTokenV2, cookies: cookieString }; } if (retcode === -3239) { const rawVerify = response.headers.get("x-rpc-verify"); console.log("[Auth Debug] x-rpc-verify raw:", rawVerify); let actionTicket = void 0; if (rawVerify) { try { const outer = JSON.parse(rawVerify); const verifyStr = typeof outer.verify_str === "string" ? JSON.parse(outer.verify_str) : outer.verify_str; actionTicket = { ...verifyStr, risk_ticket: outer.risk_ticket }; } catch (e) { console.log("[Auth Debug] x-rpc-verify parse error:", e); actionTicket = { raw: rawVerify }; } } console.log("[Auth Debug] actionTicket:", JSON.stringify(actionTicket)); return { status: "require_email_verify", retcode, deviceId, action_ticket: actionTicket, message: responseData.message || "Email verification required." }; } if (retcode === -3006) { return { status: "rate_limited", retcode, message: responseData.message || "Too many requests. Please try again later." }; } console.log( "[Auth Debug] Login failed. retcode:", retcode, "message:", responseData.message, "data:", JSON.stringify(responseData.data) ); return { status: "error", retcode, message: responseData.message || "Unknown or unsupported retcode encountered." }; } catch (e) { return { status: "error", message: (e == null ? void 0 : e.message) || "A network or system error occurred during authentication." }; } } /** * Send email verification code for a new device login. */ async sendVerificationCode(actionTicket, deviceId) { var _a, _b; const headers = { "x-rpc-app_id": "c9oqaq3s3gu8", "x-rpc-client_type": "2", "x-rpc-app_version": "4.8.0", "x-rpc-sdk_version": "2.2.0", "x-rpc-device_id": deviceId, "Content-Type": "application/json" }; const payload = { action_type: "verify_for_component", action_ticket: (_b = (_a = actionTicket.ticket) != null ? _a : actionTicket.raw) != null ? _b : actionTicket }; headers["ds"] = (0, import_auth.generateAppLoginDS)(payload); console.log( "[Auth Debug] sendVerificationCode payload:", JSON.stringify(payload) ); try { const response = await fetch(import_routes.SEND_VERIFICATION_CODE_URL, { method: "POST", headers, body: JSON.stringify(payload) }); const text = await response.text(); console.log("[Auth Debug] sendVerificationCode response text:", text); try { const data = JSON.parse(text); if (data.retcode === 0) return { status: "sent" }; if (data.retcode === -3006) return { status: "rate_limited", message: data.message }; if (data.retcode !== void 0) return { status: "error", message: data.message }; } catch (e) { } if (response.ok) return { status: "sent" }; return { status: "error", message: "HTTP ".concat(response.status) }; } catch (e) { return { status: "error", message: e == null ? void 0 : e.message }; } } /** * Verify email code to complete new device login. */ async verifyActionTicket(actionTicket, code, deviceId) { var _a; const headers = { "x-rpc-app_id": "c9oqaq3s3gu8", "x-rpc-client_type": "2", "x-rpc-app_version": "4.8.0", "x-rpc-sdk_version": "2.2.0", "x-rpc-device_id": deviceId, "Content-Type": "application/json" }; const payload = { action_type: "verify_for_component", action_ticket: (_a = actionTicket.ticket) != null ? _a : actionTicket, email_captcha: code, verify_method: 2 }; headers["ds"] = (0, import_auth.generateAppLoginDS)(payload); try { const response = await fetch(import_routes.VERIFY_ACTION_TICKET_URL, { method: "POST", headers, body: JSON.stringify(payload) }); const data = await response.json(); if (data.retcode === 0) return { status: "success" }; return { status: "error", message: data.message }; } catch (e) { return { status: "error", message: e == null ? void 0 : e.message }; } } /** * Exchange stoken_v2 to ltoken_v2 and cookie_token_v2. * * @async * @param {string} stokenV2 - The stoken_v2 value. * @param {string} mid - The mid value. * @returns {Promise<{ ltokenV2: string, cookieTokenV2: string } | null>} */ async exchangeTokens(stokenV2, mid) { const payload = { dst_token_types: [2, 4] }; const headers = { "x-rpc-app_id": "c9oqaq3s3gu8", "x-rpc-client_type": "2", "x-rpc-app_version": "4.8.0", "x-rpc-sdk_version": "2.2.0", "x-rpc-device_id": (0, import_crypto.randomUUID)().replace(/-/g, ""), ds: (0, import_auth.generateAppLoginDS)(payload), "Content-Type": "application/json", Cookie: "stoken_v2=".concat(stokenV2, "; mid=").concat(mid, ";") }; try { const response = await fetch(import_routes.APP_TOKEN_EXCHANGE_URL, { method: "POST", headers, body: JSON.stringify(payload) }); const responseData = await response.json(); if (responseData.retcode === 0 && responseData.data && responseData.data.list) { const list = responseData.data.list; let ltokenV2 = ""; let cookieTokenV2 = ""; for (const item of list) { if (item.token_type === 2) { ltokenV2 = item.token; } else if (item.token_type === 4) { cookieTokenV2 = item.token; } } return { ltokenV2, cookieTokenV2 }; } } catch (e) { } return null; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AuthClient });