@kuriyona/cecilia 0.2.0 → 1.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Cecilia
2
2
 
3
- 网易云音乐 API 的非官方 TypeScript 封装。
3
+ 网易云音乐 API 的非官方 TypeScript 封装。自行实现 weapi / eapi 加密与传输层(`node:crypto` + 全局 `fetch`),**不支持浏览器、Edge 等运行时,需要 Node ≥ 18**。
4
4
 
5
5
  ## 安装
6
6
 
@@ -11,59 +11,144 @@ pnpm add @kuriyona/cecilia
11
11
  ## 使用
12
12
 
13
13
  ```ts
14
- import { getPlaylistDetail, getLyric, getSongsDetail } from '@kuriyona/cecilia'
14
+ import {
15
+ getArtist,
16
+ getPlaylistTracks,
17
+ getSongUrl,
18
+ search,
19
+ } from '@kuriyona/cecilia'
15
20
  ```
16
21
 
17
- ### `getPlaylistDetail(id)`
22
+ 所有函数返回**精简后的领域模型**(统一语义词表:`coverUrl`、`avatarUrl`、`creator`、`duration`(毫秒)等),可选项在原始响应缺失时不会出现。每个函数都接受末位可选的 `options`:
18
23
 
19
- 获取歌单详情。
24
+ ```ts
25
+ interface RequestOptions {
26
+ cookie?: string | Record<string, string> // 登录态,如浏览器复制的含 MUSIC_U 的 cookie
27
+ realIP?: string
28
+ timeout?: number // 默认 10000
29
+ ua?: string
30
+ }
31
+ ```
20
32
 
21
33
  ```ts
22
- const detail = await getPlaylistDetail(123)
23
- // {
24
- // id: 123,
25
- // name: '歌单名',
26
- // coverImgId: 456,
27
- // coverImgUrl: 'https://example.com/cover.jpg',
28
- // userId: 789,
29
- // createTime: 1000000,
30
- // songs: [{ id: 1, addTime: 2000000 }, ...]
31
- // }
34
+ const artists = await search({ keywords: 'HoYo-MiX', type: 100 })
35
+ const playlistId = artists.artists![0].id
36
+ const tracks = await getPlaylistTracks({ id: playlistId, limit: 10 })
37
+ const urls = await getSongUrl({ id: tracks.map((t) => t.id) })
32
38
  ```
33
39
 
34
- ### `getLyric(id)`
40
+ ### 搜索
35
41
 
36
- 获取歌词。
42
+ ```ts
43
+ search(params) // keywords, type?, limit?, offset?
44
+ cloudSearch(params)
45
+ getSearchSuggest(params) // keywords?, mobile?
46
+ getHotSearches()
47
+ getHotSearchDetail()
48
+ getDefaultSearchKeyword()
49
+ searchMultimatch(params)
50
+ ```
51
+
52
+ ### 歌曲与歌词
37
53
 
38
54
  ```ts
39
- const lyric = await getLyric(1)
40
- // [
41
- // { time: 1.5, text: 'Hello' },
42
- // { time: 5, text: 'World' },
43
- // ]
55
+ getSongsDetail(ids: number[])
56
+ getSongUrl(params) // id, br?
57
+ checkMusic(params) // id, br?
58
+ getLyric(id)
59
+ getLyricNew(id) // 含 wordLines 逐字歌词与 romaLines 罗马音
60
+ getSimilarSongs(id)
61
+ ```
62
+
63
+ ### 歌单
64
+
65
+ ```ts
66
+ getPlaylistDetail(id)
67
+ getPlaylistTracks(params) // id, limit?, offset?
68
+ getPlaylistDetailDynamic(id)
69
+ getHighQualityTags()
70
+ getTopPlaylists(params)
71
+ getHighQualityPlaylists(params)
72
+ getPlaylistCategories()
73
+ getRelatedPlaylists(id)
74
+ ```
75
+
76
+ ### 歌手
77
+
78
+ ```ts
79
+ getArtist(id)
80
+ getArtistDetail(id)
81
+ getArtistSongs(params)
82
+ getArtistTopSongs(id)
83
+ getArtistAlbums(params)
84
+ getArtistList(params)
85
+ getArtistDesc(id)
86
+ getArtistMvs(params)
87
+ getArtistVideos(params)
44
88
  ```
