ben-music-mcp 0.1.1__tar.gz

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,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: ben-music-mcp
3
+ Version: 0.1.1
4
+ Summary:
5
+ Author: ben
6
+ Author-email: 2014911413@qq.com
7
+ Requires-Python: >=3.12
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: fastmcp (>=2.13.0.2)
13
+ Requires-Dist: requests (>=2.32.4)
14
+ Description-Content-Type: text/markdown
15
+
16
+ # API 工具说明
17
+
18
+ 本项目基于 FastMCP,封装了音乐相关常用接口,所有方法均以 JSON 格式返回,适合自动化调用和二次开发。
19
+
20
+ ## 方法列表
21
+
22
+ ### 1. hello
23
+ 测试方法,返回示例字符串。
24
+
25
+ ### 2. get_song_info(keywords)
26
+ 根据关键词搜索歌曲信息,返回首条歌曲的详细信息(id、name、artists、album、alias、transNames、duration、fee、mvid)。
27
+
28
+ ### 3. get_song_id(keywords)
29
+ 根据关键词搜索歌曲,返回第一首歌的ID。
30
+
31
+ ### 4. lyric(keywords)
32
+ 根据歌名关键词搜索歌词(内部先获取歌曲ID),返回歌词文本。
33
+
34
+ ### 5. search_artist(keyword)
35
+ 根据关键词搜索歌手信息,自动带上 cookie,返回首个歌手的基本信息(artistName、artistId、avatar)。
36
+
37
+ ### 6. get_artist_info(keyword)
38
+ 根据关键词获取歌手详细信息,先查 id 再查详情,返回完整歌手信息。
39
+
40
+ ### 7. get_artist_hot_songs(keyword)
41
+ 根据关键词获取歌手最火的 50 首歌曲,先查 id 再查热门歌曲。
42
+
43
+ ### 8. get_music_url(keywords, level="standard")
44
+ 根据关键词获取音乐播放 url,先查歌曲 id 再查 url,支持音质等级选择。
45
+
46
+ ### 9. get_song_comment(keywords)
47
+ 根据关键词获取歌曲评论,先查歌曲 id 再查评论,只保留有用字段(nickname、content、likedCount、timeStr)。
48
+
49
+ ### 10. get_playlist(id)
50
+ 根据歌单 id 获取歌单详情,只返回关键字段(id、name、coverImgUrl、userId、trackCount、playCount、tracks 等)。
51
+
52
+ ### 11. get_toplist(name)
53
+ 根据排行榜名获取榜单详情(先查 id 再查 get_playlist)。
54
+
55
+ ### 12. get_similar_songs(keywords)
56
+ 根据关键词获取相似音乐(先查 id 再查 /simi/song),只保留关键信息。
57
+
58
+ ### 13. get_style_songs(style_name)
59
+ 根据曲风名返回歌曲列表(先查曲风 id,再查 /style/song),只保留关键信息(id、name、artists、album、duration)。
60
+
61
+ ### 14. get_uid(nickname)
62
+ 根据用户昵称获取其 uid,返回 uid 和昵称。
63
+
64
+ ### 15. get_user_playlist(nickname)
65
+ 输入用户昵称,获取用户歌单(先查 uid,再查 /user/playlist),只保留必要字段(id、name、coverImgUrl、trackCount、playCount、creator 信息)。
66
+
67
+ ### 16. login_anonymous()
68
+ 游客登录,获取游客 cookie,并保存到全局变量。
69
+
70
+ ### 17. login_refresh()
71
+ 刷新登录状态,获取新的 cookie。
72
+
73
+ ### 18. start_netease_music(exe_path="C:\\CloudMusic\\cloudmusic.exe")
74
+ 启动本地网易云音乐客户端,支持自定义 exe 路径。
75
+
76
+ ## 返回格式
77
+ 所有方法均返回如下结构:
78
+
79
+ ```json
80
+ {
81
+ "code": 200, // 状态码,200 表示成功
82
+ "msg": "success", // 状态信息
83
+ "data": {...} // 具体数据内容
84
+ }
85
+ ```
86
+
87
+ ## 使用说明
88
+ 1. 推荐通过 MCP 工具接口调用各方法。
89
+ 2. 部分方法(如评论、歌单、曲风)已做字段精简,适合前端展示和二次处理。
90
+ 3. 启动本地网易云客户端需 Windows 环境,默认路径可自定义。
91
+
92
+ ## 依赖
93
+ - Python 3.8+
94
+ - requests
95
+ - fastmcp
96
+
97
+ ## 维护
98
+ 如需扩展新接口或字段,请参考 tool.py 和 server.py 的实现风格。
99
+
@@ -0,0 +1,83 @@
1
+ # API 工具说明
2
+
3
+ 本项目基于 FastMCP,封装了音乐相关常用接口,所有方法均以 JSON 格式返回,适合自动化调用和二次开发。
4
+
5
+ ## 方法列表
6
+
7
+ ### 1. hello
8
+ 测试方法,返回示例字符串。
9
+
10
+ ### 2. get_song_info(keywords)
11
+ 根据关键词搜索歌曲信息,返回首条歌曲的详细信息(id、name、artists、album、alias、transNames、duration、fee、mvid)。
12
+
13
+ ### 3. get_song_id(keywords)
14
+ 根据关键词搜索歌曲,返回第一首歌的ID。
15
+
16
+ ### 4. lyric(keywords)
17
+ 根据歌名关键词搜索歌词(内部先获取歌曲ID),返回歌词文本。
18
+
19
+ ### 5. search_artist(keyword)
20
+ 根据关键词搜索歌手信息,自动带上 cookie,返回首个歌手的基本信息(artistName、artistId、avatar)。
21
+
22
+ ### 6. get_artist_info(keyword)
23
+ 根据关键词获取歌手详细信息,先查 id 再查详情,返回完整歌手信息。
24
+
25
+ ### 7. get_artist_hot_songs(keyword)
26
+ 根据关键词获取歌手最火的 50 首歌曲,先查 id 再查热门歌曲。
27
+
28
+ ### 8. get_music_url(keywords, level="standard")
29
+ 根据关键词获取音乐播放 url,先查歌曲 id 再查 url,支持音质等级选择。
30
+
31
+ ### 9. get_song_comment(keywords)
32
+ 根据关键词获取歌曲评论,先查歌曲 id 再查评论,只保留有用字段(nickname、content、likedCount、timeStr)。
33
+
34
+ ### 10. get_playlist(id)
35
+ 根据歌单 id 获取歌单详情,只返回关键字段(id、name、coverImgUrl、userId、trackCount、playCount、tracks 等)。
36
+
37
+ ### 11. get_toplist(name)
38
+ 根据排行榜名获取榜单详情(先查 id 再查 get_playlist)。
39
+
40
+ ### 12. get_similar_songs(keywords)
41
+ 根据关键词获取相似音乐(先查 id 再查 /simi/song),只保留关键信息。
42
+
43
+ ### 13. get_style_songs(style_name)
44
+ 根据曲风名返回歌曲列表(先查曲风 id,再查 /style/song),只保留关键信息(id、name、artists、album、duration)。
45
+
46
+ ### 14. get_uid(nickname)
47
+ 根据用户昵称获取其 uid,返回 uid 和昵称。
48
+
49
+ ### 15. get_user_playlist(nickname)
50
+ 输入用户昵称,获取用户歌单(先查 uid,再查 /user/playlist),只保留必要字段(id、name、coverImgUrl、trackCount、playCount、creator 信息)。
51
+
52
+ ### 16. login_anonymous()
53
+ 游客登录,获取游客 cookie,并保存到全局变量。
54
+
55
+ ### 17. login_refresh()
56
+ 刷新登录状态,获取新的 cookie。
57
+
58
+ ### 18. start_netease_music(exe_path="C:\\CloudMusic\\cloudmusic.exe")
59
+ 启动本地网易云音乐客户端,支持自定义 exe 路径。
60
+
61
+ ## 返回格式
62
+ 所有方法均返回如下结构:
63
+
64
+ ```json
65
+ {
66
+ "code": 200, // 状态码,200 表示成功
67
+ "msg": "success", // 状态信息
68
+ "data": {...} // 具体数据内容
69
+ }
70
+ ```
71
+
72
+ ## 使用说明
73
+ 1. 推荐通过 MCP 工具接口调用各方法。
74
+ 2. 部分方法(如评论、歌单、曲风)已做字段精简,适合前端展示和二次处理。
75
+ 3. 启动本地网易云客户端需 Windows 环境,默认路径可自定义。
76
+
77
+ ## 依赖
78
+ - Python 3.8+
79
+ - requests
80
+ - fastmcp
81
+
82
+ ## 维护
83
+ 如需扩展新接口或字段,请参考 tool.py 和 server.py 的实现风格。
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "ben-music-mcp"
3
+ version = "0.1.1"
4
+ description = ""
5
+ authors = [
6
+ {name = "ben",email = "2014911413@qq.com"}
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "requests>=2.32.4",
12
+ "fastmcp>=2.13.0.2"
13
+ ]
14
+
15
+ [tool.poetry]
16
+ packages = [{include = "ben_music_mcp", from = "src"}]
17
+
18
+ [project.scripts]
19
+ ben-music-mcp = "ben_music_mcp:main"
20
+
21
+ [build-system]
22
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
23
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,204 @@
1
+
2
+
3
+ from fastmcp import FastMCP
4
+ from .tool import (
5
+ _hello,
6
+ _get_song_info,
7
+ _get_song_id,
8
+ _lyric,
9
+ _search_artist,
10
+ _login_anonymous,
11
+ _login_refresh,
12
+ _get_artist_info,
13
+ _get_artist_hot_songs,
14
+ _get_music_url,
15
+ _get_song_comment,
16
+ _get_playlist,
17
+ _get_toplist,
18
+ _get_similar_songs,
19
+ _get_style_songs,
20
+ _start_netease_music,
21
+ _get_uid,
22
+ _get_user_playlist,
23
+ global_cookie
24
+ )
25
+
26
+ mcp = FastMCP("MCPService")
27
+
28
+
29
+ @mcp.tool
30
+ def hello() -> dict:
31
+ """方法注释"""
32
+ return _hello()
33
+
34
+
35
+ @mcp.tool
36
+ def get_song_info(keywords: str) -> dict:
37
+ """
38
+ 根据关键词搜索歌曲信息,歌曲id无需向用户透漏
39
+ :param keywords: 歌曲关键词
40
+ :return: 歌曲信息
41
+ """
42
+ return _get_song_info(keywords)
43
+
44
+
45
+ @mcp.tool
46
+ def get_song_id(keywords: str) -> dict:
47
+ """
48
+ 根据关键词搜索歌曲,返回第一首歌的ID
49
+ :param keywords: 歌曲关键词
50
+ :return: 歌曲ID
51
+ """
52
+ return _get_song_id(keywords)
53
+
54
+
55
+ @mcp.tool
56
+ def lyric(keywords: str) -> dict:
57
+ """
58
+ 根据歌名关键词搜索歌词(内部先获取歌曲ID)
59
+ :param keywords: 歌曲关键词
60
+ :return: 歌词文本
61
+ """
62
+ return _lyric(keywords)
63
+
64
+
65
+ @mcp.tool
66
+ def search_artist(keyword: str) -> dict:
67
+ """
68
+ 根据关键词搜索歌手信息,自动带上cookie
69
+ :param keyword: 歌手关键词
70
+ :return: 歌手信息字符串
71
+ """
72
+ return _search_artist(keyword)
73
+
74
+
75
+ @mcp.tool
76
+ def get_artist_info(keyword: str) -> dict:
77
+ """
78
+ 根据关键词获取歌手详细信息,先查id再查详情,返回json
79
+ :param keyword: 歌手关键词
80
+ :return: 歌手详细信息json
81
+ """
82
+ return _get_artist_info(keyword)
83
+
84
+
85
+ @mcp.tool
86
+ def get_artist_hot_songs(keyword: str) -> dict:
87
+ """
88
+ 根据关键词获取歌手最火的50首歌曲,先查id再查热门歌曲,返回json
89
+ :param keyword: 歌手关键词
90
+ :return: 热门歌曲json
91
+ """
92
+ return _get_artist_hot_songs(keyword)
93
+
94
+
95
+ @mcp.tool
96
+ def get_music_url(keywords: str, level: str = "standard") -> dict:
97
+ """
98
+ 根据关键词获取音乐播放url,先查歌曲id再查url,返回json
99
+ :param keywords: 歌曲关键词
100
+ :param level: 音质等级
101
+ :return: 音乐url json
102
+ """
103
+ return _get_music_url(keywords, level)
104
+
105
+
106
+ @mcp.tool
107
+ def get_song_comment(keywords: str) -> dict:
108
+ """
109
+ 根据关键词获取歌曲评论,先查歌曲id再查评论,返回json
110
+ :param keywords: 歌曲关键词
111
+ :return: 歌曲评论json
112
+ """
113
+ return _get_song_comment(keywords)
114
+
115
+ @mcp.tool
116
+ def get_playlist(id: int) -> dict:
117
+ """
118
+ 根据歌单id获取歌单详情
119
+ :param id: 歌单id
120
+ :return: 歌单详情json
121
+ """
122
+ return _get_playlist(id)
123
+
124
+ @mcp.tool
125
+ def get_toplist(name: str) -> dict:
126
+ """
127
+ 根据排行榜名获取榜单详情(先查id再查_get_playlist)
128
+ :param name: 榜单名称
129
+ :return: 榜单详情json
130
+ """
131
+ return _get_toplist(name)
132
+
133
+ @mcp.tool
134
+ def get_similar_songs(keywords: str) -> dict:
135
+ """
136
+ 根据关键词获取相似音乐(先查id再查/simi/song)
137
+ :param keywords: 歌曲关键词
138
+ :return: 相似音乐json
139
+ """
140
+ return _get_similar_songs(keywords)
141
+
142
+ @mcp.tool
143
+ def get_style_songs(style_name: str) -> dict:
144
+ """
145
+ 根据曲风名返回歌曲列表。
146
+ :param style_name: 曲风名
147
+ :return: 歌曲列表json
148
+ """
149
+ return _get_style_songs(style_name)
150
+
151
+
152
+ @mcp.tool()
153
+ def get_uid(nickname: str) -> dict:
154
+ """
155
+ 根据用户昵称获取其uid
156
+ :param nickname: 用户昵称
157
+ :return: 用户uid json
158
+ """
159
+ return _get_uid(nickname)
160
+
161
+ @mcp.tool()
162
+ def get_user_playlist(nickname: str) -> dict:
163
+ """
164
+ 输入用户昵称,获取用户歌单(只保留必要字段)
165
+ :param nickname: 用户昵称
166
+ :return: 歌单列表json
167
+ """
168
+ return _get_user_playlist(nickname)
169
+
170
+
171
+
172
+
173
+
174
+ @mcp.tool
175
+ def login_anonymous() -> dict:
176
+ """
177
+ 游客登录,获取游客 cookie,并保存到全局变量
178
+ :return: 游客 cookie 字符串
179
+ """
180
+ return _login_anonymous()
181
+
182
+ @mcp.tool
183
+ def login_refresh() -> dict:
184
+ """
185
+ 刷新登录状态,获取新的 cookie
186
+ :return: 新的 cookie 字符串
187
+ """
188
+ return _login_refresh()
189
+
190
+
191
+ @mcp.tool()
192
+ def start_netease_music(exe_path: str) -> dict:
193
+ """
194
+ 启动本地网易云音乐客户端
195
+ :param exe_path: 网易云音乐客户端的 exe 路径,默认 C:\\CloudMusic\\cloudmusic.exe
196
+ :return: 启动结果 json
197
+ """
198
+ return _start_netease_music(exe_path)
199
+
200
+ def main():
201
+ mcp.run(transport="stdio")
202
+
203
+ if __name__ == "__main__":
204
+ mcp.run(transport="stdio")
@@ -0,0 +1 @@
1
+ {"MUSIC_A_T": "1762674143805", "MUSIC_R_T": "0", "MUSIC_A": "00FD2909A8CC993C4FB0FC90128C7E2E3EEC34146238C0F67438D16B51DB026C9AC74700E4270BCF0954B0454EDA13126BBD915DF4CF52EF21ACB5141059BEFA897B59C94F25484D47A8C8B257C322CB9D745805D39E61449D59DF795D936D9F5F628CE6E0D6610792EB91B0D34EBFB837357B9AC0135C97B18B1D6FB1EA248B5532FEFF6A34A5C57B4EDE76C36AACF86A542A7358C44E0F2978A3B7AA852F2A84DA48857A5CAAE3FA2D4968232C727BFD96CD7D7E7479829C726EC940B1880EFD01740C665B517699FAAEF1CA3BDD92E049A36AB793A15AD9AD75E4D691D4D821E363CAE7BF3AC9586A653999810747C974239EC1E43944EB679B3DC92CFF113BDBC9279CCDCD5271C55DD18FE58847F6AF6D5E12D1D8BB8417D99B2AEDF13B4EC9097EE893ABD6BBC56E37187D2386D061DE4288B289AD7F4068E488B691682F61F170D1DDB0E6D84A679E49677A1E529C96FE9540E0B5527B1D23C956FEAB3CB6B3647B749B1CAE39EE70CC6FFC06E5F4AC33DA9470E856C1391FD7006FB487C9537E4D11B2D469A78615B32771CAE5A725D8FD8FA172E865A236F487B665817AE5F57F4AF8781F2663CA0E5780CBBF4474D616D068125C6EAFC1741C5B844F8BB6621037BA47FC0CD9E48FA63499730DF294E62E3A2CF99319D6DD778B573CCB7B99B2AFC4CC9035540E3864862E2707B4898BD831AF07C4F0774ED9AFA3F96B94079C3F48D262C4898BD0A3E39016", "__csrf": "eabbb81b921eb9637c0b389245b3a0dc", "MUSIC_R_U": "00F9CF8A93CB06AEC0A041DB4FC98922D18884F91B648A3F78CC43943902835DB940DC984AC33ED14A019B44DCDB707C2FA8F2ED50EC556FB93B675A320C20F5E39A3EA15488819D77A922244FB3B663B4"}
@@ -0,0 +1,5 @@
1
+ from . import mcp
2
+
3
+
4
+ if __name__ == "__main__":
5
+ mcp.run(transport="sse")
@@ -0,0 +1,26 @@
1
+ [{"tagId": 1000, "tagName": "流行"},
2
+ {"tagId": 1017, "tagName": "嘻哈说唱"},
3
+ {"tagId": 1015, "tagName": "电子"},
4
+ {"tagId": 1028, "tagName": "R&B"},
5
+ {"tagId": 1008, "tagName": "摇滚"},
6
+ {"tagId": 1003, "tagName": "民谣"},
7
+ {"tagId": 10119, "tagName": "国风"},
8
+ {"tagId": 1010, "tagName": "原声带"},
9
+ {"tagId": 1026, "tagName": "儿童"},
10
+ {"tagId": 79088, "tagName": "慢摇DJ"},
11
+ {"tagId": 1001, "tagName": "中国传统特色"},
12
+ {"tagId": 1022, "tagName": "古典"},
13
+ {"tagId": 1011, "tagName": "爵士"},
14
+ {"tagId": 1252, "tagName": "二次元"},
15
+ {"tagId": 1006, "tagName": "轻音乐"},
16
+ {"tagId": 1029, "tagName": "放克"},
17
+ {"tagId": 1263, "tagName": "世界乐器体裁"},
18
+ {"tagId": 1077, "tagName": "乡村"},
19
+ {"tagId": 1007, "tagName": "新世纪"},
20
+ {"tagId": 152124, "tagName": "朋克"},
21
+ {"tagId": 1071, "tagName": "拉丁音乐"},
22
+ {"tagId": 1041, "tagName": "金属"},
23
+ {"tagId": 1037, "tagName": "蓝调"},
24
+ {"tagId": 1014, "tagName": "另类/独立"},
25
+ {"tagId": 1043, "tagName": "雷鬼"},
26
+ {"tagId": 1012, "tagName": "世界音乐"}]
@@ -0,0 +1,491 @@
1
+
2
+
3
+ import subprocess
4
+ import requests
5
+
6
+ # 全局变量保存 cookie
7
+ global_cookie = {}
8
+
9
+ import os
10
+ import json
11
+
12
+ def _save_cookie_to_file(cookie: dict, filename: str = "cookie.txt"):
13
+ """将cookie保存到文件"""
14
+ with open(filename, "w", encoding="utf-8") as f:
15
+ f.write(json.dumps(cookie, ensure_ascii=False))
16
+
17
+ def _load_cookie_from_file(filename: str = "cookie.txt") -> dict:
18
+ """从文件读取cookie"""
19
+ if not os.path.exists(filename):
20
+ return {}
21
+ try:
22
+ with open(filename, "r", encoding="utf-8") as f:
23
+ return json.loads(f.read())
24
+ except Exception:
25
+ return {}
26
+
27
+ def _get_cookie() -> dict:
28
+ """获取当前cookie(优先文件)"""
29
+ global global_cookie
30
+ if global_cookie:
31
+ return global_cookie
32
+ cookie = _load_cookie_from_file()
33
+ global_cookie = cookie
34
+ return cookie
35
+
36
+ def _hello() -> dict:
37
+ """方法注释"""
38
+ return {"code": 200, "msg": "success", "data": "我是哈尔滨工业大学的学生,我叫张三,学号是123456。"}
39
+
40
+ def _get_song_info(keywords: str) -> dict:
41
+ """
42
+ 搜索歌曲,返回json格式
43
+ :param keywords: 歌曲关键词
44
+ :return: 歌曲信息json
45
+ """
46
+ url = f"https://ncm.nekogan.com/search?keywords={keywords}"
47
+ try:
48
+ resp = requests.get(url, timeout=5)
49
+ resp.raise_for_status()
50
+ data = resp.json()
51
+ if "result" in data and "songs" in data["result"]:
52
+ songs = data["result"]["songs"]
53
+ if not songs:
54
+ return {"code": 404, "msg": "未找到相关歌曲", "data": {}}
55
+ song = songs[0]
56
+ return {
57
+ "code": 200,
58
+ "msg": "success",
59
+ "data": {
60
+ "id": song.get("id", "未知"),
61
+ "name": song.get("name", "未知"),
62
+ "artists": [a["name"] for a in song.get("artists", [])],
63
+ "album": song.get("album", {}).get("name", "未知"),
64
+ "alias": song.get("alias", []),
65
+ "transNames": song.get("transNames", []),
66
+ "duration": song.get("duration", 0),
67
+ "fee": song.get("fee", 0),
68
+ "mvid": song.get("mvid", 0)
69
+ }
70
+ }
71
+ else:
72
+ return {"code": 404, "msg": "未找到相关歌曲", "data": {}}
73
+ except Exception as e:
74
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
75
+
76
+ def _get_song_id(keywords: str) -> dict:
77
+ """
78
+ 搜索歌曲,返回第一个歌曲ID,json格式
79
+ :param keywords: 歌曲关键词
80
+ :return: 歌曲ID json
81
+ """
82
+ result = _get_song_info(keywords)
83
+ if result.get("code") == 200 and "id" in result.get("data", {}):
84
+ return {"code": 200, "msg": "success", "id": str(result["data"]["id"])}
85
+ return {"code": 404, "msg": "未找到相关歌曲ID", "id": ""}
86
+
87
+ def _lyric(keywords: str) -> dict:
88
+ """先获取歌曲id,再查歌词,返回json"""
89
+ song_id_json = _get_song_id(keywords)
90
+ song_id = song_id_json.get("id", "")
91
+ if not song_id or not song_id.isdigit():
92
+ return {"code": 404, "msg": f"未找到歌曲ID,原因:{song_id_json}", "data": ""}
93
+ url = f"https://ncm.nekogan.com/lyric?id={song_id}"
94
+ try:
95
+ resp = requests.get(url, timeout=5)
96
+ resp.raise_for_status()
97
+ data = resp.json()
98
+ lyric_text = data.get("lrc", {}).get("lyric", "")
99
+ if lyric_text:
100
+ return {"code": 200, "msg": "success", "data": lyric_text}
101
+ else:
102
+ return {"code": 404, "msg": "未找到歌词。", "data": ""}
103
+ except Exception as e:
104
+ return {"code": 500, "msg": f"请求失败 {e}", "data": ""}
105
+
106
+
107
+ def _search_artist(keyword: str) -> dict:
108
+ """
109
+ 搜索歌手,返回json格式
110
+ :param keyword: 歌手关键词
111
+ :return: 歌手信息json
112
+ """
113
+ url = f"https://ncm.nekogan.com/ugc/artist/search?keyword={keyword}"
114
+ try:
115
+ cookie = _get_cookie()
116
+ resp = requests.get(url, timeout=5, cookies=cookie)
117
+ resp.raise_for_status()
118
+ data = resp.json()
119
+ data_field = data.get("data")
120
+ if not data_field or "list" not in data_field:
121
+ return {"code": 404, "msg": "未找到相关歌手", "data": {}}
122
+ artists = data_field.get("list", [])
123
+ if not artists:
124
+ return {"code": 404, "msg": "未找到相关歌手", "data": {}}
125
+ artist = artists[0]
126
+ return {
127
+ "code": 200,
128
+ "msg": "success",
129
+ "data": {
130
+ "artistName": artist.get("artistName", "未知"),
131
+ "artistId": artist.get("artistId", "未知"),
132
+ "avatar": artist.get("artistAvatarPicUrl", "")
133
+ }
134
+ }
135
+ except Exception as e:
136
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
137
+
138
+ def _get_artist_id(keyword: str) -> dict:
139
+ """
140
+ 根据关键词搜索歌手,返回第一个歌手ID,json格式
141
+ :param keyword: 歌手关键词
142
+ :return: 歌手ID json
143
+ """
144
+ result = _search_artist(keyword)
145
+ if result.get("code") == 200 and "artistId" in result.get("data", {}):
146
+ return {"code": 200, "msg": "success", "id": str(result["data"]["artistId"])}
147
+ return {"code": 404, "msg": "未找到相关歌手ID", "id": ""}
148
+
149
+
150
+ def _get_artist_info(keyword: str) -> dict:
151
+ """
152
+ 根据关键词获取歌手详细信息,先查id再查详情,返回json
153
+ :param keyword: 歌手关键词
154
+ :return: 歌手详细信息json
155
+ """
156
+ id_json = _get_artist_id(keyword)
157
+ artist_id = id_json.get("id", "")
158
+ if not artist_id or not artist_id.isdigit():
159
+ return {"code": 404, "msg": f"未找到歌手ID,原因:{id_json}", "data": {}}
160
+ url = f"https://ncm.nekogan.com/ugc/artist/get?id={artist_id}"
161
+ try:
162
+ cookie = _get_cookie()
163
+ resp = requests.get(url, timeout=5, cookies=cookie)
164
+ resp.raise_for_status()
165
+ data = resp.json()
166
+ return {"code": 200, "msg": "success", "data": data}
167
+ except Exception as e:
168
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
169
+
170
+ def _get_artist_hot_songs(keyword: str) -> dict:
171
+ """
172
+ 根据关键词获取歌手最火的50首歌曲,先查id再查热门歌曲,返回json
173
+ :param keyword: 歌手关键词
174
+ :return: 热门歌曲json
175
+ """
176
+ id_json = _get_artist_id(keyword)
177
+ artist_id = id_json.get("id", "")
178
+ if not artist_id or not artist_id.isdigit():
179
+ return {"code": 404, "msg": f"未找到歌手ID,原因:{id_json}", "data": []}
180
+ url = f"https://ncm.nekogan.com/artist/top/song?id={artist_id}"
181
+ try:
182
+ cookie = _get_cookie()
183
+ resp = requests.get(url, timeout=5, cookies=cookie)
184
+ resp.raise_for_status()
185
+ data = resp.json()
186
+ return {"code": 200, "msg": "success", "data": data}
187
+ except Exception as e:
188
+ return {"code": 500, "msg": f"请求失败: {e}", "data": []}
189
+
190
+
191
+ def _get_music_url(keywords: str, level: str = "standard") -> dict:
192
+ """
193
+ 根据关键词获取音乐播放url,先查歌曲id再查url,返回json
194
+ :param keywords: 歌曲关键词
195
+ :param level: 音质等级
196
+ :return: 音乐url json
197
+ """
198
+ id_json = _get_song_id(keywords)
199
+ song_id = id_json.get("id", "")
200
+ if not song_id or not song_id.isdigit():
201
+ return {"code": 404, "msg": f"未找到歌曲ID,原因:{id_json}", "data": {}}
202
+ url = f"https://ncm.nekogan.com/song/url/v1?id={song_id}&level={level}"
203
+ try:
204
+ resp = requests.get(url, timeout=5)
205
+ resp.raise_for_status()
206
+ data = resp.json()
207
+ return {"code": 200, "msg": "success", "data": data}
208
+ except Exception as e:
209
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
210
+
211
+
212
+ def _get_song_comment(keywords: str) -> dict:
213
+ """
214
+ 根据关键词获取歌曲评论,只保留有用信息,返回json
215
+ :param keywords: 歌曲关键词
216
+ :return: 歌曲评论json
217
+ """
218
+ id_json = _get_song_id(keywords)
219
+ song_id = id_json.get("id", "")
220
+ if not song_id or not song_id.isdigit():
221
+ return {"code": 404, "msg": f"未找到歌曲ID,原因:{id_json}", "data": []}
222
+ url = f"https://ncm.nekogan.com/comment/music?id={song_id}"
223
+ try:
224
+ resp = requests.get(url, timeout=5)
225
+ resp.raise_for_status()
226
+ data = resp.json()
227
+ hot_comments = data.get("hotComments", [])
228
+ # 只保留有用字段
229
+ result = []
230
+ for c in hot_comments:
231
+ user = c.get("user", {})
232
+ result.append({
233
+ "nickname": user.get("nickname", "未知"),
234
+ "content": c.get("content", ""),
235
+ "likedCount": c.get("likedCount", 0),
236
+ "timeStr": c.get("timeStr", "")
237
+ })
238
+ return {"code": 200, "msg": "success", "data": result}
239
+ except Exception as e:
240
+ return {"code": 500, "msg": f"请求失败: {e}", "data": []}
241
+
242
+
243
+ def _get_playlist(id):
244
+ """
245
+ 获取歌单详情,只返回关键字段。
246
+ 参数: id (歌单id)
247
+ 返回: JSON
248
+ """
249
+ url = f"https://ncm.nekogan.com/playlist/detail?id={id}"
250
+ try:
251
+ resp = requests.get(url, timeout=10)
252
+ data = resp.json()
253
+ if data.get("code") != 200 or "playlist" not in data:
254
+ return {"code": 500, "msg": "请求失败"}
255
+ pl = data["playlist"]
256
+ result = {
257
+ "id": pl.get("id"),
258
+ "name": pl.get("name"),
259
+ "coverImgUrl": pl.get("coverImgUrl"),
260
+ "userId": pl.get("userId"),
261
+ "createTime": pl.get("createTime"),
262
+ "trackCount": pl.get("trackCount"),
263
+ "playCount": pl.get("playCount"),
264
+ "subscribedCount": pl.get("subscribedCount"),
265
+ "description": pl.get("description"),
266
+ "tags": pl.get("tags", []),
267
+ "tracks": []
268
+ }
269
+ for t in pl.get("tracks", []):
270
+ track = {
271
+ "id": t.get("id"),
272
+ "name": t.get("name"),
273
+ "ar": [a.get("name") for a in t.get("ar", [])],
274
+ "al": t.get("al", {}).get("name"),
275
+ "alia": t.get("alia", [])
276
+ }
277
+ result["tracks"].append(track)
278
+ return result
279
+ except Exception as e:
280
+ return {"code": 500, "msg": str(e)}
281
+
282
+
283
+ def _get_toplist(name: str) -> dict:
284
+ """
285
+ 根据排行榜名获取榜单详情(先查id再查_get_playlist)
286
+ :param name: 榜单名称
287
+ :return: 榜单详情json
288
+ """
289
+ try:
290
+ with open("toplist.json", "r", encoding="utf-8") as f:
291
+ data = json.load(f)
292
+ toplists = data.get("toplists", [])
293
+ match = next((item for item in toplists if item.get("name") == name), None)
294
+ if not match:
295
+ return {"code": 404, "msg": f"未找到榜单: {name}"}
296
+ return _get_playlist(match["id"])
297
+ except Exception as e:
298
+ return {"code": 500, "msg": f"读取榜单失败: {e}"}
299
+
300
+
301
+ def _get_similar_songs(keywords: str) -> dict:
302
+ """
303
+ 根据关键词获取相似音乐(先查id再查/simi/song)
304
+ :param keywords: 歌曲关键词
305
+ :return: 相似音乐json
306
+ """
307
+ id_json = _get_song_id(keywords)
308
+ song_id = id_json.get("id", "")
309
+ if not song_id or not song_id.isdigit():
310
+ return {"code": 404, "msg": f"未找到歌曲ID,原因:{id_json}", "data": []}
311
+ url = f"https://ncm.nekogan.com/simi/song?id={song_id}"
312
+ try:
313
+ resp = requests.get(url, timeout=5)
314
+ resp.raise_for_status()
315
+ data = resp.json()
316
+ songs = data.get("songs", [])
317
+ result = []
318
+ for s in songs:
319
+ result.append({
320
+ "id": s.get("id"),
321
+ "name": s.get("name"),
322
+ "artists": [a.get("name") for a in s.get("artists", [])],
323
+ "album": s.get("album", {}).get("name"),
324
+ "duration": s.get("duration")
325
+ })
326
+ return {"code": 200, "msg": "success", "data": result}
327
+ except Exception as e:
328
+ return {"code": 500, "msg": f"请求失败: {e}", "data": []}
329
+
330
+
331
+ def _get_style_songs(style_name: str) -> dict:
332
+ """
333
+ 根据曲风名获取对应曲风id,再查 /style/song?tagId=xxx,返回歌曲列表。
334
+ :param style_name: 曲风名
335
+ :return: 歌曲列表json
336
+ """
337
+
338
+ # 读取 styleList.json
339
+ try:
340
+ with open("styleList.json", "r", encoding="utf-8") as f:
341
+ style_list = json.load(f)
342
+ except Exception as e:
343
+ return {"code": 500, "msg": f"读取曲风列表失败: {e}", "data": {}}
344
+
345
+ tag_id = None
346
+ for item in style_list:
347
+ if item.get("tagName") == style_name:
348
+ tag_id = item.get("tagId")
349
+ print("tag_id: ",tag_id)
350
+ break
351
+ if not tag_id:
352
+ return {"code": 404, "msg": f"未找到曲风: {style_name}", "data": {}}
353
+
354
+ url = f"https://ncm.nekogan.com/style/song?tagId={tag_id}"
355
+ try:
356
+ cookie = _get_cookie()
357
+ resp = requests.get(url, timeout=10, cookies=cookie)
358
+ resp.raise_for_status()
359
+ data = resp.json()
360
+ print("完整响应:", data)
361
+ page = data.get("page", {})
362
+ songs = data.get("songs")
363
+ # 自动适配嵌套结构
364
+ if songs is None:
365
+ for v in data.values():
366
+ if isinstance(v, dict) and "songs" in v:
367
+ songs = v["songs"]
368
+ break
369
+ elif isinstance(v, list):
370
+ for item in v:
371
+ if isinstance(item, dict) and "songs" in item:
372
+ songs = item["songs"]
373
+ break
374
+ if songs is None:
375
+ songs = []
376
+ # 只保留关键信息
377
+ result = []
378
+ for s in songs:
379
+ if isinstance(s, dict):
380
+ result.append({
381
+ "id": s.get("id"),
382
+ "name": s.get("name"),
383
+ "artists": [a.get("name") for a in s.get("ar", [])],
384
+ "album": s.get("al", {}).get("name"),
385
+ "duration": s.get("dt")
386
+ })
387
+ print("最终精简songs:", result)
388
+ return {"code": 200, "msg": "success", "data": result}
389
+ except Exception as e:
390
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
391
+
392
+
393
+
394
+ def _get_uid(nickname: str) -> dict:
395
+ """
396
+ 根据用户昵称获取其uid
397
+ :param nickname: 用户昵称
398
+ :return: 用户uid json
399
+ """
400
+ url = f"https://ncm.nekogan.com/get/userids?nicknames={nickname}"
401
+ try:
402
+ resp = requests.get(url, timeout=5)
403
+ resp.raise_for_status()
404
+ data = resp.json()
405
+ # 适配 nicknames 字段结构
406
+ nick_dict = data.get("nicknames", {})
407
+ if not nick_dict:
408
+ return {"code": 404, "msg": "未找到用户", "data": {}}
409
+ # 取第一个昵称和uid
410
+ for n, uid in nick_dict.items():
411
+ return {"code": 200, "msg": "success", "data": {"uid": uid, "nickname": n}}
412
+ return {"code": 404, "msg": "未找到用户", "data": {}}
413
+ except Exception as e:
414
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
415
+
416
+ def _get_user_playlist(nickname: str) -> dict:
417
+ """
418
+ 输入用户昵称,获取用户歌单(只保留必要字段)
419
+ :param nickname: 用户昵称
420
+ :return: 歌单列表json
421
+ """
422
+ # 第一步:获取uid
423
+ uid_result = _get_uid(nickname)
424
+ if uid_result.get("code") != 200 or "uid" not in uid_result.get("data", {}):
425
+ return {"code": 404, "msg": f"未找到用户: {nickname}", "data": []}
426
+ uid = uid_result["data"]["uid"]
427
+ # 第二步:获取歌单
428
+ url = f"https://ncm.nekogan.com/user/playlist?uid={uid}"
429
+ try:
430
+ resp = requests.get(url, timeout=5)
431
+ resp.raise_for_status()
432
+ data = resp.json()
433
+ playlists = data.get("playlist", [])
434
+ result = []
435
+ for pl in playlists:
436
+ result.append({
437
+ "id": pl.get("id"),
438
+ "name": pl.get("name"),
439
+ "coverImgUrl": pl.get("coverImgUrl"),
440
+ "trackCount": pl.get("trackCount"),
441
+ "playCount": pl.get("playCount"),
442
+ "creator": {
443
+ "userId": pl.get("creator", {}).get("userId"),
444
+ "nickname": pl.get("creator", {}).get("nickname"),
445
+ "avatarUrl": pl.get("creator", {}).get("avatarUrl")
446
+ }
447
+ })
448
+ return {"code": 200, "msg": "success", "data": result}
449
+ except Exception as e:
450
+ return {"code": 500, "msg": f"请求失败: {e}", "data": []}
451
+
452
+
453
+
454
+
455
+ def _login_anonymous() -> dict:
456
+ url = "https://ncm.nekogan.com/register/anonimous"
457
+ try:
458
+ resp = requests.get(url, timeout=10)
459
+ resp.raise_for_status()
460
+ data = resp.json()
461
+ cookie = resp.cookies.get_dict()
462
+ global global_cookie
463
+ global_cookie = cookie
464
+ _save_cookie_to_file(cookie)
465
+ return {"code": 200, "msg": "游客登录成功", "cookie": cookie, "data": data}
466
+ except Exception as e:
467
+ return {"code": 500, "msg": f"游客登录失败: {e}", "cookie": {}, "data": {}}
468
+
469
+ def _login_refresh() -> dict:
470
+ url = "https://ncm.nekogan.com/login/refresh"
471
+ try:
472
+ resp = requests.get(url, timeout=10)
473
+ resp.raise_for_status()
474
+ data = resp.json()
475
+ cookie = resp.cookies.get_dict()
476
+ return {"code": 200, "msg": "登录状态刷新成功", "cookie": cookie, "data": data}
477
+ except Exception as e:
478
+ return {"code": 500, "msg": f"刷新登录失败: {e}", "cookie": {}, "data": {}}
479
+
480
+
481
+ def _start_netease_music(exe_path: str) -> dict:
482
+ """
483
+ 启动本地网易云音乐客户端
484
+ :param exe_path: 网易云音乐客户端的 exe 路径
485
+ :return: 启动结果 json
486
+ """
487
+ try:
488
+ subprocess.Popen(exe_path)
489
+ return {"code": 200, "msg": "网易云音乐已启动", "exe_path": exe_path}
490
+ except Exception as e:
491
+ return {"code": 500, "msg": f"启动失败: {e}", "exe_path": exe_path}
@@ -0,0 +1,65 @@
1
+ {
2
+ "toplists": [
3
+ {"name": "飙升榜", "id": 19723756},
4
+ {"name": "新歌榜", "id": 3779629},
5
+ {"name": "原创榜", "id": 2884035},
6
+ {"name": "热歌榜", "id": 3778678},
7
+ {"name": "网易云中文说唱榜", "id": 991319590},
8
+ {"name": "网易云古典榜", "id": 71384707},
9
+ {"name": "网易云电音榜", "id": 1978921795},
10
+ {"name": "网易云全球说唱榜", "id": 14028249541},
11
+ {"name": "潮流风向榜", "id": 13372522766},
12
+ {"name": "音乐合伙人推荐榜", "id": 12911403728},
13
+ {"name": "音乐合伙人热歌榜", "id": 12911589513},
14
+ {"name": "音乐合伙人留名榜", "id": 12911619970},
15
+ {"name": "音乐合伙人高分新歌榜", "id": 12911379734},
16
+ {"name": "音乐合伙人高分榜", "id": 12768855486},
17
+ {"name": "黑胶VIP爱听榜", "id": 5453912201},
18
+ {"name": "网易云ACG榜", "id": 71385702},
19
+ {"name": "网易云韩语榜", "id": 745956260},
20
+ {"name": "UK排行榜周榜", "id": 180106},
21
+ {"name": "美国Billboard榜", "id": 60198},
22
+ {"name": "Beatport全球电子舞曲榜", "id": 3812895},
23
+ {"name": "KTV唛榜", "id": 21845217},
24
+ {"name": "日本Oricon榜", "id": 60131},
25
+ {"name": "网易云欧美热歌榜", "id": 2809513713},
26
+ {"name": "网易云欧美新歌榜", "id": 2809577409},
27
+ {"name": "蛋仔派对听歌榜", "id": 8532443277},
28
+ {"name": "AI歌曲榜", "id": 9651277674},
29
+ {"name": "黑胶VIP新歌榜", "id": 7785123708},
30
+ {"name": "黑胶VIP热歌榜", "id": 7785066739},
31
+ {"name": "黑胶VIP爱搜榜", "id": 7785091694},
32
+ {"name": "实时热度榜", "id": 8246775932},
33
+ {"name": "法国 NRJ Vos Hits 周榜", "id": 27135204},
34
+ {"name": "网易云ACG动画榜", "id": 3001835560},
35
+ {"name": "网易云ACG游戏榜", "id": 3001795926},
36
+ {"name": "网易云ACG VOCALOID榜", "id": 3001890046},
37
+ {"name": "网易云日语榜", "id": 5059644681},
38
+ {"name": "网易云摇滚榜", "id": 5059633707},
39
+ {"name": "网易云国风榜", "id": 5059642708},
40
+ {"name": "潜力爆款榜", "id": 5338990334},
41
+ {"name": "网易云民谣榜", "id": 5059661515},
42
+ {"name": "听歌识曲榜", "id": 6688069460},
43
+ {"name": "网络热歌榜", "id": 6723173524},
44
+ {"name": "俄语榜", "id": 6732051320},
45
+ {"name": "越南语榜", "id": 6732014811},
46
+ {"name": "中文慢摇DJ榜", "id": 6886768100},
47
+ {"name": "俄罗斯top hit流行音乐榜", "id": 6939992364},
48
+ {"name": "泰语榜", "id": 7095271308},
49
+ {"name": "BEAT排行榜", "id": 7356827205},
50
+ {"name": "星云榜VOL.30 Wolf Alice新专来袭,人生应该拥有不一样的颜色", "id": 7325478166},
51
+ {"name": "LOOK直播歌曲榜", "id": 7603212484},
52
+ {"name": "赏音榜", "id": 7775163417},
53
+ {"name": "黑胶VIP限免榜", "id": 12344472377},
54
+ {"name": "吉利车友爱听榜", "id": 12717025277},
55
+ {"name": "昊铂车友爱听榜", "id": 10131772880},
56
+ {"name": "埃安车友爱听榜", "id": 10162841534},
57
+ {"name": "欧美R&B榜", "id": 12225155968},
58
+ {"name": "理想车友爱听榜", "id": 8703052295},
59
+ {"name": "比亚迪车友爱听榜", "id": 8702582160},
60
+ {"name": "蔚来车友爱听榜", "id": 8703220480},
61
+ {"name": "极氪车友爱听榜", "id": 8702982391},
62
+ {"name": "特斯拉车友爱听榜", "id": 8703179781},
63
+ {"name": "乐夏榜", "id": 8661209031}
64
+ ]
65
+ }