UNPKG

@ikenxuan/amagi

Version:

抖音、B站的 web 端相关数据接口基于 Node.js 的实现

1,633 lines 337 kB
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const require_chunk = require("../chunk-CKQMccvm.cjs"); let node_url = require("node:url"); node_url = require_chunk.__toESM(node_url, 1); let node_events = require("node:events"); let zod = require("zod"); zod = require_chunk.__toESM(zod, 1); let _ikenxuan_xhshow_ts = require("@ikenxuan/xhshow-ts"); let node_crypto = require("node:crypto"); node_crypto = require_chunk.__toESM(node_crypto, 1); let axios = require("axios"); axios = require_chunk.__toESM(axios, 1); let chalk = require("chalk"); let protobufjs = require("protobufjs"); protobufjs = require_chunk.__toESM(protobufjs, 1); let express = require("express"); express = require_chunk.__toESM(express, 1); //#region src/utils/deprecation.ts /** * 废弃 API 注册表 * 存储所有已注册的废弃 API 配置 */ const deprecatedApis = /* @__PURE__ */ new Map(); /** * 注册一个废弃的 API * * 将 API 添加到废弃注册表中,后续可通过 checkDeprecation 检查 * * @param config - 废弃配置对象 * * @example * ```typescript * registerDeprecatedApi({ * name: 'getDouyinData', * deprecatedIn: '6.0.0', * removedIn: '7.0.0', * replacement: 'douyinFetcher', * throwError: true * }) * ``` */ function registerDeprecatedApi(config) { deprecatedApis.set(config.name, config); } /** * 检查 API 是否已废弃并进行相应处理 * * 如果 API 已注册为废弃,根据配置决定是打印警告还是抛出错误 * * @param apiName - 要检查的 API 名称 * @throws {DeprecatedApiError} 如果 API 已废弃且配置为抛出错误 * * @example * ```typescript * // 在函数开头调用检查 * function getDouyinData(options) { * checkDeprecation('getDouyinData') * // ... * } * ``` */ function checkDeprecation(apiName) { const config = deprecatedApis.get(apiName); if (!config) return; const message = buildDeprecationMessage(config); if (config.throwError) throw new DeprecatedApiError(message, config); else console.warn(message); } /** * 根据配置构建废弃提示消息 * * @param config - 废弃配置 * @returns 格式化的废弃提示消息字符串 */ function buildDeprecationMessage(config) { const lines = [`[DEPRECATED] "${config.name}" 已在 v${config.deprecatedIn} 版本废弃。`]; if (config.replacement) lines.push(`请使用 "${config.replacement}" 替代。`); else lines.push("此接口已被上游删除,无法继续使用,无可用替代方案。"); if (config.removedIn) lines.push(`此 API 将在 v${config.removedIn} 版本移除。`); if (config.migrationGuide) lines.push(`迁移指南: ${config.migrationGuide}`); return lines.join("\n"); } /** * 废弃 API 调用错误类 * * 当调用已废弃且配置为抛出错误的 API 时抛出此错误 * 包含完整的废弃配置信息,便于调试和迁移 */ var DeprecatedApiError = class DeprecatedApiError extends Error { /** 废弃配置信息,包含替代方案等详细信息 */ config; /** * 创建废弃 API 错误实例 * * @param message - 错误消息 * @param config - 废弃配置对象 */ constructor(message, config) { super(message); this.name = "DeprecatedApiError"; this.config = config; Error.captureStackTrace?.(this, DeprecatedApiError); } }; registerDeprecatedApi({ name: "getDouyinData", deprecatedIn: "6.0.0", removedIn: "7.0.0", replacement: "douyinFetcher 或 client.douyin.fetcher", migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md", throwError: true }); registerDeprecatedApi({ name: "getBilibiliData", deprecatedIn: "6.0.0", removedIn: "7.0.0", replacement: "bilibiliFetcher 或 client.bilibili.fetcher", migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md", throwError: true }); registerDeprecatedApi({ name: "getKuaishouData", deprecatedIn: "6.0.0", removedIn: "7.0.0", replacement: "kuaishouFetcher 或 client.kuaishou.fetcher", migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md", throwError: true }); registerDeprecatedApi({ name: "getXiaohongshuData", deprecatedIn: "6.0.0", removedIn: "7.0.0", replacement: "xiaohongshuFetcher 或 client.xiaohongshu.fetcher", migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md", throwError: true }); [ { name: "单个视频作品数据", replacement: "fetchVideoInfo" }, { name: "单个视频下载信息数据", replacement: "fetchVideoStreamUrl" }, { name: "评论数据", replacement: "fetchComments" }, { name: "指定评论的回复", replacement: "fetchCommentReplies" }, { name: "用户主页数据", replacement: "fetchUserCard" }, { name: "用户主页动态列表数据", replacement: "fetchUserDynamicList" }, { name: "用户空间详细信息", replacement: "fetchUserSpaceInfo" }, { name: "获取UP主总播放量", replacement: "fetchUploaderTotalViews" }, { name: "Emoji数据", replacement: "fetchEmojiList" }, { name: "番剧基本信息数据", replacement: "fetchBangumiInfo" }, { name: "番剧下载信息数据", replacement: "fetchBangumiStreamUrl" }, { name: "动态详情数据", replacement: "fetchDynamicDetail" }, { name: "直播间信息", replacement: "fetchLiveRoomInfo" }, { name: "直播间初始化信息", replacement: "fetchLiveRoomInitInfo" }, { name: "登录基本信息", replacement: "fetchLoginStatus" }, { name: "申请二维码", replacement: "requestLoginQrcode" }, { name: "二维码状态", replacement: "checkQrcodeStatus" }, { name: "AV转BV", replacement: "convertAvToBv" }, { name: "BV转AV", replacement: "convertBvToAv" }, { name: "专栏正文内容", replacement: "fetchArticleContent" }, { name: "专栏显示卡片信息", replacement: "fetchArticleCards" }, { name: "专栏文章基本信息", replacement: "fetchArticleInfo" }, { name: "文集基本信息", replacement: "fetchArticleListInfo" }, { name: "实时弹幕", replacement: "fetchVideoDanmaku" }, { name: "从_v_voucher_申请_captcha", replacement: "requestCaptchaFromVoucher" }, { name: "验证验证码结果", replacement: "validateCaptchaResult" }, { name: "视频作品数据", replacement: "fetchVideoWork" }, { name: "图集作品数据", replacement: "fetchImageAlbumWork" }, { name: "合辑作品数据", replacement: "fetchSlidesWork" }, { name: "文字作品数据", replacement: "fetchTextWork" }, { name: "聚合解析", replacement: "parseWork" }, { name: "指定评论回复数据", replacement: "fetchCommentReplies" }, { name: "用户主页视频列表数据", replacement: "fetchUserVideoList" }, { name: "热点词数据", replacement: "fetchSuggestWords" }, { name: "搜索数据", replacement: "searchContent" }, { name: "音乐数据", replacement: "fetchMusicInfo" }, { name: "直播间信息数据", replacement: "fetchLiveRoomInfo" }, { name: "申请二维码数据", replacement: "requestLoginQrcode" }, { name: "动态表情数据", replacement: "fetchDynamicEmojiList" }, { name: "弹幕数据", replacement: "fetchDanmakuList" }, { name: "单个视频作品数据", replacement: "fetchVideoWork" }, { name: "首页推荐数据", replacement: "fetchHomeFeed" }, { name: "单个笔记数据", replacement: "fetchNoteDetail" }, { name: "用户数据", replacement: "fetchUserProfile" }, { name: "用户笔记数据", replacement: "fetchUserNoteList" }, { name: "表情列表", replacement: "fetchEmojiList" }, { name: "搜索笔记", replacement: "searchNotes" } ].forEach(({ name, replacement }) => { registerDeprecatedApi({ name: `methodType: '${name}'`, deprecatedIn: "6.0.0", removedIn: "7.0.0", replacement: `fetcher.${replacement}()`, throwError: true }); }); registerDeprecatedApi({ name: `methodType: '动态卡片数据'`, deprecatedIn: "6.0.0", removedIn: "7.0.0", migrationGuide: "https://amagi-docs.vercel.app/docs/changelog/6.1.3", throwError: false }); registerDeprecatedApi({ name: "fetchDynamicCard", deprecatedIn: "6.1.3", removedIn: "7.0.0", migrationGuide: "https://amagi-docs.vercel.app/docs/changelog/6.1.3", throwError: false }); //#endregion //#region src/model/DataFetchers.ts /** * 数据获取器模块 (已废弃) * * 此模块中的 getXXXData 函数已在 v6 版本废弃并移除 * 请使用新的 fetcher API 替代 * * @module model/DataFetchers * @deprecated v6 已废弃,请使用 fetcher API 替代 */ /** * 获取抖音数据 * * @deprecated v6 已废弃,请使用 douyinFetcher 或 client.douyin.fetcher 替代 * @throws {DeprecatedApiError} 调用时抛出废弃错误 * * @example * ```typescript * // 旧用法 (已废弃,会抛出错误) * const data = await getDouyinData('videoWork', { aweme_id: '123' }, cookie) * * // 新用法 * import { douyinFetcher } from '@ikenxuan/amagi' * const data = await douyinFetcher.fetchVideoWork({ aweme_id: '123' }, cookie) * * // 或使用客户端实例 * const client = createAmagiClient({ cookies: { douyin: cookie } }) * const data = await client.douyin.fetcher.fetchVideoWork({ aweme_id: '123' }) * ``` */ function getDouyinData(..._args) { checkDeprecation("getDouyinData"); throw new Error("getDouyinData 已废弃"); } /** * 获取B站数据 * * @deprecated v6 已废弃,请使用 bilibiliFetcher 或 client.bilibili.fetcher 替代 * @throws {DeprecatedApiError} 调用时抛出废弃错误 * * @example * ```typescript * // 旧用法 (已废弃,会抛出错误) * const data = await getBilibiliData('videoInfo', { bvid: 'BV123' }, cookie) * * // 新用法 * import { bilibiliFetcher } from '@ikenxuan/amagi' * const data = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV123' }, cookie) * * // 或使用客户端实例 * const client = createAmagiClient({ cookies: { bilibili: cookie } }) * const data = await client.bilibili.fetcher.fetchVideoInfo({ bvid: 'BV123' }) * ``` */ function getBilibiliData(..._args) { checkDeprecation("getBilibiliData"); throw new Error("getBilibiliData 已废弃"); } /** * 获取快手数据 * * @deprecated v6 已废弃,请使用 kuaishouFetcher 或 client.kuaishou.fetcher 替代 * @throws {DeprecatedApiError} 调用时抛出废弃错误 * * @example * ```typescript * // 旧用法 (已废弃,会抛出错误) * const data = await getKuaishouData('videoWork', { photoId: '123' }, cookie) * * // 新用法 * import { kuaishouFetcher } from '@ikenxuan/amagi' * const data = await kuaishouFetcher.fetchVideoWork({ photoId: '123' }, cookie) * * // 或使用客户端实例 * const client = createAmagiClient({ cookies: { kuaishou: cookie } }) * const data = await client.kuaishou.fetcher.fetchVideoWork({ photoId: '123' }) * ``` */ function getKuaishouData(..._args) { checkDeprecation("getKuaishouData"); throw new Error("getKuaishouData 已废弃"); } /** * 获取小红书数据 * * @deprecated v6 已废弃,请使用 xiaohongshuFetcher 或 client.xiaohongshu.fetcher 替代 * @throws {DeprecatedApiError} 调用时抛出废弃错误 * * @example * ```typescript * // 旧用法 (已废弃,会抛出错误) * const data = await getXiaohongshuData('noteDetail', { note_id: '123' }, cookie) * * // 新用法 * import { xiaohongshuFetcher } from '@ikenxuan/amagi' * const data = await xiaohongshuFetcher.fetchNoteDetail({ note_id: '123' }, cookie) * * // 或使用客户端实例 * const client = createAmagiClient({ cookies: { xiaohongshu: cookie } }) * const data = await client.xiaohongshu.fetcher.fetchNoteDetail({ note_id: '123' }) * ``` */ function getXiaohongshuData(..._args) { checkDeprecation("getXiaohongshuData"); throw new Error("getXiaohongshuData 已废弃"); } //#endregion //#region src/platform/bilibili/API.ts /** * B站 API URL 构建类 * * 提供所有 B站 API 的 URL 构建方法 */ var BilibiliAPI = class { /** 获取登录基本信息 */ getLoginStatus() { return "https://api.bilibili.com/x/web-interface/nav"; } /** 获取视频详细信息 */ getVideoInfo(data) { return `https://api.bilibili.com/x/web-interface/view?bvid=${data.bvid}`; } /** 获取视频流信息 */ getVideoStream(data) { return `https://api.bilibili.com/x/player/playurl?avid=${data.avid}&cid=${data.cid}`; } /** * 获取评论区明细 * @see https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/readme.md#评论区类型代码 */ getComments(data) { const params = new URLSearchParams({ oid: data.oid.toString(), type: data.type.toString(), mode: (data.mode ?? 3).toString(), plat: "1", seek_rpid: "", web_location: "1315875" }); if (data.pagination_str) params.append("pagination_str", JSON.stringify({ offset: data.pagination_str })); else params.append("pagination_str", JSON.stringify({ offset: "" })); return `https://api.bilibili.com/x/v2/reply/wbi/main?${params.toString()}`; } /** 获取评论区状态 */ getCommentStatus(data) { return `https://api.bilibili.com/x/v2/reply/subject/description?type=${data.type}&oid=${data.oid}`; } /** 获取指定评论的回复 */ getCommentReplies(data) { return `https://api.bilibili.com/x/v2/reply/reply?type=${data.type}&oid=${data.oid}&root=${data.root}&ps=${data.number}`; } /** 获取表情列表 */ getEmojiList() { return "https://api.bilibili.com/x/emote/user/panel/web?business=reply&web_location=0.0"; } /** 获取番剧明细 */ getBangumiInfo(data) { if (data.ep_id) return `https://api.bilibili.com/pgc/view/web/season?ep_id=${data.ep_id}`; else if (data.season_id) return `https://api.bilibili.com/pgc/view/web/season?season_id=${data.season_id}`; else throw new Error("Missing required parameter: ep_id or season_id"); } /** 获取番剧视频流信息 */ getBangumiStream(data) { return `https://api.bilibili.com/pgc/player/web/playurl?cid=${data.cid}&ep_id=${data.ep_id}`; } /** 获取用户空间动态 */ getUserDynamicList(data) { return `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?${new URLSearchParams({ host_mid: data.host_mid.toString(), offset: "", platform: "web", features: "itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote,forwardListHidden,decorationCard,commentsNewVersion,onlyfansAssetsV2,ugcDelete,onlyfansQaCard,avatarAutoTheme,sunflowerStyle,eva3CardOpus,eva3CardVideo,eva3CardComment" }).toString()}`; } /** 获取动态详情 */ getDynamicDetail(data) { return `https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?id=${data.dynamic_id}&features=itemOpusStyle,opusBigCover,onlyfansVote,endFooterHidden,decorationCard,onlyfansAssetsV2,ugcDelete,onlyfansQaCard,editable,opusPrivateVisible,avatarAutoTheme`; } /** * 获取动态卡片信息 * * @deprecated B站官方已于 `2025-08-09` 删除原 `dynamic_svr` 接口,该接口已停用。 * 调用将返回错误信息,请使用 {@link getDynamicDetail} 替代。 */ getDynamicCard(data) { return this.getDynamicDetail(data); } /** 获取用户名片信息 */ getUserCard(data) { return `https://api.bilibili.com/x/web-interface/card?mid=${data.host_mid}&photo=true`; } /** 获取直播间信息 */ getLiveRoomInfo(data) { return `https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${data.room_id}`; } /** 获取直播间初始化信息 */ getLiveRoomInit(data) { return `https://api.live.bilibili.com/room/v1/Room/room_init?id=${data.room_id}`; } /** 申请登录二维码 */ getLoginQrcode() { return "https://passport.bilibili.com/x/passport-login/web/qrcode/generate"; } /** 查询二维码状态 */ getQrcodeStatus(data) { return `https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=${data.qrcode_key}`; } /** 获取UP主总播放量 */ getUploaderTotalViews(data) { return `https://api.bilibili.com/x/space/upstat?mid=${data.host_mid}`; } /** 获取专栏正文内容 */ getArticleContent(data) { return `https://api.bilibili.com/x/article/view?id=${data.id}`; } /** 获取专栏显示卡片信息 */ getArticleCards(data) { return `https://api.bilibili.com/x/article/cards?ids=${Array.isArray(data.ids) ? data.ids.join(",") : data.ids}`; } /** 获取专栏文章基本信息 */ getArticleInfo(data) { return `https://api.bilibili.com/x/article/viewinfo?id=${data.id}`; } /** 获取文集基本信息 */ getArticleListInfo(data) { return `https://api.bilibili.com/x/article/list/web/articles?id=${data.id}`; } /** 获取用户空间详细信息 */ getUserSpaceInfo(data) { return `https://api.bilibili.com/x/space/wbi/acc/info?mid=${data.host_mid}`; } /** 从 v_voucher 申请验证码 */ getCaptchaFromVoucher(data) { return { Url: "https://api.bilibili.com/x/gaia-vgate/v1/register", Body: { ...data.csrf !== void 0 && { csrf: data.csrf }, v_voucher: data.v_voucher } }; } /** 验证验证码结果 */ validateCaptcha(data) { return { Url: "https://api.bilibili.com/x/gaia-vgate/v1/validate", Body: { challenge: data.challenge, token: data.token, validate: data.validate, seccode: data.seccode, ...data.csrf !== void 0 && { csrf: data.csrf } } }; } /** * 获取实时弹幕(web端 protobuf 接口) * @see https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/danmaku_proto.md */ getVideoDanmaku(data) { return `https://api.bilibili.com/x/v2/dm/web/seg.so?${new URLSearchParams({ type: "1", oid: data.cid.toString(), segment_index: (data.segment_index ?? 1).toString() }).toString()}`; } }; /** B站 API URL 构建器实例 */ const bilibiliApiUrls = new BilibiliAPI(); //#endregion //#region src/platform/bilibili/BilibiliApi.ts /** * 创建废弃的 API 存根函数 */ const createDeprecatedStub$3 = (methodName) => { return (..._args) => { checkDeprecation("getBilibiliData"); throw new Error(`bilibili.${methodName} 已废弃,请使用 bilibiliFetcher 替代`); }; }; /** * B站相关 API 的命名空间。 * * @deprecated v6 已废弃,请使用 bilibiliFetcher 或 client.bilibili.fetcher 替代 */ const bilibili = { /** @deprecated 请使用 bilibiliFetcher.fetchVideoInfo 替代 */ getVideoInfo: createDeprecatedStub$3("getVideoInfo"), /** @deprecated 请使用 bilibiliFetcher.fetchVideoStreamUrl 替代 */ getVideoStream: createDeprecatedStub$3("getVideoStream"), /** @deprecated 请使用 bilibiliFetcher.fetchComments 替代 */ getComments: createDeprecatedStub$3("getComments"), /** @deprecated 请使用 bilibiliFetcher.fetchCommentReplies 替代 */ getCommentReply: createDeprecatedStub$3("getCommentReply"), /** @deprecated 请使用 bilibiliFetcher.fetchUserCard 替代 */ getUserProfile: createDeprecatedStub$3("getUserProfile"), /** @deprecated 请使用 bilibiliFetcher.fetchUserDynamicList 替代 */ getUserDynamic: createDeprecatedStub$3("getUserDynamic"), /** @deprecated 请使用 bilibiliFetcher.fetchEmojiList 替代 */ getEmojiList: createDeprecatedStub$3("getEmojiList"), /** @deprecated 请使用 bilibiliFetcher.fetchBangumiInfo 替代 */ getBangumiInfo: createDeprecatedStub$3("getBangumiInfo"), /** @deprecated 请使用 bilibiliFetcher.fetchBangumiStreamUrl 替代 */ getBangumiStream: createDeprecatedStub$3("getBangumiStream"), /** @deprecated 请使用 bilibiliFetcher.fetchDynamicDetail 替代 */ getDynamicInfo: createDeprecatedStub$3("getDynamicInfo"), /** @deprecated 请使用 bilibiliFetcher.fetchDynamicCard 替代 */ getDynamicCard: createDeprecatedStub$3("getDynamicCard"), /** @deprecated 请使用 bilibiliFetcher.fetchLiveRoomInfo 替代 */ getLiveRoomDetail: createDeprecatedStub$3("getLiveRoomDetail"), /** @deprecated 请使用 bilibiliFetcher.fetchLiveRoomInitInfo 替代 */ getLiveRoomInitInfo: createDeprecatedStub$3("getLiveRoomInitInfo"), /** @deprecated 请使用 bilibiliFetcher.fetchLoginStatus 替代 */ getLoginBasicInfo: createDeprecatedStub$3("getLoginBasicInfo"), /** @deprecated 请使用 bilibiliFetcher.requestLoginQrcode 替代 */ getLoginQrcode: createDeprecatedStub$3("getLoginQrcode"), /** @deprecated 请使用 bilibiliFetcher.checkQrcodeStatus 替代 */ checkQrcodeStatus: createDeprecatedStub$3("checkQrcodeStatus"), /** @deprecated 请使用 bilibiliFetcher.fetchUploaderTotalViews 替代 */ getUserTotalPlayCount: createDeprecatedStub$3("getUserTotalPlayCount"), /** @deprecated 请使用 bilibiliFetcher.convertAvToBv 替代 */ convertAvToBv: createDeprecatedStub$3("convertAvToBv"), /** @deprecated 请使用 bilibiliFetcher.convertBvToAv 替代 */ convertBvToAv: createDeprecatedStub$3("convertBvToAv"), /** @deprecated 请使用 bilibiliFetcher.fetchArticleContent 替代 */ getArticleContent: createDeprecatedStub$3("getArticleContent"), /** @deprecated 请使用 bilibiliFetcher.fetchArticleCards 替代 */ getArticleCard: createDeprecatedStub$3("getArticleCard"), /** @deprecated 请使用 bilibiliFetcher.fetchArticleInfo 替代 */ getArticleInfo: createDeprecatedStub$3("getArticleInfo"), /** @deprecated 请使用 bilibiliFetcher.fetchArticleListInfo 替代 */ getColumnInfo: createDeprecatedStub$3("getColumnInfo"), /** @deprecated 请使用 bilibiliFetcher.fetchUserSpaceInfo 替代 */ getUserProfileDetail: createDeprecatedStub$3("getUserProfileDetail"), /** @deprecated 请使用 bilibiliFetcher.requestCaptchaFromVoucher 替代 */ applyVoucherCaptcha: createDeprecatedStub$3("applyVoucherCaptcha"), /** @deprecated 请使用 bilibiliFetcher.validateCaptchaResult 替代 */ validateCaptcha: createDeprecatedStub$3("validateCaptcha"), /** @deprecated 请使用 bilibiliFetcher.fetchVideoDanmaku 替代 */ getDanmaku: createDeprecatedStub$3("getDanmaku") }; /** * 创建绑定了cookie的B站API对象 * * @deprecated v6 已废弃,请使用 createBoundBilibiliFetcher 替代 */ const createBoundBilibiliApi = (_cookie, _requestConfig) => { return { ...bilibili }; }; //#endregion //#region src/model/events.ts /** * Amagi 事件系统 * @module model/events * @description 提供类型安全的事件发射器,用于日志、HTTP、网络和 API 事件的监听与触发 */ /** * 类型安全的事件发射器 * @description 继承自 Node.js EventEmitter,提供泛型约束确保事件名称与数据类型匹配 */ var TypedEventEmitter = class extends node_events.EventEmitter { /** * 触发事件 * @param event - 事件名称 * @param data - 事件数据 * @returns 是否有监听器处理了该事件 */ emit(event, data) { return super.emit(event, data); } /** * 注册事件监听器 * @param event - 事件名称 * @param listener - 事件处理函数 * @returns this (支持链式调用) */ on(event, listener) { return super.on(event, listener); } /** * 注册一次性事件监听器 * @param event - 事件名称 * @param listener - 事件处理函数 (只触发一次) * @returns this (支持链式调用) */ once(event, listener) { return super.once(event, listener); } /** * 移除事件监听器 * @param event - 事件名称 * @param listener - 要移除的事件处理函数 * @returns this (支持链式调用) */ off(event, listener) { return super.off(event, listener); } }; /** * Amagi 全局事件发射器实例 * @description 单例模式,所有模块共享同一个事件总线 * @example * ```typescript * import { amagiEvents } from 'amagi/model/events' * * // 监听 API 成功事件 * amagiEvents.on('api:success', (data) => { * console.log(`[${data.platform}] ${data.methodType} 耗时 ${data.duration}ms`) * }) * ``` */ const amagiEvents = new TypedEventEmitter(); /** * 发射日志事件 * @param level - 日志级别 * @param message - 日志消息 * @param args - 附加参数 */ const emitLog = (level, message, ...args) => { amagiEvents.emit(`log:${level}`, { level, message, args: args.length > 0 ? args : void 0, timestamp: /* @__PURE__ */ new Date() }); }; /** * 发射 HTTP 请求事件 * @param data - 请求数据 (不含 timestamp) */ const emitHttpRequest = (data) => { amagiEvents.emit("http:request", { ...data, timestamp: /* @__PURE__ */ new Date() }); }; /** * 发射 HTTP 响应事件 * @param data - 响应数据 (不含 timestamp) */ const emitHttpResponse = (data) => { amagiEvents.emit("http:response", { ...data, timestamp: /* @__PURE__ */ new Date() }); }; /** * 发射网络重试事件 * @param data - 重试数据 (不含 timestamp) */ const emitNetworkRetry = (data) => { amagiEvents.emit("network:retry", { ...data, timestamp: /* @__PURE__ */ new Date() }); }; /** * 发射网络错误事件 * @param data - 错误数据 (不含 timestamp) */ const emitNetworkError = (data) => { amagiEvents.emit("network:error", { ...data, timestamp: /* @__PURE__ */ new Date() }); }; /** * 发射 API 成功事件 * @param data - 成功数据 (不含 timestamp) */ const emitApiSuccess = (data) => { amagiEvents.emit("api:success", { ...data, timestamp: /* @__PURE__ */ new Date() }); }; /** * 发射 API 错误事件 * @param data - 错误数据 (不含 timestamp) */ const emitApiError = (data) => { amagiEvents.emit("api:error", { ...data, timestamp: /* @__PURE__ */ new Date() }); }; /** * 发射 info 级别日志 * @param message - 日志消息 * @param args - 附加参数 */ const emitLogInfo = (message, ...args) => { emitLog("info", message, ...args); }; /** * 发射 warn 级别日志 * @param message - 日志消息 * @param args - 附加参数 */ const emitLogWarn = (message, ...args) => { emitLog("warn", message, ...args); }; /** * 发射 error 级别日志 * @param message - 日志消息 * @param args - 附加参数 */ const emitLogError = (message, ...args) => { emitLog("error", message, ...args); }; /** * 发射 debug 级别日志 * @param message - 日志消息 * @param args - 附加参数 */ const emitLogDebug = (message, ...args) => { emitLog("debug", message, ...args); }; /** * 发射 mark 级别日志 (用于重要标记) * @param message - 日志消息 * @param args - 附加参数 */ const emitLogMark = (message, ...args) => { emitLog("mark", message, ...args); }; //#endregion //#region src/validation/utils.ts function smartNumber(errorMessage, minValue = 1, isInteger = false) { if (isInteger) return zod.default.coerce.number({ error: errorMessage }).int({ error: `${errorMessage.replace("不能为空", "")}必须是整数,不能包含小数` }).min(minValue, { error: `${errorMessage.replace("不能为空", "")}必须大于等于${minValue}` }); else return zod.default.coerce.number({ error: errorMessage }).min(minValue, { error: `${errorMessage.replace("不能为空", "")}必须大于等于${minValue}` }); } /** * 智能正整数转换器 - 专门用于正整数类型的转换 * @param errorMessage - 自定义错误信息 * @returns Zod正整数验证器 */ const smartPositiveInteger = (errorMessage) => { return smartNumber(errorMessage, 1, true); }; /** * 从页面HTML中提取用户信息 * @param html - 包含用户页面HTML的字符串 * @returns 提取到的用户信息对象或null */ const extractCreatorInfoFromHtml = (html) => { const match = html.match(/<script>window\.__INITIAL_STATE__=(.+)<\/script>/m); if (!match) return null; try { const jsonStr = match[1].replace(/:undefined/g, ":null"); return JSON.parse(jsonStr)?.user?.userPageData ?? null; } catch (error) { console.error("解析用户信息失败:", error); return null; } }; //#endregion //#region src/validation/bilibili.ts /** 视频信息参数验证 */ const BilibiliVideoParamsSchema = zod.default.object({ methodType: zod.default.literal("videoInfo", { error: "方法类型必须是\"videoInfo\"" }), bvid: zod.default.string({ error: "BVID必须是字符串" }).min(1, { error: "BVID不能为空" }) }); /** 视频流参数验证 */ const BilibiliVideoDownloadParamsSchema = zod.default.object({ methodType: zod.default.literal("videoStream", { error: "方法类型必须是\"videoStream\"" }), avid: smartNumber("AVID不能为空", 1, true), cid: smartNumber("CID不能为空", 1, true) }); /** 评论参数验证 */ const BilibiliCommentParamsSchema = zod.default.object({ methodType: zod.default.literal("comments", { error: "方法类型必须是\"comments\"" }), oid: zod.default.string({ error: "OID必须是字符串" }).min(1, { error: "OID不能为空" }), type: smartNumber("评论类型不能为空", 1, true).refine((val) => [ 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 33 ].includes(val), { error: "无效的评论区类型" }), number: zod.default.coerce.number({ error: "评论数量必须是数字" }).int({ error: "评论数量必须是整数" }).positive({ error: "评论数量必须是正数" }).default(20).optional(), pn: zod.default.coerce.number({ error: "页码必须是数字" }).int({ error: "页码必须是整数" }).positive({ error: "页码必须是正数" }).default(1).optional() }); /** 评论回复参数验证 */ const BilibiliCommentReplyParamsSchema = zod.default.object({ methodType: zod.default.literal("commentReplies", { error: "方法类型必须是\"commentReplies\"" }), oid: zod.default.string({ error: "OID必须是字符串" }).min(1, { error: "OID不能为空" }), type: smartNumber("评论类型不能为空", 1, true).refine((val) => [ 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 33 ].includes(val), { error: "无效的评论区类型" }), root: zod.default.string({ error: "根评论ID必须是字符串" }).min(1, { error: "根评论ID不能为空" }), number: zod.default.coerce.number({ error: "评论数量必须是数字" }).int({ error: "评论数量必须是整数" }).positive({ error: "评论数量必须是正数" }).default(20).optional(), pn: zod.default.coerce.number({ error: "页码必须是数字" }).int({ error: "页码必须是整数" }).positive({ error: "页码必须是正数" }).default(1).optional() }); /** 用户参数验证 */ const BilibiliUserParamsSchema = zod.default.object({ methodType: zod.default.enum([ "userCard", "userDynamicList", "uploaderTotalViews", "userSpaceInfo" ], { error: "方法类型必须是指定的枚举值之一" }), host_mid: smartNumber("UP主UID不能为空", 1, true) }); /** 表情参数验证 */ const BilibiliEmojiParamsSchema = zod.default.object({ methodType: zod.default.literal("emojiList", { error: "方法类型必须是\"emojiList\"" }) }); /** 番剧信息参数验证 */ const BilibiliBangumiInfoParamsSchema = zod.default.object({ methodType: zod.default.literal("bangumiInfo", { error: "方法类型必须是\"bangumiInfo\"" }), ep_id: zod.default.string({ error: "番剧EP ID必须是字符串" }).min(1, { error: "番剧EP ID不能为空" }).optional(), season_id: zod.default.string({ error: "番剧季度ID必须是字符串" }).optional() }).refine((data) => data.ep_id ?? data.season_id, { error: "ep_id 和 season_id 至少需要提供一个", path: ["ep_id"] }); /** 番剧流参数验证 */ const BilibiliBangumiStreamParamsSchema = zod.default.object({ methodType: zod.default.literal("bangumiStream", { error: "方法类型必须是\"bangumiStream\"" }), cid: smartNumber("CID不能为空", 1, true), ep_id: zod.default.string({ error: "番剧EP ID必须是字符串" }).min(1, { error: "番剧EP ID不能为空" }) }); /** 动态参数验证 */ const BilibiliDynamicParamsSchema = zod.default.object({ methodType: zod.default.enum(["dynamicDetail", "dynamicCard"], { error: "方法类型必须是\"dynamicDetail\"或\"dynamicCard\"" }), dynamic_id: zod.default.string({ error: "动态ID必须是字符串" }).min(1, { error: "动态ID不能为空" }) }); /** 直播间参数验证 */ const BilibiliLiveParamsSchema = zod.default.object({ methodType: zod.default.enum(["liveRoomInfo", "liveRoomInit"], { error: "方法类型必须是\"liveRoomInfo\"或\"liveRoomInit\"" }), room_id: zod.default.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" }) }); /** 登录状态参数验证 */ const BilibiliLoginParamsSchema = zod.default.object({ methodType: zod.default.literal("loginStatus", { error: "方法类型必须是\"loginStatus\"" }) }); /** 申请二维码参数验证 */ const BilibiliQrcodeParamsSchema = zod.default.object({ methodType: zod.default.literal("loginQrcode", { error: "方法类型必须是\"loginQrcode\"" }) }); /** 二维码状态参数验证 */ const BilibiliQrcodeStatusParamsSchema = zod.default.object({ methodType: zod.default.literal("qrcodeStatus", { error: "方法类型必须是\"qrcodeStatus\"" }), qrcode_key: zod.default.string({ error: "二维码key必须是字符串" }).min(1, { error: "二维码key不能为空" }) }); /** AV转BV参数验证 */ const BilibiliAv2BvParamsSchema = zod.default.object({ methodType: zod.default.literal("avToBv", { error: "方法类型必须是\"avToBv\"" }), avid: zod.default.coerce.number({ error: "AVID必须是数字" }).int({ error: "AVID必须是整数" }).positive({ error: "AVID必须是正数" }) }); /** BV转AV参数验证 */ const BilibiliBv2AvParamsSchema = zod.default.object({ methodType: zod.default.literal("bvToAv", { error: "方法类型必须是\"bvToAv\"" }), bvid: zod.default.string({ error: "BVID必须是字符串" }).min(1, { error: "BVID不能为空" }) }); /** 专栏内容参数验证 */ const BilibiliArticleParamsSchema = zod.default.object({ methodType: zod.default.literal("articleContent", { error: "方法类型必须是\"articleContent\"" }), id: zod.default.string({ error: "专栏ID必须是字符串" }).min(1, { error: "专栏ID不能为空" }) }); /** 专栏卡片参数验证 */ const BilibiliArticleCardParamsSchema = zod.default.object({ methodType: zod.default.literal("articleCards", { error: "方法类型必须是\"articleCards\"" }), ids: zod.default.union([zod.default.array(zod.default.string({ error: "被查询的 id 列表必须是字符串数组" })).min(1, { error: "被查询的 id 列表不能为空" }), zod.default.string({ error: "被查询的 id 列表必须是字符串" }).min(1, { error: "被查询的 id 列表不能为空" })]) }); /** 专栏信息参数验证 */ const BilibiliArticleInfoParamsSchema = zod.default.object({ methodType: zod.default.literal("articleInfo", { error: "方法类型必须是\"articleInfo\"" }), id: zod.default.string({ error: "专栏ID必须是字符串" }).min(1, { error: "专栏ID不能为空" }) }); /** 文集信息参数验证 */ const BilibiliColumnInfoParamsSchema = zod.default.object({ methodType: zod.default.literal("articleListInfo", { error: "方法类型必须是\"articleListInfo\"" }), id: zod.default.string({ error: "文集ID必须是字符串" }).min(1, { error: "文集ID不能为空" }) }); /** 验证码申请参数验证 */ const BilibiliApplyCaptchaParamsSchema = zod.default.object({ methodType: zod.default.literal("captchaFromVoucher", { error: "方法类型必须是\"captchaFromVoucher\"" }), csrf: zod.default.string({ error: "CSRF Token必须是字符串" }).optional(), v_voucher: zod.default.string({ error: "验证码ID必须是字符串" }).min(1, { error: "验证码ID不能为空" }) }); /** 验证码验证参数验证 */ const BilibiliValidateCaptchaParamsSchema = zod.default.object({ methodType: zod.default.literal("validateCaptcha", { error: "方法类型必须是\"validateCaptcha\"" }), csrf: zod.default.string({ error: "CSRF Token必须是字符串" }).optional(), challenge: zod.default.string({ error: "验证码challenge必须是字符串" }).min(1, { error: "验证码challenge不能为空" }), token: zod.default.string({ error: "验证码token必须是字符串" }).min(1, { error: "验证码token不能为空" }), validate: zod.default.string({ error: "验证码validate必须是字符串" }).min(1, { error: "验证码validate不能为空" }), seccode: zod.default.string({ error: "验证码seccode必须是字符串" }).min(1, { error: "验证码seccode不能为空" }) }); /** 弹幕参数验证 */ const BilibiliDanmakuParamsSchema = zod.default.object({ methodType: zod.default.literal("videoDanmaku", { error: "方法类型必须是\"videoDanmaku\"" }), cid: smartNumber("CID不能为空", 1, true), segment_index: zod.default.coerce.number({ error: "分段序号必须是数字" }).int({ error: "分段序号必须是整数" }).positive({ error: "分段序号必须是正数" }).default(1).optional() }); /** B站参数验证模式映射 */ const BilibiliValidationSchemas = { videoInfo: BilibiliVideoParamsSchema, videoStream: BilibiliVideoDownloadParamsSchema, comments: BilibiliCommentParamsSchema, commentReplies: BilibiliCommentReplyParamsSchema, userCard: BilibiliUserParamsSchema, userDynamicList: BilibiliUserParamsSchema, userSpaceInfo: BilibiliUserParamsSchema, emojiList: BilibiliEmojiParamsSchema, bangumiInfo: BilibiliBangumiInfoParamsSchema, bangumiStream: BilibiliBangumiStreamParamsSchema, dynamicDetail: BilibiliDynamicParamsSchema, dynamicCard: BilibiliDynamicParamsSchema, liveRoomInfo: BilibiliLiveParamsSchema, liveRoomInit: BilibiliLiveParamsSchema, loginStatus: BilibiliLoginParamsSchema, loginQrcode: BilibiliQrcodeParamsSchema, qrcodeStatus: BilibiliQrcodeStatusParamsSchema, uploaderTotalViews: BilibiliUserParamsSchema, avToBv: BilibiliAv2BvParamsSchema, bvToAv: BilibiliBv2AvParamsSchema, articleContent: BilibiliArticleParamsSchema, articleCards: BilibiliArticleCardParamsSchema, articleInfo: BilibiliArticleInfoParamsSchema, articleListInfo: BilibiliColumnInfoParamsSchema, captchaFromVoucher: BilibiliApplyCaptchaParamsSchema, validateCaptcha: BilibiliValidateCaptchaParamsSchema, videoDanmaku: BilibiliDanmakuParamsSchema }; /** B站方法路由映射 */ const BilibiliMethodRoutes = { videoInfo: "/fetch_one_video", videoStream: "/fetch_video_playurl", comments: "/fetch_work_comments", commentReplies: "/fetch_comment_reply", userCard: "/fetch_user_profile", userDynamicList: "/fetch_user_dynamic", userSpaceInfo: "/fetch_user_space_info", emojiList: "/fetch_emoji_list", bangumiInfo: "/fetch_bangumi_video_info", bangumiStream: "/fetch_bangumi_video_playurl", dynamicDetail: "/fetch_dynamic_info", dynamicCard: "/fetch_dynamic_card", liveRoomInfo: "/fetch_live_room_detail", liveRoomInit: "/fetch_liveroom_def", loginStatus: "/login_basic_info", loginQrcode: "/new_login_qrcode", qrcodeStatus: "/check_qrcode", uploaderTotalViews: "/fetch_user_full_view", avToBv: "/av_to_bv", bvToAv: "/bv_to_av", articleContent: "/fetch_article_content", articleCards: "/fetch_article_card", articleInfo: "/fetch_article_info", articleListInfo: "/fetch_column_info", captchaFromVoucher: "/apply_captcha", validateCaptcha: "/validate_captcha", videoDanmaku: "/fetch_danmaku" }; //#endregion //#region src/validation/douyin.ts /** 作品参数验证 */ const DouyinWorkParamsSchema = zod.default.object({ methodType: zod.default.enum([ "videoWork", "imageAlbumWork", "slidesWork", "parseWork", "textWork" ], { error: "方法类型必须是指定的枚举值之一" }), aweme_id: zod.default.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }) }); /** 评论参数验证 */ const DouyinCommentParamsSchema = zod.default.object({ methodType: zod.default.literal("comments", { error: "方法类型必须是\"comments\"" }), aweme_id: zod.default.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }), number: smartPositiveInteger("评论数量必须是正整数").optional().default(50), cursor: zod.default.coerce.number({ error: "游标必须是数字" }).int({ error: "游标必须是整数" }).min(0, { error: "游标不能小于0" }).default(0).optional() }); /** 热点词参数验证 */ const DouyinHotWordsParamsSchema = zod.default.object({ methodType: zod.default.literal("suggestWords", { error: "方法类型必须是\"suggestWords\"" }), query: zod.default.string({ error: "搜索词必须是字符串" }).min(1, { error: "搜索词不能为空" }) }); /** 搜索参数验证 */ const DouyinSearchParamsSchema = zod.default.object({ methodType: zod.default.literal("search", { error: "方法类型必须是\"search\"" }), query: zod.default.string({ error: "搜索词必须是字符串" }).min(1, { error: "搜索词不能为空" }), type: zod.default.enum([ "general", "user", "video" ], { error: "搜索类型必须是\"general\"、\"user\"或\"video\"" }).optional().default("general"), number: smartPositiveInteger("搜索数量必须是正整数").optional().default(10), search_id: zod.default.string({ error: "搜索ID必须是字符串" }).optional() }); /** 评论回复参数验证 */ const DouyinCommentReplyParamsSchema = zod.default.object({ methodType: zod.default.literal("commentReplies", { error: "方法类型必须是\"commentReplies\"" }), aweme_id: zod.default.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }), comment_id: zod.default.string({ error: "评论ID必须是字符串" }).min(1, { error: "评论ID不能为空" }), number: smartPositiveInteger("评论数量必须是正整数").optional().default(5), cursor: zod.default.coerce.number({ error: "游标必须是数字" }).int({ error: "游标必须是整数" }).min(0, { error: "游标不能小于0" }).default(0).optional() }); /** 用户参数验证 */ const DouyinUserParamsSchema = zod.default.object({ methodType: zod.default.literal("userProfile", { error: "方法类型必须是\"userProfile\"" }), sec_uid: zod.default.string({ error: "用户ID必须是字符串" }).min(1, { error: "用户ID不能为空" }) }); /** 用户列表参数验证(视频列表、喜欢列表、推荐列表) */ const DouyinUserListParamsSchema = zod.default.object({ methodType: zod.default.enum([ "userVideoList", "userFavoriteList", "userRecommendList" ], { error: "方法类型必须是指定的枚举值之一" }), sec_uid: zod.default.string({ error: "用户ID必须是字符串" }).min(1, { error: "用户ID不能为空" }), number: smartPositiveInteger("获取数量必须是正整数").optional().default(18), max_cursor: zod.default.string({ error: "游标必须是字符串" }).optional() }); /** 音乐参数验证 */ const DouyinMusicParamsSchema = zod.default.object({ methodType: zod.default.literal("musicInfo", { error: "方法类型必须是\"musicInfo\"" }), music_id: zod.default.string({ error: "音乐ID必须是字符串" }).min(1, { error: "音乐ID不能为空" }) }); /** 直播间参数验证 */ const DouyinLiveRoomParamsSchema = zod.default.object({ methodType: zod.default.literal("liveRoomInfo", { error: "方法类型必须是\"liveRoomInfo\"" }), web_rid: zod.default.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" }), room_id: zod.default.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" }) }); /** 二维码参数验证 */ const DouyinQrcodeParamsSchema = zod.default.object({ methodType: zod.default.literal("loginQrcode", { error: "方法类型必须是\"loginQrcode\"" }), verify_fp: zod.default.string({ error: "fp指纹必须是字符串" }).min(1, { error: "fp指纹不能为空" }) }); /** 表情列表参数验证 */ const DouyinEmojiListParamsSchema = zod.default.object({ methodType: zod.default.literal("emojiList", { error: "方法类型必须是\"emojiList\"" }) }); /** 动态表情参数验证 */ const DouyinEmojiProParamsSchema = zod.default.object({ methodType: zod.default.literal("dynamicEmojiList", { error: "方法类型必须是\"dynamicEmojiList\"" }) }); /** 弹幕参数验证 */ const DouyinDanmakuParamsSchema = zod.default.object({ methodType: zod.default.literal("danmakuList", { error: "方法类型必须是\"danmakuList\"" }), aweme_id: zod.default.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }), start_time: zod.default.coerce.number({ error: "开始时间必须是数字" }).int({ error: "开始时间必须是整数" }).min(0, { error: "开始时间不能小于0" }).optional(), end_time: zod.default.coerce.number({ error: "结束时间必须是数字" }).int({ error: "结束时间必须是整数" }).min(0, { error: "结束时间不能小于0" }).optional(), duration: zod.default.coerce.number({ error: "视频时长必须是数字" }).int({ error: "视频时长必须是整数" }).min(0, { error: "视频时长不能小于0" }) }).refine((data) => { if (data.end_time !== void 0) return data.end_time <= data.duration; return true; }, { error: "获取弹幕区间的结束时间不能超过视频总时长", path: ["end_time"] }).refine((data) => { if (data.start_time !== void 0 && data.end_time !== void 0) return data.start_time < data.end_time; return true; }, { error: "获取弹幕区间的开始时间必须小于结束时间", path: ["start_time"] }); /** 抖音参数验证模式映射 */ const DouyinValidationSchemas = { textWork: DouyinWorkParamsSchema, parseWork: DouyinWorkParamsSchema, videoWork: DouyinWorkParamsSchema, imageAlbumWork: DouyinWorkParamsSchema, slidesWork: DouyinWorkParamsSchema, comments: DouyinCommentParamsSchema, userProfile: DouyinUserParamsSchema, userVideoList: DouyinUserListParamsSchema, userFavoriteList: DouyinUserListParamsSchema, userRecommendList: DouyinUserListParamsSchema, suggestWords: DouyinHotWordsParamsSchema, search: DouyinSearchParamsSchema, musicInfo: DouyinMusicParamsSchema, liveRoomInfo: DouyinLiveRoomParamsSchema, loginQrcode: DouyinQrcodeParamsSchema, emojiList: DouyinEmojiListParamsSchema, dynamicEmojiList: DouyinEmojiProParamsSchema, commentReplies: DouyinCommentReplyParamsSchema, danmakuList: DouyinDanmakuParamsSchema }; /** 抖音方法路由映射 */ const DouyinMethodRoutes = { parseWork: "/fetch_one_work", textWork: "/fetch_one_work", videoWork: "/fetch_one_work", imageAlbumWork: "/fetch_one_work", slidesWork: "/fetch_one_work", comments: "/fetch_work_comments", commentReplies: "/fetch_video_comment_replies", userProfile: "/fetch_user_info", userVideoList: "/fetch_user_post_videos", userFavoriteList: "/fetch_user_favorite_list", userRecommendList: "/fetch_user_recommend_list", search: "/fetch_search_info", suggestWords: "/fetch_suggest_words", musicInfo: "/fetch_music_work", emojiList: "/fetch_emoji_list", dynamicEmojiList: "/fetch_emoji_pro_list", liveRoomInfo: "/fetch_user_live_videos", danmakuList: "/fetch_work_danmaku", loginQrcode: "/fetch_login_qrcode" }; //#endregion //#region src/validation/kuaishou.ts /** * 快手视频参数验证模式 */ const KuaishouVideoParamsSchema = zod.default.object({ methodType: zod.default.literal("videoWork", { error: "methodType must be \"videoWork\"" }), photoId: zod.default.string({ error: "photoId must be a string" }).min(1, { error: "photoId cannot be empty" }) }); /** * 快手评论参数验证模式 */ const KuaishouCommentParamsSchema = zod.default.object({ methodType: zod.default.literal("comments", { error: "methodType must be \"comments\"" }), photoId: zod.default.string({ error: "photoId must be a string" }).min(1, { error: "photoId cannot be empty" }) }); /** * 快手用户主页参数验证模式 */ const KuaishouUserProfileParamsSchema = zod.default.object({ methodType: zod.default.literal("userProfile", { error: "methodType must be \"userProfile\"" }), principalId: zod.default.string({ error: "principalId must be a string" }).min(1, { error: "principalId cannot be empty" }) }); /** * 快手用户作品列表参数验证模式 */ const KuaishouUserWorkListParamsSchema = zod.default.object({ methodType: zod.default.literal("userWorkList", { error: "methodType must be \"userWorkList\"" }), principalId: zod.default.string({ error: "principalId must be a string" }).min(1, { error: "principalId cannot be empty" }), pcursor: zod.default.string({ error: "pcursor must be a string" }).optional(), count: zod.default.number({ error: "count must be a number" }).int({ error: "count must be an integer" }).positive({ error: "count must be positive" }).max(100, { error: "count must be less than or equal to 100" }).optional() }); /** * 快手直播间信息参数验证模式 */ const KuaishouLiveRoomInfoParamsSchema = zod.default.object({ methodType: zod.default.literal("liveRoomInfo", { error: "methodType must be \"liveRoomInfo\"" }), principalId: zod.default.string({ error: "principalId must be a string" }).min(1, { error: "principalId cannot be empty" }) }); /** * 快手表情参数验证模式 */ const KuaishouEmojiParamsSchema = zod.default.object({ methodType: zod.default.literal("emojiList", { error: "methodType must be \"emojiList\"" }) }); /** * 快手参数验证模式映射 */ const KuaishouValidationSchemas = { videoWork: KuaishouVideoParamsSchema, comments: KuaishouCommentParamsSchema, userProfile: KuaishouUserProfileParamsSchema, userWorkList: KuaishouUserWorkListParamsSchema, liveRoomInfo: KuaishouLiveRoomInfoParamsSchema, emojiList: KuaishouEmojiParamsSchema }; /** * 快手方法路由映射 */ const KuaishouMethodRoutes = { videoWork: "/fetch_one_work", comments: "/fetch_work_comments", userProfile: "/fetch_user_profile", userWorkList: "/fetch_user_work_list", liveRoomInfo: "/fetch_live_room_info", emojiList: "/fetch_emoji_list" }; //#endregion //#region src/platform/xiaohongshu/sign/index.ts /** * 小红书签名算法类 */ var xiaohongshuSign = class { static client = new _ikenxuan_xhshow_ts.Xhshow(); /** * 生成GET请求的X-S签名 * @param path - API路径 * @param a1Cookie - a1 cookie值 * @param clientType - 客户端类型,默认为 'xhs-pc-web' * @param params - 查询参数对象 * @returns X-S签名 */ static generateXSGet(path, a1Cookie, clientType = "xhs-pc-web", params = {}) { return this.client.signXsGet(path, a1Cookie, clientType, params); } /** * 生成POST请求的X-S签名 * @param path - API路径 * @param a1Cookie - a1 cookie值 * @param clientType - 客户端类型,默认为 'xhs-pc-web' * @param body - 请求体对象 * @returns X-S签名 */ static generateXSPost(path, a1Cookie, clientType = "xhs-pc-web", body = {}) { return this.client.signXsPost(path, a1Cookie, clientType, body); } /** * 生成X-S-Common参数 * @param cookies - cookie字符串 * @returns Base64编码的随机字符串 */ static generateXSCommon(cookies) { return this.client.signXsCommon(cookies); } /** * 生成X-T时间戳 * @returns 当前时间戳字符串 */ static generateXT() { return this.client.getXT(); } /** * 生成X-B3-Traceid * @returns 16位随机字符串 */ static generateXB3Traceid() { return this.client.getB3TraceId(); } /** * 从cookie字符串中提取a1值 * @param cookieString - 完整的cookie字符串 * @returns a1 cookie值 */ static extractA1FromCookie(cookieString) { const match = cookieString.match(/a1=([^;]+)/); return match ? match[1] : ""; } /** * 生成搜索ID * @returns 搜索ID字符串 */ static getSearchId = () => (BigInt(Date.now()) << 64n) + BigInt(Math.floor(Math.random() * 2147483646)).toString(36); }; //#endregion //#region src/platform/xiaohongshu/API.ts /** * 搜索排序类型枚举 */ let SearchSortType = /* @__PURE__ */ function(SearchSortType) { /** * 默认排序 */ SearchSortType["GENERAL"] = "general"; /** * 最受欢迎(按热度降序) */ SearchSortType["MOST_POPULAR"] = "popularity_descending"; /** * 最新发布(按时间降序) */ SearchSortType["LATEST"] = "time_descending"; return SearchSortType; }({}); /** * 搜索笔记类型枚举 */ let SearchNoteType = /* @__PURE__ */ function(SearchNoteType) { /** * 默认(全部类型) */ SearchNoteType[SearchNoteType["ALL"] = 0] = "ALL"; /** * 仅视频 */ SearchNoteType[SearchNoteType["VIDEO"] = 1] = "VIDEO"; /** * 仅图片 */ SearchNoteType[SearchNoteType["IMAGE"] = 2] = "IMAGE"; return SearchNoteType; }({}); /** * 构建查询字符串 * @param params - 参数对象 * @returns 查询字符串 */ const buildQueryString$1 = (params) => { return Object.entries(params).filter(([_, value]) => value !== void 0 && value !== null).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&"); }; /** * 小红书API地址配置 */ const xiaohongshuApiUrls = { /** * 获取首页推荐数据的接口地址 * @param data - 请求参数 * @returns 完整的接口URL */ homeFeed(data = {}) { return { apiPath: "/api/sns/web/v1/homefeed", Url: "https://edith.xiaohongshu.com/api/sns/web/v1/homefeed", Body: { cursor_score: data.cursor_score ?? "1.7599348899670024E9", num: data.num ?? 33, refresh_type: data.refresh_type ?? 3, note_index: data.note_index ?? 33, category: data.category ?? "homefeed_recommend", search_key: data.search_key ?? "", image_formats: [ "jpg", "webp", "avif" ] } }; }, /** * 获取单个笔记数据的接口地址 * @param data - 请求参数 * @returns 完整的接口URL */ noteDetail(data) { return { apiPath: "/api/sns/web/v1/feed", Url: "https://edith.xiaohongshu.com/api/sns/web/v1/feed", Body: { source_note_id: data.note_id, image_formats: [ "jpg", "webp", "avif" ], extra: { need_body_topic: "1" }, xsec_source: "pc_feed", xsec_token: data.xsec_token } }; }, /** * 获取评论数据的接口地址 * @param data - 请求参数 * @returns 完整的接口URL */ noteComments(data) { return { apiPath: "/api/sns/web/v2/comment/page", Url: `https://edith.xiaohongshu.com/api/sns/web/v2/comment/page?${buildQueryString$1({ note_id: data.note_id, cursor: data.cursor ?? "", image_formats: [ "jpg", "webp", "avif" ].join(","), xsec_token: data.xsec_token })}` }; }, /** * 获取用户数据的接口地址 * @param data - 请求参数 * @returns 完整的接口URL */ userProfile(data) { return { apiPath: "/api/sns/web/v1/user/otherinfo", Url: `https://www.xiaohongshu.com/user/profile/${data.user_id}` }; }, /** * 获取用户笔记数据的接口地址 * @param data - 请求参数 * @returns 完整的接口URL */ userNoteList(data) { return { apiPath: "/api/sns/web/v1/user_posted", Url: `https://edith.xiaohongshu.com/api/sns/web/v1/user_posted?${buildQueryString$1({ user_id: data.user_id, cursor: data.cursor ?? "", num: data.num ?? 30, image_formats: [ "jpg", "webp", "avif" ].join(","), xsec_source: "pc_feed" })}` }; }, /** * 获取笔记表情列表的接口地址 * @param data - 请求参数 * @returns 完整的接口URL */ emojiList(data) { return { apiPath: "/api/im/redmoji/detail", Url: "https://edith.xiaohongshu.com/api/im/redmoji/detail" }; }, /** * 搜索笔记的接口地址 * @param data - 请求参数 * @returns 完整的接口URL */ searchNotes(data) { return { apiPath: "/api/sns/web/v1/search/notes", Body: { keyword: data.keyword, page: data.page ?? 1, page_size: data.page_size ?? 20, sort: "general", note_type: 0, search_id: xiaohongshuSign.getSearchId(), image_formats: [ "jpg", "webp", "avif" ] }, Url: "https://edith.xiaohongshu.com/api/sns/web/v1/search/notes" }; } }; /** * 创建小红书API URLs实例 * @returns 小红书API URLs对象 */ const createXiaohongshuApiUrls = () => { return xiaohongshuApiUrls; }; //#endregion //#region src/validation/xiaohongshu.ts const SearchSortTypeValues = Object.values(SearchSortType).filter((v) => typeof v === "string"); const SearchNoteTypeValues = Object.values(SearchNoteType).filter((v) => typeof v === "number"); /** * 小红书验证模式映射 */ const XiaohongshuValidationSchemas = { homeFeed: zod.default.object({ methodType: zod.default.literal("homeFeed", { error: "methodType must be \"homeFeed\"" }), cursor_score: zod.default.string({ error: "cursor_score must be a string" }).optional(), num: zod.default.coerce.number({ error: "num must be a number" }).int({ error: "num must be an integer" }).min(1, { error: "num cannot be less than 1" }).max(100, { error: "num cannot be greater than 100" }).optional(), refresh_type: zod.default.coerce.number({ error: "refresh_type must be a number" }).int({ error: "refresh_type must be an integer" }).optional(), note_index: zod.default.coerce.number({ error: "note_index must be a number" }).int({ error: "note_index must be an integer" }).optional(), category: zod.default.string({ error: "category must be a string" }).optional(), search_key: zod.default.string({ error: "search_key must be a string" }).optional() }), noteDetail: zod.default.object({ methodType: zod.default.literal("noteDetail", { error: "methodType must be \"noteDetail\"" }), note_id: zod.default.string({ error: "note_id must be a string" }), xsec_token: zod.default.string({ error: "xsec_token must be a string" }) }), noteComments: zod.default.object({ methodType: zod.default.literal("noteComments", { error: "methodType must be \"noteComments\"" }), note_id: zod.default.string({ error: "note_id must be a string" }), cursor: zod.d