@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
JavaScript
;
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
});