UNPKG

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