/** * @fileoverview KuGouMusicApi 程序化 API 入口模块 * * 本模块是项目的编程式调用入口(区别于 index.js 的 HTTP 服务入口), * 负责: * 1. 扫描 `module/` 目录下所有 API 模块文件并动态加载 * 2. 为每个模块创建统一的包装函数,自动处理 Cookie 格式转换 * 3. 将所有 API 函数与服务器工具、请求工具合并后统一导出 * * 导出结构为扁平对象,模块文件名(去 .js 后缀)即为 API 函数名。 * 例如:`module/search.js` → `api.search(params)` * * @module app * @requires node:fs * @requires path * @requires ./util - 工具函数(cookieToJson 等) * @requires ./server - 服务器管理(startService、getModulesDefinitions) * @requires ./util/request - 底层 HTTP 请求工具(createRequest) * * @example * // 作为库使用(编程式调用,不启动 HTTP 服务) * const api = require('./app'); * * // 手机登录 * const loginRes = await api.login_cellphone({ * mobile: '13800138000', * code: '123456' * }); * * // 搜索音乐(携带认证 Cookie) * const searchRes = await api.search({ * keywords: '海阔天空', * cookie: `token=${loginRes.body.token};userid=${loginRes.body.userid}` * }); */ const fs = require('node:fs'); const path = require('path'); const { cookieToJson } = require('./util'); /** * 动态注册的 API 函数集合 * * 键为模块文件名(去 .js 后缀),值为对应的包装函数。 * 例如:`{ search: [Function], login_cellphone: [Function], ... }` * * @type {Record) => Promise>} */ let obj = {}; /** * ============================================================ * 动态扫描并加载 module/ 目录下的所有 API 模块 * ============================================================ * * 扫描流程: * 1. 使用 `fs.readdirSync` 同步读取 `module/` 目录下所有文件 * 2. 对文件列表进行倒序排列(与 server.js 中的路由注册顺序保持一致) * 3. 过滤出以 `.js` 结尾的文件 * 4. 对每个模块文件: * a. 通过 `require` 加载模块,获取模块处理函数 * b. 从文件名提取 API 函数名(去掉 `.js` 后缀,如 `search.js` → `search`) * c. 创建包装函数并注册到 `obj` 对象中 * * 每个包装函数的作用: * - 将调用者传入的 cookie 字符串自动转换为 JSON 对象 * - 合并调用参数与默认 cookie * - 调用原始模块处理函数,同时注入请求工厂函数(lazy 加载 createRequest) */ fs.readdirSync(path.join(__dirname, 'module')) .reverse() .forEach((file) => { // 跳过非 .js 文件(如 .json、.map、目录等) if (!file.endsWith('.js')) return; // 加载模块,获取其导出的处理函数 let fileModule = require(path.join(__dirname, 'module', file)); // 提取 API 函数名:取文件名中第一个 `.` 之前的部分 // 例如:`search.js` → `search`,`login_cellphone.js` → `login_cellphone` let fn = file.split('.').shift() || ''; /** * 为当前模块创建包装函数 * * @param {Record} [data={}] - 调用者传入的请求参数 * @returns {Promise} 模块处理函数的返回结果 */ obj[fn] = (data = {}) => { // 自动将 cookie 字符串转换为 JSON 对象 // 调用者可以传入 cookie 字符串(如 "token=xxx;userid=xxx")或对象格式 if (typeof data.cookie === 'string') data.cookie = cookieToJson(data.cookie); // 调用原始模块处理函数,传入两个参数: // 1. 合并后的参数对象(确保 cookie 始终为对象格式,默认为空对象) // 2. 请求工厂函数:延迟加载 createRequest,避免循环依赖 return fileModule({ ...data, cookie: data.cookie ? data.cookie : {} }, (...args) => { // 延迟加载:仅在实际需要发起请求时才 require createRequest // 这样可以避免模块加载阶段的循环依赖问题 const { createRequest } = require('./util/request'); return createRequest(...args); }); }; }); /** * ============================================================ * 统一导出 * ============================================================ * * 最终导出一个扁平对象,包含三部分内容: * * 1. `...require('./server')` — 服务器管理工具 * - `startService()`: 启动 HTTP 服务 * - `getModulesDefinitions()`: 动态扫描模块定义 * * 2. `...require('./util/request')` — 底层请求工具 * - `createRequest()`: 创建 HTTP 请求 * * 3. `...obj` — 所有 API 模块函数 * - 每个 module/*.js 文件对应一个同名函数 * - 如 `search`、`login_cellphone`、`song_url` 等 * * @type {Record & import("./server")} */ module.exports = { ...require('./server'), ...require('./util/request'), ...obj };