inki-music-api 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SourceLoader = exports.SourceInstance = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const vm_1 = __importDefault(require("vm"));
10
+ const lx_runtime_1 = require("./lx-runtime");
11
+ const parseMeta = (code) => {
12
+ const meta = {};
13
+ const lines = code.split('\n').slice(0, 40);
14
+ for (const line of lines) {
15
+ const matched = line.match(/@(name|description|version|author|homepage)\s+(.+)/);
16
+ if (matched)
17
+ meta[matched[1]] = matched[2].trim();
18
+ }
19
+ return meta;
20
+ };
21
+ const createSandbox = (runtime) => {
22
+ const sandbox = {
23
+ lx: runtime.lx,
24
+ console,
25
+ setTimeout,
26
+ clearTimeout,
27
+ setInterval,
28
+ clearInterval,
29
+ Promise,
30
+ Buffer,
31
+ process,
32
+ URL,
33
+ URLSearchParams,
34
+ TextEncoder,
35
+ TextDecoder,
36
+ isNaN,
37
+ isFinite,
38
+ parseInt,
39
+ parseFloat,
40
+ JSON,
41
+ Object,
42
+ Array,
43
+ Number,
44
+ String,
45
+ Boolean,
46
+ Math,
47
+ Date,
48
+ Error,
49
+ TypeError,
50
+ RangeError,
51
+ RegExp,
52
+ Map,
53
+ Set,
54
+ WeakMap,
55
+ WeakSet,
56
+ Symbol,
57
+ Proxy,
58
+ Reflect,
59
+ ArrayBuffer,
60
+ Uint8Array,
61
+ Int8Array,
62
+ Uint16Array,
63
+ Int16Array,
64
+ Uint32Array,
65
+ Int32Array,
66
+ Float32Array,
67
+ Float64Array,
68
+ };
69
+ Object.defineProperty(sandbox, 'globalThis', {
70
+ enumerable: true,
71
+ get() {
72
+ return sandbox;
73
+ },
74
+ });
75
+ return sandbox;
76
+ };
77
+ class SourceInstance {
78
+ constructor(runtime, initedData, meta) {
79
+ this.runtime = runtime;
80
+ this.meta = meta;
81
+ this.sources = initedData.sources || {};
82
+ }
83
+ dispatch(source, action, info) {
84
+ return this.runtime.dispatch(lx_runtime_1.EVENT_NAMES.request, { source, action, info });
85
+ }
86
+ getMusicUrl(source, musicInfo, quality = '128k') {
87
+ return this.dispatch(source, 'musicUrl', { type: quality, musicInfo });
88
+ }
89
+ getLyric(source, musicInfo) {
90
+ return this.dispatch(source, 'lyric', { musicInfo });
91
+ }
92
+ getPic(source, musicInfo) {
93
+ return this.dispatch(source, 'pic', { musicInfo });
94
+ }
95
+ search(source, keywords, page = 1, limit = 20) {
96
+ return this.dispatch(source, 'search', { keywords, page, limit });
97
+ }
98
+ getHot(source, page = 1, limit = 20) {
99
+ return this.dispatch(source, 'hot', { page, limit });
100
+ }
101
+ getRank(source, rankType, page = 1, limit = 20) {
102
+ return this.dispatch(source, 'rank', { rankType, page, limit });
103
+ }
104
+ dispatchAction(source, action, info) {
105
+ return this.dispatch(source, action, info);
106
+ }
107
+ }
108
+ exports.SourceInstance = SourceInstance;
109
+ class SourceLoader {
110
+ constructor(httpClient, options) {
111
+ this.httpClient = httpClient;
112
+ this.options = options;
113
+ }
114
+ updateOptions(options) {
115
+ this.options = { ...this.options, ...options };
116
+ }
117
+ async loadFromFile(scriptPath) {
118
+ const resolved = path_1.default.resolve(scriptPath);
119
+ if (!fs_1.default.existsSync(resolved))
120
+ throw new Error(`[inki-music-api] 源脚本不存在: ${resolved}`);
121
+ const code = fs_1.default.readFileSync(resolved, 'utf-8');
122
+ return this.loadFromCode(code);
123
+ }
124
+ async loadFromCode(code) {
125
+ const runtime = new lx_runtime_1.LxRuntime(this.httpClient);
126
+ const meta = parseMeta(code);
127
+ meta.rawScript = code;
128
+ runtime.lx.currentScriptInfo = meta;
129
+ const sandbox = createSandbox(runtime);
130
+ vm_1.default.createContext(sandbox);
131
+ try {
132
+ vm_1.default.runInContext(code, sandbox, { filename: 'lx-source.js', timeout: 5000 });
133
+ }
134
+ catch (error) {
135
+ throw new Error(`[inki-music-api] 源脚本执行出错: ${error.message}`);
136
+ }
137
+ const initedData = await runtime.waitForInited(this.options.initTimeout);
138
+ return new SourceInstance(runtime, initedData, meta);
139
+ }
140
+ }
141
+ exports.SourceLoader = SourceLoader;
@@ -0,0 +1,330 @@
1
+ export type SourceKey = 'kw' | 'kg' | 'tx' | 'wy' | 'mg' | string;
2
+ export type ApiErrorCode = 'E_SOURCE_INIT' | 'E_SOURCE_UNSUPPORTED' | 'E_REQUEST_FAILED' | 'E_REMOTE_SOURCE_INVALID' | 'E_TIMEOUT' | 'E_RATE_LIMIT' | 'E_UPSTREAM';
3
+ export declare const SUPPORTED_PLATFORMS: readonly ["kg", "kw", "tx", "wy", "mg"];
4
+ export type SupportedPlatform = (typeof SUPPORTED_PLATFORMS)[number];
5
+ export type MusicQuality = '128k' | '320k' | 'flac' | 'flac24bit' | string;
6
+ export interface RequestOptions {
7
+ /** 请求方法,如 GET / POST */
8
+ method?: string;
9
+ /** 请求头 */
10
+ headers?: Record<string, string | number>;
11
+ /** JSON 请求体 */
12
+ body?: unknown;
13
+ /** form 请求体 */
14
+ form?: Record<string, unknown>;
15
+ /** 超时时间(毫秒) */
16
+ timeout?: number;
17
+ /** 最大重定向次数 */
18
+ follow_max?: number;
19
+ /** 是否校验证书 */
20
+ rejectUnauthorized?: boolean;
21
+ }
22
+ export interface RetryConfig {
23
+ /** 最大重试次数 */
24
+ retries?: number;
25
+ /** 初始重试延时(毫秒) */
26
+ retryDelayMs?: number;
27
+ /** 触发重试的 HTTP 状态码 */
28
+ retryStatusCodes?: number[];
29
+ /** 触发重试的错误码 */
30
+ retryErrorCodes?: string[];
31
+ }
32
+ export interface CacheConfig {
33
+ /** 是否启用内存缓存 */
34
+ enabled?: boolean;
35
+ /** 默认缓存时长(毫秒) */
36
+ ttlMs?: number;
37
+ /** 最大缓存键数量 */
38
+ maxEntries?: number;
39
+ }
40
+ export interface RequestResponse<T = unknown> {
41
+ /** HTTP 状态码 */
42
+ statusCode: number;
43
+ /** 响应头 */
44
+ headers: Record<string, unknown>;
45
+ /** 响应体 */
46
+ body: T;
47
+ }
48
+ export interface SourceScriptMeta {
49
+ /** 源脚本名称 */
50
+ name?: string;
51
+ /** 源脚本描述 */
52
+ description?: string;
53
+ /** 源脚本版本 */
54
+ version?: string;
55
+ /** 作者 */
56
+ author?: string;
57
+ /** 项目主页 */
58
+ homepage?: string;
59
+ /** 原始脚本文本 */
60
+ rawScript?: string;
61
+ }
62
+ export interface SourceInfo {
63
+ /** 源展示名称 */
64
+ name?: string;
65
+ /** 源类型,一般为 music */
66
+ type?: string;
67
+ /** 支持的动作列表,如 musicUrl/search */
68
+ actions?: string[];
69
+ /** 支持的音质列表 */
70
+ qualitys?: MusicQuality[];
71
+ }
72
+ export interface SourceInitData {
73
+ /** 源能力映射表 */
74
+ sources?: Record<string, SourceInfo>;
75
+ /** 其它扩展字段 */
76
+ [key: string]: unknown;
77
+ }
78
+ export interface MusicTypeInfo {
79
+ /** 音质类型 */
80
+ type: MusicQuality;
81
+ /** 体积描述(如 4.5MB) */
82
+ size: string | null;
83
+ /** 对应音质的 hash(部分源需要) */
84
+ hash?: string;
85
+ }
86
+ export interface MusicLyric {
87
+ /** 原文歌词 */
88
+ lyric: string;
89
+ /** 翻译歌词 */
90
+ tlyric: string;
91
+ /** 音译歌词 */
92
+ rlyric: string;
93
+ /** LX 扩展歌词 */
94
+ lxlyric: string;
95
+ }
96
+ export interface MusicInfo {
97
+ /** 榜单中的排名 */
98
+ rank?: number;
99
+ /** 歌曲主键(跨平台唯一性由 source + songmid 决定) */
100
+ songmid: string;
101
+ /** 平台来源 */
102
+ source: SourceKey;
103
+ /** 歌曲名 */
104
+ name: string;
105
+ /** 歌手名 */
106
+ singer: string;
107
+ /** 专辑名 */
108
+ albumName: string;
109
+ /** 专辑 ID */
110
+ albumId?: string;
111
+ /** 时长,格式 mm:ss */
112
+ interval: string;
113
+ /** 封面链接 */
114
+ img: string | null;
115
+ /** 兼容字段,歌词链接或内容 */
116
+ lrc?: string | null;
117
+ /** 当前歌曲可用音质列表 */
118
+ types: MusicTypeInfo[];
119
+ /** 音质索引映射 */
120
+ _types: Record<string, {
121
+ size: string | null;
122
+ hash?: string;
123
+ }>;
124
+ /** 不同音质的已解析直链缓存 */
125
+ typeUrl: Record<string, string>;
126
+ /** 平台哈希(如 kg) */
127
+ hash?: string;
128
+ /** 秒级时长(用于歌词接口) */
129
+ _interval?: number;
130
+ /** 平台歌曲数字 ID(如 tx) */
131
+ songId?: number;
132
+ /** 专辑 mid(如 tx) */
133
+ albumMid?: string;
134
+ /** 媒体 mid(如 tx) */
135
+ strMediaMid?: string;
136
+ /** 平台版权 ID(如 mg) */
137
+ copyrightId?: string;
138
+ /** 原文歌词链接 */
139
+ lrcUrl?: string;
140
+ /** 逐字歌词链接 */
141
+ mrcUrl?: string;
142
+ /** 翻译歌词链接 */
143
+ trcUrl?: string;
144
+ }
145
+ export interface SearchResult {
146
+ /** 当前页歌曲列表 */
147
+ list: MusicInfo[];
148
+ /** 总页数 */
149
+ allPage: number;
150
+ /** 总条数 */
151
+ total: number;
152
+ /** 每页条数 */
153
+ limit: number;
154
+ /** 平台来源 */
155
+ source: SourceKey;
156
+ }
157
+ export interface RankResult {
158
+ /** 榜单类型 */
159
+ rankType: string;
160
+ /** 总条数 */
161
+ total: number;
162
+ /** 当前榜单歌曲 */
163
+ list: MusicInfo[];
164
+ /** 页码 */
165
+ page: number;
166
+ /** 每页条数 */
167
+ limit: number;
168
+ /** 平台来源 */
169
+ source: SourceKey;
170
+ }
171
+ export interface SongListItem {
172
+ /** 歌单 ID */
173
+ id: string;
174
+ /** 歌单名 */
175
+ name: string;
176
+ /** 作者 */
177
+ author: string;
178
+ /** 封面 */
179
+ img: string;
180
+ /** 描述 */
181
+ desc: string;
182
+ /** 播放量文本 */
183
+ play_count: string;
184
+ /** 歌曲总数 */
185
+ total?: number;
186
+ /** 更新时间 */
187
+ time?: string;
188
+ /** 平台 */
189
+ source: SourceKey;
190
+ }
191
+ export interface SongListTag {
192
+ /** 标签 ID */
193
+ id: string;
194
+ /** 标签名 */
195
+ name: string;
196
+ /** 父级 ID */
197
+ parent_id?: string;
198
+ /** 父级名称 */
199
+ parent_name?: string;
200
+ /** 平台 */
201
+ source: SourceKey;
202
+ }
203
+ export interface SongListTagGroup {
204
+ /** 分组名 */
205
+ name: string;
206
+ /** 分组标签 */
207
+ list: SongListTag[];
208
+ }
209
+ export interface SongListTagResult {
210
+ /** 全量标签分组 */
211
+ tags: SongListTagGroup[];
212
+ /** 热门标签 */
213
+ hotTag: SongListTag[];
214
+ /** 平台 */
215
+ source: SourceKey;
216
+ }
217
+ export interface SongListResult {
218
+ /** 当前页歌单 */
219
+ list: SongListItem[];
220
+ /** 总条数 */
221
+ total: number;
222
+ /** 页码 */
223
+ page: number;
224
+ /** 每页条数 */
225
+ limit: number;
226
+ /** 平台 */
227
+ source: SourceKey;
228
+ }
229
+ export interface SongListDetailInfo {
230
+ /** 歌单名 */
231
+ name: string;
232
+ /** 封面 */
233
+ img: string;
234
+ /** 描述 */
235
+ desc: string;
236
+ /** 创建者 */
237
+ author: string;
238
+ /** 播放量 */
239
+ play_count: string;
240
+ }
241
+ export interface SongListDetailResult {
242
+ /** 歌单内歌曲 */
243
+ list: MusicInfo[];
244
+ /** 页码 */
245
+ page: number;
246
+ /** 每页条数 */
247
+ limit: number;
248
+ /** 歌曲总数 */
249
+ total: number;
250
+ /** 平台 */
251
+ source: SourceKey;
252
+ /** 歌单信息 */
253
+ info: SongListDetailInfo;
254
+ }
255
+ export interface CommentItem {
256
+ /** 评论 ID */
257
+ id: string;
258
+ /** 评论正文 */
259
+ text: string;
260
+ /** 时间戳 */
261
+ time: number;
262
+ /** 时间文本 */
263
+ timeStr: string;
264
+ /** 用户昵称 */
265
+ userName: string;
266
+ /** 用户头像 */
267
+ avatar: string;
268
+ /** 用户 ID */
269
+ userId: string;
270
+ /** 点赞数 */
271
+ likedCount: number;
272
+ }
273
+ export interface CommentResult {
274
+ /** 平台 */
275
+ source: SourceKey;
276
+ /** 评论列表 */
277
+ comments: CommentItem[];
278
+ /** 总条数 */
279
+ total: number;
280
+ /** 页码 */
281
+ page: number;
282
+ /** 每页条数 */
283
+ limit: number;
284
+ /** 总页数 */
285
+ maxPage: number;
286
+ }
287
+ export interface MusicApiConfig {
288
+ /** 源初始化超时(毫秒) */
289
+ initTimeout?: number;
290
+ /** 请求超时(毫秒) */
291
+ requestTimeout?: number;
292
+ /** 最大重定向次数 */
293
+ followMaxRedirects?: number;
294
+ /** 是否校验证书 */
295
+ rejectUnauthorized?: boolean;
296
+ /** HTTP 重试配置 */
297
+ retry?: RetryConfig;
298
+ /** 内存缓存配置 */
299
+ cache?: CacheConfig;
300
+ }
301
+ export interface SearchOptions {
302
+ /** 页码 */
303
+ page?: number;
304
+ /** 每页条数 */
305
+ limit?: number;
306
+ }
307
+ export interface RemoteSourceOptions {
308
+ /** 请求头 */
309
+ headers?: Record<string, string | number>;
310
+ /** 超时(毫秒) */
311
+ timeout?: number;
312
+ /** 是否校验证书 */
313
+ rejectUnauthorized?: boolean;
314
+ }
315
+ export interface SourceCapabilities {
316
+ search: boolean;
317
+ musicUrl: boolean;
318
+ lyric: boolean;
319
+ pic: boolean;
320
+ hot: boolean;
321
+ rank: boolean;
322
+ songListTags: boolean;
323
+ songList: boolean;
324
+ songListDetail: boolean;
325
+ comment: boolean;
326
+ hotComment: boolean;
327
+ searchMusic: boolean;
328
+ findMusic: boolean;
329
+ dispatch: boolean;
330
+ }
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SUPPORTED_PLATFORMS = void 0;
4
+ exports.SUPPORTED_PLATFORMS = ['kg', 'kw', 'tx', 'wy', 'mg'];
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "inki-music-api",
3
+ "version": "2.0.0",
4
+ "description": "lx-music 自定义源 TypeScript + axios 封装,支持 MusicApi class 与外部源加载",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json",
16
+ "typecheck": "tsc -p tsconfig.json --noEmit",
17
+ "lint": "eslint \"src/**/*.ts\"",
18
+ "format": "prettier --write \"src/**/*.{ts,js}\" \"test/**/*.{ts,js}\" README.md",
19
+ "format:check": "prettier --check \"src/**/*.{ts,js}\" \"test/**/*.{ts,js}\" README.md",
20
+ "test": "ts-node test/demo.ts",
21
+ "prepublishOnly": "npm run format:check && npm run lint && npm run typecheck && npm run build",
22
+ "publish:dry": "npm publish --dry-run",
23
+ "publish:latest": "npm publish --access public",
24
+ "release:patch": "npm version patch && npm run publish:latest",
25
+ "release:minor": "npm version minor && npm run publish:latest",
26
+ "release:major": "npm version major && npm run publish:latest"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "src/sources",
31
+ "README.md"
32
+ ],
33
+ "dependencies": {
34
+ "axios": "^1.11.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.18.8",
38
+ "@typescript-eslint/eslint-plugin": "^8.30.1",
39
+ "@typescript-eslint/parser": "^8.30.1",
40
+ "eslint": "^8.57.1",
41
+ "prettier": "^3.6.2",
42
+ "ts-node": "^10.9.2",
43
+ "typescript": "^5.9.3"
44
+ },
45
+ "keywords": [
46
+ "lx-music",
47
+ "music",
48
+ "api",
49
+ "custom-source"
50
+ ],
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "license": "MIT"
55
+ }