@yeci226/hoyoapi
Version:
HoYoAPI is an unofficial API Wrapper library developed to facilitate communication with the official HoYoLab API.
1,124 lines (1,115 loc) • 34.8 kB
JavaScript
import {
DiaryEnum,
DiaryMonthEnum,
GenshinDiaryModule,
GenshinImpact,
GenshinRecordModule,
GenshinRegion,
GenshinTCGModule,
SpiralAbyssScheduleEnum,
getGenshinRegion
} from "./chunk-KH46XCBK.mjs";
import {
HIRecordModule,
HonkaiImpact,
HonkaiRegion,
getHi3Region
} from "./chunk-KUISQ4G3.mjs";
import {
ForgottenHallModeEnum,
ForgottenHallScheduleEnum,
HSRDiaryEnum,
HSRDiaryModule,
HSRDiaryMonthEnum,
HSRRecordModule,
HonkaiStarRail,
HsrRegion,
MimoModule,
WikiModule,
getHsrRegion
} from "./chunk-PP7SIENP.mjs";
import {
DailyModule,
RedeemModule
} from "./chunk-TB4Z5SOA.mjs";
import {
ACCOUNT_API,
APP_LOGIN_URL,
APP_TOKEN_EXCHANGE_URL,
BBS_API,
Cookie,
DAILY_CLAIM_API,
DAILY_INFO_API,
DAILY_REWARD_API,
DEFAULT_REFERER,
GAME_RECORD_CARD_API,
GENSHIN_DIARY_DETAIL_API,
GENSHIN_DIARY_LIST_API,
GENSHIN_RECORD_AVATAR_BASIC_INFO_API,
GENSHIN_RECORD_CHARACTER_API,
GENSHIN_RECORD_DAILY_NOTE_API,
GENSHIN_RECORD_INDEX_API,
GENSHIN_RECORD_SPIRAL_ABYSS_API,
GENSHIN_REDEEM_CLAIM_API,
GENSHIN_TCG_BASICINFO,
GENSHIN_TCG_CARDLIST,
GENSHIN_TCG_CHALLANGE_DECK,
GENSHIN_TCG_CHALLANGE_RECORD,
GENSHIN_TCG_CHALLANGE_SCHEDULE,
GENSHIN_TCG_MATCHLIST,
GamesEnum,
HI_RECORD_ABYSS_API,
HI_RECORD_ARENA_API,
HI_RECORD_CHARACTER_API,
HI_RECORD_ELYSIAN_API,
HI_RECORD_INDEX_API,
HI_REDEEM_CLAIM_API,
HK4E_API,
HKRPG_API,
HSR_DIARY_DETAIL_API,
HSR_DIARY_LIST_API,
HSR_RECORD_ANOMALY_ARBITRATION_API,
HSR_RECORD_APOCALYPSE_PHANTOM_API,
HSR_RECORD_CHARACTER_API,
HSR_RECORD_FORGOTTEN_HALL_API,
HSR_RECORD_INDEX_API,
HSR_RECORD_NOTE_API,
HSR_RECORD_PURE_FICTION_API,
HSR_RECORD_WIDGET_API,
HSR_REDEEM_CLAIM_API,
HSR_WIKI_ENTRY_API,
HSR_WIKI_ENTRY_LIST_API,
HSR_WIKI_SEARCH_API,
HTTPRequest,
HoyoAPIError,
Hoyolab,
Language,
LanguageEnum,
MIMO_BASE_API,
MIMO_EXCHANGE_API,
MIMO_EXCHANGE_LIST_API,
MIMO_FINISH_TASK_API,
MIMO_INDEX_API,
MIMO_LOTTERY_API,
MIMO_LOTTERY_INFO_API,
MIMO_RECEIVE_POINT_API,
MIMO_TASK_LIST_API,
NAP_API,
PUBLIC_API,
SEND_VERIFICATION_CODE_URL,
TAKUMI_API,
USER_GAMES_LIST,
VERIFY_ACTION_TICKET_URL,
WIKI_BASE_API,
ZZZ_BANBOO_API,
ZZZ_RECORD_CHARACTER_API,
ZZZ_RECORD_CHARACTER_LIST_API,
ZZZ_RECORD_DEADLY_ASSAULT_API,
ZZZ_RECORD_HADAL_API,
ZZZ_RECORD_INDEX_API,
ZZZ_RECORD_NOTE_API,
ZZZ_RECORD_SHIYU_DEFENSE_API,
ZZZ_REDEEM_CLAIM_API,
ZZZ_WIKI_ENTRY_API,
ZZZ_WIKI_ENTRY_LIST_API,
ZZZ_WIKI_SEARCH_API,
__publicField
} from "./chunk-FMPTW7AC.mjs";
// src/client/zzz/zzz.interface.ts
var ZZZRegion = /* @__PURE__ */ ((ZZZRegion2) => {
ZZZRegion2["USA"] = "prod_gf_us";
ZZZRegion2["EUROPE"] = "prod_gf_eu";
ZZZRegion2["ASIA"] = "prod_gf_jp";
ZZZRegion2["CHINA_TAIWAN"] = "prod_gf_sg";
return ZZZRegion2;
})(ZZZRegion || {});
// src/client/zzz/zzz.helper.ts
function getZZZRegion(uid) {
const server_region = Number(uid.toString().trim().slice(0, 2));
let key;
switch (server_region) {
case 10:
key = "USA";
break;
case 15:
key = "EUROPE";
break;
case 13:
key = "ASIA";
break;
case 17:
key = "CHINA_TAIWAN";
break;
default:
throw new HoyoAPIError("Given UID ".concat(uid, " is invalid !"));
}
return ZZZRegion[key];
}
// src/client/zzz/record/record.enum.ts
var ShiyuDefenseScheduleEnum = /* @__PURE__ */ ((ShiyuDefenseScheduleEnum2) => {
ShiyuDefenseScheduleEnum2[ShiyuDefenseScheduleEnum2["CURRENT"] = 1] = "CURRENT";
ShiyuDefenseScheduleEnum2[ShiyuDefenseScheduleEnum2["PREVIOUS"] = 2] = "PREVIOUS";
return ShiyuDefenseScheduleEnum2;
})(ShiyuDefenseScheduleEnum || {});
var DeadlyAssaultScheduleEnum = /* @__PURE__ */ ((DeadlyAssaultScheduleEnum2) => {
DeadlyAssaultScheduleEnum2[DeadlyAssaultScheduleEnum2["CURRENT"] = 1] = "CURRENT";
DeadlyAssaultScheduleEnum2[DeadlyAssaultScheduleEnum2["PREVIOUS"] = 2] = "PREVIOUS";
return DeadlyAssaultScheduleEnum2;
})(DeadlyAssaultScheduleEnum || {});
var HadalScheduleEnum = /* @__PURE__ */ ((HadalScheduleEnum2) => {
HadalScheduleEnum2[HadalScheduleEnum2["CURRENT"] = 1] = "CURRENT";
HadalScheduleEnum2[HadalScheduleEnum2["PREVIOUS"] = 2] = "PREVIOUS";
return HadalScheduleEnum2;
})(HadalScheduleEnum || {});
// src/client/zzz/record/record.ts
var ZZZRecordModule = class {
/**
* Creates an instance of ZZZRecordModule.
*
* @param request The HTTPRequest object used for making API requests.
* @param lang The language enum value.
* @param region The region string or null if not provided.
* @param uid The UID number or null if not provided.
*/
constructor(request, lang, region, uid) {
this.request = request;
this.lang = lang;
this.region = region;
this.uid = uid;
}
/**
* Retrieves the characters associated with the provided region and UID.
*
* @returns {Promise<IZZZCharacterFull[]>} A Promise that resolves to an array of full ZZZ characters.
* @throws {HoyoAPIError} if the region or UID parameters are missing or failed to be filled.
* @throws {HoyoAPIError} if failed to retrieve data, please double-check the provided UID.
*/
async characters() {
var _a;
if (!this.region || !this.uid) {
throw new HoyoAPIError("UID parameter is missing or failed to be filled");
}
this.request.setQueryParams({
server: this.region,
role_id: this.uid,
lang: this.lang
}).setDs(true);
const {
response: res,
body,
params,
headers
} = await this.request.send(ZZZ_RECORD_CHARACTER_LIST_API);
if (res.retcode !== 0) {
throw new HoyoAPIError(
(_a = res.message) != null ? _a : "Failed to retrieve data, please double-check the provided UID.",
res.retcode,
{
response: res,
request: {
body,
headers,
params
}
}
);
}
const data = res.data;
return data.avatar_list;
}
/**
* Retrieves the character associated with the provided region and UID.
*
* @param characterId Character ID to retrieve.
* @returns {Promise<IZZZCharacterFull>} A Promise that resolves to a ZZZ character.
* @throws {HoyoAPIError} if the region or UID parameters are missing or failed to be filled.
* @throws {HoyoAPIError} if the character ID parameter is missing or failed to be filled.
* @throws {HoyoAPIError} if failed to retrieve data, please double-check the provided UID.
*/
async character(characterId) {
var _a;
if (!this.region || !this.uid) {
throw new HoyoAPIError("UID parameter is missing or failed to be filled");
}
if (!characterId) {
throw new HoyoAPIError(
"Character ID parameter is missing or failed to be filled"
);
}
this.request.setQueryParams({
server: this.region,
role_id: this.uid,
lang: this.lang
}).setDs(true);
const {
response: res,
body,
params,
headers
} = await this.request.send(
ZZZ_RECORD_CHARACTER_API + "?id_list[]=" + characterId
);
if (res.retcode !== 0) {
throw new HoyoAPIError(
(_a = res.message) != null ? _a : "Failed to retrieve data, please double-check the provided UID.",
res.retcode,
{
response: res,
request: {
body,
headers,
params
}
}
);
}
const data = res.data;
return data.avatar_list;
}
/**
* Retrieves the records associated with the provided region and UID.
*
* @returns {Promise<IZZZRecord>} A Promise that resolves to the ZZZ record object.
* @throws {HoyoAPIError} if the region or UID parameters are missing or failed to be filled.
* @throws {HoyoAPIError} if failed to retrieve data, please double-check the provided UID.
*/
async records() {
var _a;
if (!this.region || !this.uid) {
throw new HoyoAPIError("UID parameter is missing or failed to be filled");
}
this.request.setQueryParams({
server: this.region,
role_id: this.uid,
lang: this.lang
}).setDs(true);
const {
response: res,
body,
params,
headers
} = await this.request.send(ZZZ_RECORD_INDEX_API);
if (res.retcode !== 0) {
throw new HoyoAPIError(
(_a = res.message) != null ? _a : "Failed to retrieve data, please double-check the provided UID.",
res.retcode,
{
response: res,
request: {
body,
headers,
params
}
}
);
}
return res.data;
}
/**
* Retrieves the note associated with the provided region and UID.
*
* @returns {Promise<IZZZNote>} A Promise that resolves to the ZZZ note object.
* @throws {HoyoAPIError} if the region or UID parameters are missing or failed to be filled.
* @throws {HoyoAPIError} if failed to retrieve data, please double-check the provided UID.
*/
async note() {
var _a;
if (!this.region || !this.uid) {
throw new HoyoAPIError("UID parameter is missing or failed to be filled");
}
this.request.setQueryParams({
server: this.region,
role_id: this.uid,
lang: this.lang
}).setDs(true);
const {
response: res,
body,
params,
headers
} = await this.request.send(ZZZ_RECORD_NOTE_API);
if (res.retcode !== 0) {
throw new HoyoAPIError(
(_a = res.message) != null ? _a : "Failed to retrieve data, please double-check the provided UID.",
res.retcode,
{
response: res,
request: {
body,
headers,
params
}
}
);
}
return res.data;
}
/**
* Retrieves the shiyu defense information associated with the provided region and UID.
*
* @param scheduleType The schedule type for the shiyu defense (optional, defaults to CURRENT).
* @returns {Promise<IZZZShiyuDefense>} A Promise that resolves to the shiyu defense information object.
* @throws {HoyoAPIError} if the region or UID parameters are missing or failed to be filled.
* @throws {HoyoAPIError} if the given scheduleType parameter is invalid.
* @throws {HoyoAPIError} if failed to retrieve data, please double-check the provided UID.
*/
async shiyuDefense(scheduleType = 1 /* CURRENT */) {
var _a;
if (!this.region || !this.uid) {
throw new HoyoAPIError("UID parameter is missing or failed to be filled");
}
if (Object.values(ShiyuDefenseScheduleEnum).includes(scheduleType) === false) {
throw new HoyoAPIError("The given scheduleType parameter is invalid !");
}
this.request.setQueryParams({
server: this.region,
role_id: this.uid,
schedule_type: scheduleType,
lang: this.lang,
need_all: "true"
}).setDs();
const {
response: res,
body,
params,
headers
} = await this.request.send(ZZZ_RECORD_SHIYU_DEFENSE_API);
if (res.retcode !== 0) {
throw new HoyoAPIError(
(_a = res.message) != null ? _a : "Failed to retrieve data, please double-check the provided UID.",
res.retcode,
{
response: res,
request: {
body,
headers,
params
}
}
);
}
return res.data;
}
/**
* Retrieves the deadly assault information associated with the provided region and UID.
*
* @param scheduleType The schedule type for the deadly assault (optional, defaults to CURRENT).
* @returns {Promise<IZZZDeadlyAssault>} A Promise that resolves to the deadly assault information object.
* @throws {HoyoAPIError} if the region or UID parameters are missing or failed to be filled.
* @throws {HoyoAPIError} if the given scheduleType parameter is invalid.
* @throws {HoyoAPIError} if failed to retrieve data, please double-check the provided UID.
*/
async deadlyAssault(scheduleType = 1 /* CURRENT */) {
var _a;
if (!this.region || !this.uid) {
throw new HoyoAPIError("UID parameter is missing or failed to be filled");
}
if (Object.values(DeadlyAssaultScheduleEnum).includes(scheduleType) === false) {
throw new HoyoAPIError("The given scheduleType parameter is invalid !");
}
this.request.setQueryParams({
region: this.region,
uid: this.uid,
schedule_type: scheduleType,
lang: this.lang,
need_all: "true"
}).setDs();
const {
response: res,
body,
params,
headers
} = await this.request.send(ZZZ_RECORD_DEADLY_ASSAULT_API);
if (res.retcode !== 0) {
throw new HoyoAPIError(
(_a = res.message) != null ? _a : "Failed to retrieve data, please double-check the provided UID.",
res.retcode,
{
response: res,
request: {
body,
headers,
params
}
}
);
}
return res.data;
}
/**
* Retrieves the hadal information associated with the provided region and UID.
*
* @param scheduleType The schedule type for the hadal (optional, defaults to CURRENT).
* @returns {Promise<IZZZHadal>} A Promise that resolves to the hadal information object.
* @throws {HoyoAPIError} if the region or UID parameters are missing or failed to be filled.
* @throws {HoyoAPIError} if the given scheduleType parameter is invalid.
* @throws {HoyoAPIError} if failed to retrieve data, please double-check the provided UID.
*/
async hadalInfo(scheduleType = 1 /* CURRENT */) {
var _a;
if (!this.region || !this.uid) {
throw new HoyoAPIError("UID parameter is missing or failed to be filled");
}
if (Object.values(HadalScheduleEnum).includes(scheduleType) === false) {
throw new HoyoAPIError("The given scheduleType parameter is invalid !");
}
this.request.setQueryParams({
server: this.region,
role_id: this.uid,
schedule_type: scheduleType,
lang: this.lang,
need_all: "true"
}).setDs();
const {
response: res,
body,
params,
headers
} = await this.request.send(ZZZ_RECORD_HADAL_API);
if (res.retcode !== 0) {
throw new HoyoAPIError(
(_a = res.message) != null ? _a : "Failed to retrieve data, please double-check the provided UID.",
res.retcode,
{
response: res,
request: {
body,
headers,
params
}
}
);
}
return res.data;
}
};
// src/client/zzz/zzz.ts
var ZenlessZoneZero = class _ZenlessZoneZero {
/**
* Constructs a new `ZZZ` object.
*
* @param options The options object used to configure the object.
*/
constructor(options) {
/**
* The `DailyModule` object provides an interface to interact with the daily check-in feature in Zenless Zone Zero.
*
*/
__publicField(this, "daily");
/**
* The `RedeemModule` object provides an interface to interact with the code redemption feature in Zenless Zone Zero.
*
*/
__publicField(this, "redeem");
/**
* The `ZZZRecordModule` object provides an interface to interact with the user record feature in Honkai Star Rails.
*
*/
__publicField(this, "record");
/**
* The `MimoModule` object provides an interface to interact with the Mimo travel event for ZZZ.
*/
__publicField(this, "mimo");
/**
* The `WikiModule` object provides an interface to search the Hoyolab ZZZ wiki.
*/
__publicField(this, "wiki");
/**
* HoyYolab account object
*
*/
__publicField(this, "_account", null);
/**
* The cookie object to be used in requests.
*/
__publicField(this, "cookie");
/**
* The `Request` object used to make requests.
*/
__publicField(this, "request");
/**
* The UID of the user, if available.
*/
__publicField(this, "uid");
/**
* The region of the user, if available.
*/
__publicField(this, "region");
/**
* The language to be used in requests.
*/
__publicField(this, "lang");
var _a;
const cookie = typeof options.cookie === "string" ? Cookie.parseCookieString(options.cookie) : options.cookie;
this.cookie = cookie;
if (!options.lang) {
options.lang = Language.parseLang(cookie.mi18nLang);
}
options.lang = Language.parseLang(options.lang);
this.request = new HTTPRequest(Cookie.parseCookie(this.cookie));
this.request.setReferer(DEFAULT_REFERER);
this.request.setLang(options.lang);
this.uid = (_a = options.uid) != null ? _a : null;
this.region = this.uid !== null ? getZZZRegion(this.uid) : null;
this.lang = options.lang;
this.daily = new DailyModule(
this.request,
this.lang,
"nap_global" /* ZENLESS_ZONE_ZERO */,
this.region
);
this.redeem = new RedeemModule(
this.request,
this.lang,
"nap_global" /* ZENLESS_ZONE_ZERO */,
this.region,
this.uid
);
this.record = new ZZZRecordModule(
this.request,
this.lang,
this.region,
this.uid
);
this.mimo = new MimoModule(this.request, this.lang, 8);
this.wiki = new WikiModule(this.request, this.lang, "zzz");
}
/**
* Create a new instance of the ZenlessZoneZero class asynchronously.
*
* @param options The options object used to configure the object.
* @throws {HoyoAPIError} Error Wnen the CookieTokenV2 is not set.
* @returns {Promise<ZenLessZoneZero>} A promise that resolves with a new ZZZ instance.
*
* @remarks
* If an object is instantiated from this method but options.cookie.cookieTokenV2 is not set,
* it will throw an error. This method will access an Endpoint that contains a list of game accounts,
* which requires the cookieTokenV2 option.
* @remarks
* Because CookieTokenV2 has a short expiration time and cannot be refreshed so far.
* It is evident that every few days, when logging in, it always requests authentication first.
* Therefore, this method that uses CookieTokenV2 is not suitable if filled statically.
*/
static async create(options) {
try {
let game = null;
if (typeof options.uid === "undefined") {
const hoyolab = new Hoyolab({
cookie: options.cookie
});
game = await hoyolab.gameAccount("nap_global" /* ZENLESS_ZONE_ZERO */);
options.uid = parseInt(game.game_uid);
}
const zzz = new _ZenlessZoneZero(options);
zzz.account = game;
return zzz;
} catch (error) {
throw new HoyoAPIError(error.message, error.code);
}
}
/**
* Setter for the account property. Prevents from changing the value once set
* @param game The game object to set as the account.
*/
set account(game) {
if (this.account === null && game !== null) {
this._account = game;
}
}
/**
* Getter for the account property.
* @returns {IGame | null} The current value of the account property.
*/
get account() {
return this._account;
}
/**
* Retrieves daily information.
*
* @alias {@link ZenLessZoneZero.daily | ZZZ.daily.info()}
* @deprecated Use through {@link ZenLessZoneZero.daily | ZZZ.daily.info()} instead
*/
dailyInfo() {
return this.daily.info();
}
/**
* Retrieve daily rewards information.
*
* @alias {@link ZenLessZoneZero.daily | ZZZ.daily.rewards()}
* @deprecated Use through {@link ZenLessZoneZero.daily | ZZZ.daily.rewards()} instead
*/
dailyRewards() {
return this.daily.rewards();
}
/**
* Get the daily reward for a specific day or the current day
*
* @param day number | null
* @alias {@link ZenLessZoneZero.daily | ZZZ.daily.reward()}
* @deprecated Use through {@link ZenLessZoneZero.daily | ZZZ.daily.reward()} instead
*/
dailyReward(day = null) {
return this.daily.reward(day);
}
/**
* Claim current reward
*
* @alias {@link ZenLessZoneZero.daily | ZZZ.daily.claim()}
* @deprecated Use through {@link ZenLessZoneZero.daily | ZZZ.daily.claim()} instead
*/
dailyClaim() {
return this.daily.claim();
}
/**
* Redeems a code for a specific account.
*
* @param code string
* @alias {@link ZenLessZoneZero.daily | ZZZ.redeem.claim()}
* @deprecated Use through {@link ZenLessZoneZero.daily | ZZZ.redeem.claim()} instead
*/
redeemCode(code) {
return this.redeem.claim(code);
}
};
// src/client/auth/auth.helper.ts
import * as crypto from "crypto";
var LOGIN_KEY_TYPE_1 = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4PMS2JVMwBsOIrYWRluY\nwEiFZL7Aphtm9z5Eu/anzJ09nB00uhW+ScrDWFECPwpQto/GlOJYCUwVM/raQpAj\n/xvcjK5tNVzzK94mhk+j9RiQ+aWHaTXmOgurhxSp3YbwlRDvOgcq5yPiTz0+kSeK\nZJcGeJ95bvJ+hJ/UMP0Zx2qB5PElZmiKvfiNqVUk8A8oxLJdBB5eCpqWV6CUqDKQ\nKSQP4sM0mZvQ1Sr4UcACVcYgYnCbTZMWhJTWkrNXqI8TMomekgny3y+d6NX/cFa6\n6jozFIF4HCX5aW8bp8C8vq2tFvFbleQ/Q3CU56EWWKMrOcpmFtRmC18s9biZBVR/\n8QIDAQAB\n-----END PUBLIC KEY-----";
function encryptCredentials(text) {
const buffer = Buffer.from(text, "utf8");
const encrypted = crypto.publicEncrypt(
{
key: LOGIN_KEY_TYPE_1,
padding: crypto.constants.RSA_PKCS1_PADDING
},
buffer
);
return encrypted.toString("base64");
}
function generateAppLoginDS(body) {
const salt = "IZPgfb0dRPtBeLuFkdDznSZ6f4wWt6y2";
const t = Math.floor(Date.now() / 1e3);
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let r = "";
for (let i = 0; i < 6; i++) {
r += chars.charAt(Math.floor(Math.random() * chars.length));
}
const b = JSON.stringify(body);
const rawStr = "salt=".concat(salt, "&t=").concat(t, "&r=").concat(r, "&b=").concat(b, "&q=");
const hash = crypto.createHash("md5").update(rawStr).digest("hex");
return "".concat(t, ",").concat(r, ",").concat(hash);
}
function buildAigisHeader(sessionId, mmtData, geetestResult) {
const finalData = {
...mmtData,
...geetestResult
};
const b64Data = Buffer.from(JSON.stringify(finalData)).toString("base64");
return "".concat(sessionId, ";").concat(b64Data);
}
// src/client/auth/auth.ts
import { randomUUID } from "crypto";
var AuthClient = class {
/**
* 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: encryptCredentials(options.account),
password: encryptCredentials(options.password)
};
const deviceId = (_a = options.deviceId) != null ? _a : 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: generateAppLoginDS(payload),
"Content-Type": "application/json"
};
if (options.aigisHeaderObject) {
headers["x-rpc-aigis"] = options.aigisHeaderObject;
}
try {
const response = await fetch(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"] = generateAppLoginDS(payload);
console.log(
"[Auth Debug] sendVerificationCode payload:",
JSON.stringify(payload)
);
try {
const response = await fetch(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"] = generateAppLoginDS(payload);
try {
const response = await fetch(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": randomUUID().replace(/-/g, ""),
ds: generateAppLoginDS(payload),
"Content-Type": "application/json",
Cookie: "stoken_v2=".concat(stokenV2, "; mid=").concat(mid, ";")
};
try {
const response = await fetch(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;
}
};
export {
ACCOUNT_API,
APP_LOGIN_URL,
APP_TOKEN_EXCHANGE_URL,
AuthClient,
BBS_API,
Cookie,
DAILY_CLAIM_API,
DAILY_INFO_API,
DAILY_REWARD_API,
DEFAULT_REFERER,
DailyModule,
DiaryEnum,
DiaryMonthEnum,
ForgottenHallModeEnum,
ForgottenHallScheduleEnum,
GAME_RECORD_CARD_API,
GENSHIN_DIARY_DETAIL_API,
GENSHIN_DIARY_LIST_API,
GENSHIN_RECORD_AVATAR_BASIC_INFO_API,
GENSHIN_RECORD_CHARACTER_API,
GENSHIN_RECORD_DAILY_NOTE_API,
GENSHIN_RECORD_INDEX_API,
GENSHIN_RECORD_SPIRAL_ABYSS_API,
GENSHIN_REDEEM_CLAIM_API,
GENSHIN_TCG_BASICINFO,
GENSHIN_TCG_CARDLIST,
GENSHIN_TCG_CHALLANGE_DECK,
GENSHIN_TCG_CHALLANGE_RECORD,
GENSHIN_TCG_CHALLANGE_SCHEDULE,
GENSHIN_TCG_MATCHLIST,
GamesEnum,
GenshinDiaryModule,
GenshinImpact,
GenshinRecordModule,
GenshinRegion,
GenshinTCGModule,
HIRecordModule,
HI_RECORD_ABYSS_API,
HI_RECORD_ARENA_API,
HI_RECORD_CHARACTER_API,
HI_RECORD_ELYSIAN_API,
HI_RECORD_INDEX_API,
HI_REDEEM_CLAIM_API,
HK4E_API,
HKRPG_API,
HSRDiaryEnum,
HSRDiaryModule,
HSRDiaryMonthEnum,
HSRRecordModule,
HSR_DIARY_DETAIL_API,
HSR_DIARY_LIST_API,
HSR_RECORD_ANOMALY_ARBITRATION_API,
HSR_RECORD_APOCALYPSE_PHANTOM_API,
HSR_RECORD_CHARACTER_API,
HSR_RECORD_FORGOTTEN_HALL_API,
HSR_RECORD_INDEX_API,
HSR_RECORD_NOTE_API,
HSR_RECORD_PURE_FICTION_API,
HSR_RECORD_WIDGET_API,
HSR_REDEEM_CLAIM_API,
HSR_WIKI_ENTRY_API,
HSR_WIKI_ENTRY_LIST_API,
HSR_WIKI_SEARCH_API,
HTTPRequest,
HonkaiImpact,
HonkaiRegion,
HonkaiStarRail,
HoyoAPIError,
Hoyolab,
HsrRegion,
Language,
LanguageEnum,
MIMO_BASE_API,
MIMO_EXCHANGE_API,
MIMO_EXCHANGE_LIST_API,
MIMO_FINISH_TASK_API,
MIMO_INDEX_API,
MIMO_LOTTERY_API,
MIMO_LOTTERY_INFO_API,
MIMO_RECEIVE_POINT_API,
MIMO_TASK_LIST_API,
MimoModule,
NAP_API,
PUBLIC_API,
RedeemModule,
SEND_VERIFICATION_CODE_URL,
SpiralAbyssScheduleEnum,
TAKUMI_API,
USER_GAMES_LIST,
VERIFY_ACTION_TICKET_URL,
WIKI_BASE_API,
WikiModule,
ZZZRegion,
ZZZ_BANBOO_API,
ZZZ_RECORD_CHARACTER_API,
ZZZ_RECORD_CHARACTER_LIST_API,
ZZZ_RECORD_DEADLY_ASSAULT_API,
ZZZ_RECORD_HADAL_API,
ZZZ_RECORD_INDEX_API,
ZZZ_RECORD_NOTE_API,
ZZZ_RECORD_SHIYU_DEFENSE_API,
ZZZ_REDEEM_CLAIM_API,
ZZZ_WIKI_ENTRY_API,
ZZZ_WIKI_ENTRY_LIST_API,
ZZZ_WIKI_SEARCH_API,
ZenlessZoneZero,
buildAigisHeader,
encryptCredentials,
generateAppLoginDS,
getGenshinRegion,
getHi3Region,
getHsrRegion,
getZZZRegion
};