45
89
 
46
- ### `getSongsDetail(ids)`
90
+ ### 专辑
91
+
92
+ ```ts
93
+ getAlbum(id)
94
+ getAlbumProduct(id)
95
+ getAlbumDynamic(id)
96
+ getAlbumSaleBoard(params)
97
+ getAlbumPrivileges(id)
98
+ getAlbumList(params)
99
+ getNewestAlbums()
100
+ ```
47
101
 
48
- 批量获取歌曲详情。
102
+ ### 示例
49
103
 
50
104
  ```ts
105
+ const detail = await getPlaylistDetail(123)
106
+ // {
107
+ // id: 123,
108
+ // name: '歌单名',
109
+ // creatorId: 789,
110
+ // coverUrl: 'https://example.com/cover.jpg',
111
+ // createTime: 1000000,
112
+ // songs: [{ id: 1, addTime: 2000000 }, ...] // 仅曲目 id,全量歌曲用 getPlaylistTracks
113
+ // }
114
+
51
115
  const songs = await getSongsDetail([1, 2])
52
116
  // [
53
117
  // {
54
118
  // id: 1,
55
119
  // name: 'Song A',
56
120
  // artists: [{ id: 10, name: 'Artist 1' }],
57
- // album: { id: 100, name: 'Album A', picUrl: '...' },
121
+ // album: { id: 100, name: 'Album A', coverUrl: '...' },
58
122
  // duration: 200000,
59
123
  // },
60
124
  // ]
61
125
  ```
62
126
 
127
+ ## 从 0.2 升级
128
+
129
+ `getPlaylistDetail` / `getLyric` / `getSongsDetail` 已改为走加密接口并对齐新词表(破坏性变更):
130
+
131
+ - `PlaylistDetails` → `PlaylistDetail`;`coverImgUrl` → `coverUrl`;`userId` → `creatorId`;删除 `coverImgId`
132
+ - `getSongsDetail` 的 `album.picUrl` → `album.coverUrl`;`SongDetail` 现为 `Song` 的别名
133
+ - `getLyric` 返回值不变
134
+
135
+ ## 已知限制
136
+
137
+ - `/song/url`(eapi)免登录可用,但普通 cookie 只返回试听片段(`freeTrialInfo` 会给出截取起止时间),完整/高码率需要会员 cookie;`/song/url/v1`(xeapi)本期未实现。
138
+ - **未实现匿名 token 与易盾反作弊 token(`checkToken` v2/v3)**,也不实现 xeapi。部分接口因此需要自行传入登录 cookie 才能调用。
139
+ - `/simi/artist`(相似歌手)上游返回 `code 301 未登录`(weapi 与明文 api 均如此),本期未纳入导出。
140
+ - `search({ type: 2000 })` 的语音搜索分支依赖上游当前响应结构(`songs` 或 `resources`),结构变化时会抛 `NeteaseApiError` 而不是返回空结果。
141
+ - `getRelatedPlaylists` 抓取 `music.163.com/playlist?id=` 的 HTML;页面结构变化时会抛 `NeteaseApiError`。
142
+ - 上游非 200 的成功码(如 `201`)默认视为失败;`request()` 的 `acceptCodes` 内部参数可按接口放行。
143
+
63
144
  ## 开发
64
145
 
65
146
  ```bash
66
- pnpm test # 运行测试
67
- pnpm test:watch # 监听模式
68
- pnpm build # 构建
69
- ```
147
+ pnpm test # 离线测试(mock fetch + 加密自洽 + 响应整形)
148
+ pnpm test:live # 真实网络测试(含 test/probe.live.test.ts 探针,dump 到 docs/probe/)
149
+ pnpm test:watch # 监听模式
150
+ pnpm build # 构建
151
+ pnpm clone:api-enhanced # 拉取 api-enhanced 参考实现(仅供比对,运行时不依赖)
152
+ ```
153
+
154
+ 离线测试直接断言各接口的请求 URL、加密方式与整形结果,不需要网络;`pnpm test:live -t probe` 会把上游原始响应写入 `docs/probe/<api>.json`(已 gitignore),用于核对字段路径。