ben-music-mcp 0.1.9__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.
Potentially problematic release.
This version of ben-music-mcp might be problematic. Click here for more details.
- ben_music_mcp-0.1.9/PKG-INFO +72 -0
- ben_music_mcp-0.1.9/README.md +53 -0
- ben_music_mcp-0.1.9/pyproject.toml +26 -0
- ben_music_mcp-0.1.9/src/ben_music_mcp/__init__.py +280 -0
- ben_music_mcp-0.1.9/src/ben_music_mcp/action.py +106 -0
- ben_music_mcp-0.1.9/src/ben_music_mcp/cookie.txt +1 -0
- ben_music_mcp-0.1.9/src/ben_music_mcp/server.py +5 -0
- ben_music_mcp-0.1.9/src/ben_music_mcp/styleList.json +26 -0
- ben_music_mcp-0.1.9/src/ben_music_mcp/tool.py +520 -0
- ben_music_mcp-0.1.9/src/ben_music_mcp/toplist.json +65 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ben-music-mcp
|
|
3
|
+
Version: 0.1.9
|
|
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: psutil (>=6.1.1)
|
|
14
|
+
Requires-Dist: pyautogui (>=0.9.54)
|
|
15
|
+
Requires-Dist: requests (>=2.32.4)
|
|
16
|
+
Requires-Dist: uiautomation (>=2.0.20)
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# ben-music-mcp
|
|
20
|
+
|
|
21
|
+
本产品是一套面向开发者和智能体的音乐信息服务工具,专注于高效、专业的音乐数据获取与个性化推荐。所有接口均采用标准 JSON 格式,便于自动化集成与二次开发。
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 产品功能概览
|
|
26
|
+
|
|
27
|
+
### 歌曲与歌手信息
|
|
28
|
+
- **歌曲检索**:支持通过关键词精准获取歌曲详情,包括名称、艺人、专辑等核心信息。
|
|
29
|
+
- **歌词查询**:一键获取任意歌曲的完整歌词文本。
|
|
30
|
+
- **歌手检索与详情**:快速定位歌手并获取其详细资料及代表作品。
|
|
31
|
+
- **热门歌曲榜单**:自动获取歌手最受欢迎的曲目列表。
|
|
32
|
+
|
|
33
|
+
### 音乐播放与评论
|
|
34
|
+
- **在线试听链接**:根据需求获取不同音质等级的音乐播放地址。
|
|
35
|
+
- **热门评论提取**:智能筛选并返回歌曲的高质量评论,便于用户快速了解口碑。
|
|
36
|
+
- **相似歌曲推荐**:基于音乐特征,智能推荐与目标歌曲风格相近的作品。
|
|
37
|
+
|
|
38
|
+
### 歌单与榜单服务
|
|
39
|
+
- **歌单详情查询**:支持按歌单编号获取完整歌单信息,涵盖曲目、封面、创建者等。
|
|
40
|
+
- **排行榜检索**:覆盖主流及特色音乐榜单,助力发现最新流行趋势。
|
|
41
|
+
- **用户歌单获取**:根据用户昵称,自动获取其公开歌单列表。
|
|
42
|
+
|
|
43
|
+
### 曲风与标签体系
|
|
44
|
+
- **曲风分类查询**:支持按风格名称获取对应曲风下的热门歌曲。
|
|
45
|
+
- **风格列表获取**:一键获取所有可选音乐风格,便于个性化筛选。
|
|
46
|
+
- **榜单名称获取**:快速获取所有可用排行榜名称,提升榜单检索效率。
|
|
47
|
+
|
|
48
|
+
### 用户与客户端服务
|
|
49
|
+
- **用户身份查询**:通过昵称获取用户唯一标识,便于个性化服务。
|
|
50
|
+
- **本地客户端启动**:支持远程启动本地网易云音乐客户端,提升桌面体验。
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## 接口返回格式
|
|
56
|
+
所有接口均返回标准结构,便于前后端集成:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"code": 200, // 状态码,200 表示成功
|
|
61
|
+
"msg": "success", // 状态信息
|
|
62
|
+
"data": {...} // 具体数据内容
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 技术与环境要求
|
|
69
|
+
- Python 3.8 及以上
|
|
70
|
+
- requests
|
|
71
|
+
- fastmcp
|
|
72
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ben-music-mcp
|
|
2
|
+
|
|
3
|
+
本产品是一套面向开发者和智能体的音乐信息服务工具,专注于高效、专业的音乐数据获取与个性化推荐。所有接口均采用标准 JSON 格式,便于自动化集成与二次开发。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 产品功能概览
|
|
8
|
+
|
|
9
|
+
### 歌曲与歌手信息
|
|
10
|
+
- **歌曲检索**:支持通过关键词精准获取歌曲详情,包括名称、艺人、专辑等核心信息。
|
|
11
|
+
- **歌词查询**:一键获取任意歌曲的完整歌词文本。
|
|
12
|
+
- **歌手检索与详情**:快速定位歌手并获取其详细资料及代表作品。
|
|
13
|
+
- **热门歌曲榜单**:自动获取歌手最受欢迎的曲目列表。
|
|
14
|
+
|
|
15
|
+
### 音乐播放与评论
|
|
16
|
+
- **在线试听链接**:根据需求获取不同音质等级的音乐播放地址。
|
|
17
|
+
- **热门评论提取**:智能筛选并返回歌曲的高质量评论,便于用户快速了解口碑。
|
|
18
|
+
- **相似歌曲推荐**:基于音乐特征,智能推荐与目标歌曲风格相近的作品。
|
|
19
|
+
|
|
20
|
+
### 歌单与榜单服务
|
|
21
|
+
- **歌单详情查询**:支持按歌单编号获取完整歌单信息,涵盖曲目、封面、创建者等。
|
|
22
|
+
- **排行榜检索**:覆盖主流及特色音乐榜单,助力发现最新流行趋势。
|
|
23
|
+
- **用户歌单获取**:根据用户昵称,自动获取其公开歌单列表。
|
|
24
|
+
|
|
25
|
+
### 曲风与标签体系
|
|
26
|
+
- **曲风分类查询**:支持按风格名称获取对应曲风下的热门歌曲。
|
|
27
|
+
- **风格列表获取**:一键获取所有可选音乐风格,便于个性化筛选。
|
|
28
|
+
- **榜单名称获取**:快速获取所有可用排行榜名称,提升榜单检索效率。
|
|
29
|
+
|
|
30
|
+
### 用户与客户端服务
|
|
31
|
+
- **用户身份查询**:通过昵称获取用户唯一标识,便于个性化服务。
|
|
32
|
+
- **本地客户端启动**:支持远程启动本地网易云音乐客户端,提升桌面体验。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## 接口返回格式
|
|
38
|
+
所有接口均返回标准结构,便于前后端集成:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"code": 200, // 状态码,200 表示成功
|
|
43
|
+
"msg": "success", // 状态信息
|
|
44
|
+
"data": {...} // 具体数据内容
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 技术与环境要求
|
|
51
|
+
- Python 3.8 及以上
|
|
52
|
+
- requests
|
|
53
|
+
- fastmcp
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ben-music-mcp"
|
|
3
|
+
version = "0.1.9"
|
|
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
|
+
"pyautogui>=0.9.54",
|
|
14
|
+
"psutil>=6.1.1",
|
|
15
|
+
"uiautomation>=2.0.20"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[tool.poetry]
|
|
19
|
+
packages = [{include = "ben_music_mcp", from = "src"}]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
ben-music-mcp = "ben_music_mcp:main"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
26
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
from fastmcp import FastMCP
|
|
2
|
+
from .action import (
|
|
3
|
+
_open_cloud_music,
|
|
4
|
+
_search_and_play,
|
|
5
|
+
_play_and_pause,
|
|
6
|
+
_next_song,
|
|
7
|
+
_pre_song,
|
|
8
|
+
exe_path0
|
|
9
|
+
)
|
|
10
|
+
from .tool import (
|
|
11
|
+
_hello,
|
|
12
|
+
_get_song_info,
|
|
13
|
+
_get_song_id,
|
|
14
|
+
_lyric,
|
|
15
|
+
_search_artist,
|
|
16
|
+
_login_anonymous,
|
|
17
|
+
_login_refresh,
|
|
18
|
+
_get_artist_info,
|
|
19
|
+
_get_artist_hot_songs,
|
|
20
|
+
_get_music_url,
|
|
21
|
+
_get_song_comment,
|
|
22
|
+
_get_playlist,
|
|
23
|
+
_get_toplist,
|
|
24
|
+
_get_similar_songs,
|
|
25
|
+
_get_style_songs,
|
|
26
|
+
_start_netease_music,
|
|
27
|
+
_get_uid,
|
|
28
|
+
_get_user_playlist,
|
|
29
|
+
_return_stylelist,
|
|
30
|
+
_return_toplist_name,
|
|
31
|
+
global_cookie
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
mcp = FastMCP("MCPService")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@mcp.tool
|
|
38
|
+
def hello() -> dict:
|
|
39
|
+
"""version=0.1.9"""
|
|
40
|
+
return _hello()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@mcp.tool
|
|
47
|
+
def get_song_info(keywords: str) -> dict:
|
|
48
|
+
"""
|
|
49
|
+
根据关键词搜索歌曲信息,歌曲id无需向用户透漏
|
|
50
|
+
:param keywords: 歌曲关键词
|
|
51
|
+
:return: 歌曲信息
|
|
52
|
+
"""
|
|
53
|
+
return _get_song_info(keywords)
|
|
54
|
+
|
|
55
|
+
# @mcp.tool
|
|
56
|
+
# def get_song_id(keywords: str) -> dict:
|
|
57
|
+
# """
|
|
58
|
+
# 根据关键词搜索歌曲,返回第一首歌的ID
|
|
59
|
+
# :param keywords: 歌曲关键词
|
|
60
|
+
# :return: 歌曲ID
|
|
61
|
+
# """
|
|
62
|
+
# return _get_song_id(keywords)
|
|
63
|
+
|
|
64
|
+
@mcp.tool
|
|
65
|
+
def lyric(keywords: str) -> dict:
|
|
66
|
+
"""
|
|
67
|
+
根据歌名关键词搜索歌词
|
|
68
|
+
:param keywords: 歌曲关键词
|
|
69
|
+
:return: 歌词文本
|
|
70
|
+
"""
|
|
71
|
+
return _lyric(keywords)
|
|
72
|
+
|
|
73
|
+
@mcp.tool
|
|
74
|
+
def get_music_url(keywords: str, level: str = "standard") -> dict:
|
|
75
|
+
"""
|
|
76
|
+
根据关键词获取音乐在线试听url
|
|
77
|
+
:param keywords: 歌曲关键词
|
|
78
|
+
:param level: 音质等级
|
|
79
|
+
:return: 音乐url json
|
|
80
|
+
"""
|
|
81
|
+
return _get_music_url(keywords, level)
|
|
82
|
+
|
|
83
|
+
@mcp.tool
|
|
84
|
+
def get_song_comment(keywords: str) -> dict:
|
|
85
|
+
"""
|
|
86
|
+
根据歌名关键词获取歌曲评论
|
|
87
|
+
:param keywords: 歌曲关键词
|
|
88
|
+
:return: 歌曲评论json
|
|
89
|
+
"""
|
|
90
|
+
return _get_song_comment(keywords)
|
|
91
|
+
|
|
92
|
+
@mcp.tool
|
|
93
|
+
def get_similar_songs(keywords: str) -> dict:
|
|
94
|
+
"""
|
|
95
|
+
根据关键词获取相似音乐(先查id再查/simi/song)
|
|
96
|
+
:param keywords: 歌曲关键词
|
|
97
|
+
:return: 相似音乐json
|
|
98
|
+
"""
|
|
99
|
+
return _get_similar_songs(keywords)
|
|
100
|
+
|
|
101
|
+
@mcp.tool
|
|
102
|
+
def get_style_songs(style_name: str) -> dict:
|
|
103
|
+
"""
|
|
104
|
+
根据曲风名返回歌曲列表。
|
|
105
|
+
:param style_name: 曲风名
|
|
106
|
+
:return: 歌曲列表json
|
|
107
|
+
"""
|
|
108
|
+
return _get_style_songs(style_name)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@mcp.tool
|
|
115
|
+
def search_artist(keyword: str) -> dict:
|
|
116
|
+
"""
|
|
117
|
+
根据关键词搜索歌手信息
|
|
118
|
+
:param keyword: 歌手关键词
|
|
119
|
+
:return: 歌手信息字符串
|
|
120
|
+
"""
|
|
121
|
+
return _search_artist(keyword)
|
|
122
|
+
|
|
123
|
+
@mcp.tool
|
|
124
|
+
def get_artist_info(keyword: str) -> dict:
|
|
125
|
+
"""
|
|
126
|
+
根据关键词获取歌手详细信息
|
|
127
|
+
:param keyword: 歌手关键词
|
|
128
|
+
:return: 歌手详细信息json
|
|
129
|
+
"""
|
|
130
|
+
return _get_artist_info(keyword)
|
|
131
|
+
|
|
132
|
+
@mcp.tool
|
|
133
|
+
def get_artist_hot_songs(keyword: str) -> dict:
|
|
134
|
+
"""
|
|
135
|
+
根据关键词获取歌手最火的50首歌曲
|
|
136
|
+
:param keyword: 歌手关键词
|
|
137
|
+
:return: 热门歌曲json
|
|
138
|
+
"""
|
|
139
|
+
return _get_artist_hot_songs(keyword)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@mcp.tool
|
|
146
|
+
def get_playlist(id: int) -> dict:
|
|
147
|
+
"""
|
|
148
|
+
根据歌单id获取歌单详情
|
|
149
|
+
:param id: 歌单id
|
|
150
|
+
:return: 歌单详情json
|
|
151
|
+
"""
|
|
152
|
+
return _get_playlist(id)
|
|
153
|
+
|
|
154
|
+
@mcp.tool
|
|
155
|
+
def get_toplist(name: str) -> dict:
|
|
156
|
+
"""
|
|
157
|
+
根据排行榜名字获取榜单详情(先查id再查_get_playlist)
|
|
158
|
+
:param name: 榜单名称
|
|
159
|
+
:return: 榜单详情json
|
|
160
|
+
"""
|
|
161
|
+
return _get_toplist(name)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# @mcp.tool()
|
|
168
|
+
# def get_uid(nickname: str) -> dict:
|
|
169
|
+
# """
|
|
170
|
+
# 根据用户昵称获取其uid
|
|
171
|
+
# :param nickname: 用户昵称
|
|
172
|
+
# :return: 用户uid json
|
|
173
|
+
# """
|
|
174
|
+
# return _get_uid(nickname)
|
|
175
|
+
|
|
176
|
+
@mcp.tool()
|
|
177
|
+
def get_user_playlist(nickname: str) -> dict:
|
|
178
|
+
"""
|
|
179
|
+
输入用户昵称,获取用户歌单(含有歌单id)
|
|
180
|
+
:param nickname: 用户昵称
|
|
181
|
+
:return: 歌单列表json
|
|
182
|
+
"""
|
|
183
|
+
return _get_user_playlist(nickname)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@mcp.tool
|
|
190
|
+
def return_stylelist() -> dict:
|
|
191
|
+
"""
|
|
192
|
+
返回所有风格名称(不含id),从styleList.json读取。没有输入参数
|
|
193
|
+
:return: {"code": 200, "msg": "success", "data": [风格名列表]}
|
|
194
|
+
"""
|
|
195
|
+
return _return_stylelist()
|
|
196
|
+
|
|
197
|
+
@mcp.tool
|
|
198
|
+
def return_toplist_name() -> dict:
|
|
199
|
+
"""
|
|
200
|
+
返回所有排行榜名称(不含id),从toplist.json读取。没有输入参数
|
|
201
|
+
:return: {"code": 200, "msg": "success", "data": [榜单名列表]}
|
|
202
|
+
"""
|
|
203
|
+
return _return_toplist_name()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# @mcp.tool
|
|
208
|
+
# def login_anonymous() -> dict:
|
|
209
|
+
# """
|
|
210
|
+
# 游客登录,获取游客 cookie,并保存到全局变量
|
|
211
|
+
# :return: 游客 cookie 字符串
|
|
212
|
+
# """
|
|
213
|
+
# return _login_anonymous()
|
|
214
|
+
|
|
215
|
+
# @mcp.tool
|
|
216
|
+
# def login_refresh() -> dict:
|
|
217
|
+
# """
|
|
218
|
+
# 刷新登录状态,获取新的 cookie
|
|
219
|
+
# :return: 新的 cookie 字符串
|
|
220
|
+
# """
|
|
221
|
+
# return _login_refresh()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# @mcp.tool()
|
|
225
|
+
# def start_netease_music(exe_path: str) -> dict:
|
|
226
|
+
# """
|
|
227
|
+
# 启动本地网易云音乐客户端
|
|
228
|
+
# :param exe_path: 网易云音乐客户端的 exe 路径,默认 C:\\CloudMusic\\cloudmusic.exe
|
|
229
|
+
# :return: 启动结果 json
|
|
230
|
+
# """
|
|
231
|
+
# return _start_netease_music(exe_path)
|
|
232
|
+
|
|
233
|
+
@mcp.tool()
|
|
234
|
+
def open_cloud_music(exe_path: str = None) -> dict:
|
|
235
|
+
"""
|
|
236
|
+
启动本地网易云音乐客户端
|
|
237
|
+
:param exe_path: 网易云音乐客户端的 exe 路径,如果用户没提供路径就请使用默认参数: C:\\CloudMusic\\cloudmusic.exe
|
|
238
|
+
:return: 启动结果 json
|
|
239
|
+
"""
|
|
240
|
+
return _open_cloud_music(exe_path)
|
|
241
|
+
|
|
242
|
+
@mcp.tool()
|
|
243
|
+
def search_and_play_mcp(keyword: str) -> dict:
|
|
244
|
+
"""
|
|
245
|
+
搜索并播放指定歌曲。
|
|
246
|
+
:param keyword: 歌曲关键词
|
|
247
|
+
:return: 操作结果json
|
|
248
|
+
"""
|
|
249
|
+
return _search_and_play(keyword)
|
|
250
|
+
|
|
251
|
+
@mcp.tool()
|
|
252
|
+
def play_and_pause_mcp() -> dict:
|
|
253
|
+
"""
|
|
254
|
+
播放/暂停当前歌曲。
|
|
255
|
+
:return: 操作结果json
|
|
256
|
+
"""
|
|
257
|
+
return _play_and_pause()
|
|
258
|
+
|
|
259
|
+
@mcp.tool()
|
|
260
|
+
def next_song() -> dict:
|
|
261
|
+
"""
|
|
262
|
+
切换到下一首歌曲。
|
|
263
|
+
:return: 操作结果json
|
|
264
|
+
"""
|
|
265
|
+
return _next_song()
|
|
266
|
+
|
|
267
|
+
@mcp.tool()
|
|
268
|
+
def pre_song() -> dict:
|
|
269
|
+
"""
|
|
270
|
+
切换到上一首歌曲。
|
|
271
|
+
:return: 操作结果json
|
|
272
|
+
"""
|
|
273
|
+
return _pre_song()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def main():
|
|
277
|
+
mcp.run(transport="stdio")
|
|
278
|
+
|
|
279
|
+
if __name__ == "__main__":
|
|
280
|
+
mcp.run(transport="sse")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
|
|
3
|
+
if platform.system() == "Windows":
|
|
4
|
+
import uiautomation as auto
|
|
5
|
+
import time
|
|
6
|
+
import subprocess
|
|
7
|
+
import psutil
|
|
8
|
+
import pyautogui
|
|
9
|
+
|
|
10
|
+
exe_path0 = r'C:\CloudMusic\cloudmusic.exe'
|
|
11
|
+
def _open_cloud_music(exe_path: str = None):
|
|
12
|
+
"""
|
|
13
|
+
打开网易云音乐客户端。
|
|
14
|
+
exe_path: 可选,指定客户端路径。如果输入则更新默认路径。
|
|
15
|
+
"""
|
|
16
|
+
global exe_path0
|
|
17
|
+
try:
|
|
18
|
+
if exe_path:
|
|
19
|
+
exe_path0 = exe_path
|
|
20
|
+
path_to_use = exe_path0
|
|
21
|
+
found = False
|
|
22
|
+
for w in auto.GetRootControl().GetChildren():
|
|
23
|
+
pid = w.ProcessId
|
|
24
|
+
try:
|
|
25
|
+
proc = psutil.Process(pid)
|
|
26
|
+
if proc.name().lower() == 'cloudmusic.exe':
|
|
27
|
+
w.SetActive()
|
|
28
|
+
found = True
|
|
29
|
+
break
|
|
30
|
+
except Exception:
|
|
31
|
+
continue
|
|
32
|
+
if not found:
|
|
33
|
+
subprocess.Popen(path_to_use)
|
|
34
|
+
time.sleep(2) # 等待程序启动
|
|
35
|
+
return {"code": 200, "msg": "网易云音乐已启动"}
|
|
36
|
+
except Exception as e:
|
|
37
|
+
return {"code": 500, "msg": f"启动失败: {e}"}
|
|
38
|
+
|
|
39
|
+
def _search_and_play(keyword:str, timeout:float=0.2):
|
|
40
|
+
try:
|
|
41
|
+
_open_cloud_music()
|
|
42
|
+
pyautogui.click(447, 43) # 点击输入框
|
|
43
|
+
time.sleep(timeout)
|
|
44
|
+
auto.SendKeys(keyword) # 输入搜索内容,这个还是uiautomation好用
|
|
45
|
+
time.sleep(timeout)
|
|
46
|
+
pyautogui.click(380, 42) # 点击搜索框
|
|
47
|
+
time.sleep(timeout)
|
|
48
|
+
pyautogui.click(392, 190) # 点击“单曲”
|
|
49
|
+
time.sleep(1)
|
|
50
|
+
pyautogui.doubleClick(402, 339) # 双击“第一首”播放
|
|
51
|
+
return {"code": 200, "msg": f"已成功播放歌曲: {keyword}"}
|
|
52
|
+
except Exception as e:
|
|
53
|
+
return {"code": 500, "msg": f"播放歌曲失败: {e}"}
|
|
54
|
+
|
|
55
|
+
def _play_and_pause():
|
|
56
|
+
try:
|
|
57
|
+
_open_cloud_music()
|
|
58
|
+
pyautogui.click(961, 1028) # 点击播放按钮
|
|
59
|
+
return {"code": 200, "msg": "成功【播放】/【暂停】"}
|
|
60
|
+
except Exception as e:
|
|
61
|
+
return {"code": 500, "msg": f"操作失败: {e}"}
|
|
62
|
+
|
|
63
|
+
def _next_song():
|
|
64
|
+
try:
|
|
65
|
+
_open_cloud_music()
|
|
66
|
+
pyautogui.click(1025, 1032) # 点击下一首按钮
|
|
67
|
+
return {"code": 200, "msg": "切换下一首成功"}
|
|
68
|
+
except Exception as e:
|
|
69
|
+
return {"code": 500, "msg": f"操作失败: {e}"}
|
|
70
|
+
|
|
71
|
+
def _pre_song():
|
|
72
|
+
try:
|
|
73
|
+
_open_cloud_music()
|
|
74
|
+
pyautogui.click(894, 1028) # 点击上一首按钮
|
|
75
|
+
return {"code": 200, "msg": "切换上一首成功"}
|
|
76
|
+
except Exception as e:
|
|
77
|
+
return {"code": 500, "msg": f"操作失败: {e}"}
|
|
78
|
+
|
|
79
|
+
else:
|
|
80
|
+
exe_path0 = None
|
|
81
|
+
def _open_cloud_music(exe_path: str = None):
|
|
82
|
+
return {"code": 501, "msg": "本功能仅支持 Windows 环境,请本地 Local 使用 uvx ben-music-mcp@latest"}
|
|
83
|
+
|
|
84
|
+
def _search_and_play(keyword: str, timeout: float = 0.2):
|
|
85
|
+
return {"code": 501, "msg": "本功能仅支持 Windows 环境,请本地 Local 使用 uvx ben-music-mcp@latest"}
|
|
86
|
+
|
|
87
|
+
def _play_and_pause():
|
|
88
|
+
return {"code": 501, "msg": "本功能仅支持 Windows 环境,请本地 Local 使用 uvx ben-music-mcp@latest"}
|
|
89
|
+
|
|
90
|
+
def _next_song():
|
|
91
|
+
return {"code": 501, "msg": "本功能仅支持 Windows 环境,请本地 Local 使用 uvx ben-music-mcp@latest"}
|
|
92
|
+
|
|
93
|
+
def _pre_song():
|
|
94
|
+
return {"code": 501, "msg": "本功能仅支持 Windows 环境,请本地 Local 使用 uvx ben-music-mcp@latest"}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
# _open_cloud_music()
|
|
99
|
+
|
|
100
|
+
# _search_and_play("打上花火")
|
|
101
|
+
|
|
102
|
+
# _play_and_pause()
|
|
103
|
+
|
|
104
|
+
# _next_song()
|
|
105
|
+
|
|
106
|
+
_pre_song()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"MUSIC_A_T": "1762674143805", "MUSIC_R_T": "0", "MUSIC_A": "00FD2909A8CC993C4FB0FC90128C7E2E3EEC34146238C0F67438D16B51DB026C9AC74700E4270BCF0954B0454EDA13126BBD915DF4CF52EF21ACB5141059BEFA897B59C94F25484D47A8C8B257C322CB9D745805D39E61449D59DF795D936D9F5F628CE6E0D6610792EB91B0D34EBFB837357B9AC0135C97B18B1D6FB1EA248B5532FEFF6A34A5C57B4EDE76C36AACF86A542A7358C44E0F2978A3B7AA852F2A84DA48857A5CAAE3FA2D4968232C727BFD96CD7D7E7479829C726EC940B1880EFD01740C665B517699FAAEF1CA3BDD92E049A36AB793A15AD9AD75E4D691D4D821E363CAE7BF3AC9586A653999810747C974239EC1E43944EB679B3DC92CFF113BDBC9279CCDCD5271C55DD18FE58847F6AF6D5E12D1D8BB8417D99B2AEDF13B4EC9097EE893ABD6BBC56E37187D2386D061DE4288B289AD7F4068E488B691682F61F170D1DDB0E6D84A679E49677A1E529C96FE9540E0B5527B1D23C956FEAB3CB6B3647B749B1CAE39EE70CC6FFC06E5F4AC33DA9470E856C1391FD7006FB487C9537E4D11B2D469A78615B32771CAE5A725D8FD8FA172E865A236F487B665817AE5F57F4AF8781F2663CA0E5780CBBF4474D616D068125C6EAFC1741C5B844F8BB6621037BA47FC0CD9E48FA63499730DF294E62E3A2CF99319D6DD778B573CCB7B99B2AFC4CC9035540E3864862E2707B4898BD831AF07C4F0774ED9AFA3F96B94079C3F48D262C4898BD0A3E39016", "__csrf": "eabbb81b921eb9637c0b389245b3a0dc", "MUSIC_R_U": "00F9CF8A93CB06AEC0A041DB4FC98922D18884F91B648A3F78CC43943902835DB940DC984AC33ED14A019B44DCDB707C2FA8F2ED50EC556FB93B675A320C20F5E39A3EA15488819D77A922244FB3B663B4"}
|
|
@@ -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,520 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
# 全局变量保存 cookie
|
|
5
|
+
global_cookie = {}
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
def _save_cookie_to_file(cookie: dict, filename: str = "cookie.txt"):
|
|
11
|
+
"""将cookie保存到文件"""
|
|
12
|
+
path = os.path.join(os.path.dirname(__file__), filename)
|
|
13
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
14
|
+
f.write(json.dumps(cookie, ensure_ascii=False))
|
|
15
|
+
|
|
16
|
+
def _load_cookie_from_file(filename: str = "cookie.txt") -> dict:
|
|
17
|
+
"""从文件读取cookie"""
|
|
18
|
+
path = os.path.join(os.path.dirname(__file__), filename)
|
|
19
|
+
if not os.path.exists(path):
|
|
20
|
+
return {}
|
|
21
|
+
try:
|
|
22
|
+
with open(path, "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=10, 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
|
+
path = os.path.join(os.path.dirname(__file__), "toplist.json")
|
|
291
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
292
|
+
data = json.load(f)
|
|
293
|
+
toplists = data.get("toplists", [])
|
|
294
|
+
match = next((item for item in toplists if item.get("name") == name), None)
|
|
295
|
+
if not match:
|
|
296
|
+
return {"code": 404, "msg": f"未找到榜单: {name}"}
|
|
297
|
+
return _get_playlist(match["id"])
|
|
298
|
+
except Exception as e:
|
|
299
|
+
return {"code": 500, "msg": f"读取榜单失败: {e}"}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _get_similar_songs(keywords: str) -> dict:
|
|
303
|
+
"""
|
|
304
|
+
根据关键词获取相似音乐(先查id再查/simi/song)
|
|
305
|
+
:param keywords: 歌曲关键词
|
|
306
|
+
:return: 相似音乐json
|
|
307
|
+
"""
|
|
308
|
+
id_json = _get_song_id(keywords)
|
|
309
|
+
song_id = id_json.get("id", "")
|
|
310
|
+
if not song_id or not song_id.isdigit():
|
|
311
|
+
return {"code": 404, "msg": f"未找到歌曲ID,原因:{id_json}", "data": []}
|
|
312
|
+
url = f"https://ncm.nekogan.com/simi/song?id={song_id}"
|
|
313
|
+
try:
|
|
314
|
+
resp = requests.get(url, timeout=5)
|
|
315
|
+
resp.raise_for_status()
|
|
316
|
+
data = resp.json()
|
|
317
|
+
songs = data.get("songs", [])
|
|
318
|
+
result = []
|
|
319
|
+
for s in songs:
|
|
320
|
+
result.append({
|
|
321
|
+
"id": s.get("id"),
|
|
322
|
+
"name": s.get("name"),
|
|
323
|
+
"artists": [a.get("name") for a in s.get("artists", [])],
|
|
324
|
+
"album": s.get("album", {}).get("name"),
|
|
325
|
+
"duration": s.get("duration")
|
|
326
|
+
})
|
|
327
|
+
return {"code": 200, "msg": "success", "data": result}
|
|
328
|
+
except Exception as e:
|
|
329
|
+
return {"code": 500, "msg": f"请求失败: {e}", "data": []}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _get_style_songs(style_name: str) -> dict:
|
|
333
|
+
"""
|
|
334
|
+
根据曲风名获取对应曲风id,再查 /style/song?tagId=xxx,返回歌曲列表。
|
|
335
|
+
:param style_name: 曲风名
|
|
336
|
+
:return: 歌曲列表json
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
# 读取 styleList.json
|
|
340
|
+
try:
|
|
341
|
+
path = os.path.join(os.path.dirname(__file__), "styleList.json")
|
|
342
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
343
|
+
style_list = json.load(f)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
return {"code": 500, "msg": f"读取曲风列表失败: {e}", "data": {}}
|
|
346
|
+
|
|
347
|
+
tag_id = None
|
|
348
|
+
for item in style_list:
|
|
349
|
+
if item.get("tagName") == style_name:
|
|
350
|
+
tag_id = item.get("tagId")
|
|
351
|
+
break
|
|
352
|
+
if not tag_id:
|
|
353
|
+
return {"code": 404, "msg": f"未找到曲风: {style_name}", "data": {}}
|
|
354
|
+
|
|
355
|
+
url = f"https://ncm.nekogan.com/style/song?tagId={tag_id}"
|
|
356
|
+
try:
|
|
357
|
+
cookie = _get_cookie()
|
|
358
|
+
resp = requests.get(url, timeout=10, cookies=cookie)
|
|
359
|
+
resp.raise_for_status()
|
|
360
|
+
data = resp.json()
|
|
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
|
+
return {"code": 200, "msg": "success", "data": result}
|
|
388
|
+
except Exception as e:
|
|
389
|
+
return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _get_uid(nickname: str) -> dict:
|
|
394
|
+
"""
|
|
395
|
+
根据用户昵称获取其uid
|
|
396
|
+
:param nickname: 用户昵称
|
|
397
|
+
:return: 用户uid json
|
|
398
|
+
"""
|
|
399
|
+
url = f"https://ncm.nekogan.com/get/userids?nicknames={nickname}"
|
|
400
|
+
try:
|
|
401
|
+
resp = requests.get(url, timeout=5)
|
|
402
|
+
resp.raise_for_status()
|
|
403
|
+
data = resp.json()
|
|
404
|
+
# 适配 nicknames 字段结构
|
|
405
|
+
nick_dict = data.get("nicknames", {})
|
|
406
|
+
if not nick_dict:
|
|
407
|
+
return {"code": 404, "msg": "未找到用户", "data": {}}
|
|
408
|
+
# 取第一个昵称和uid
|
|
409
|
+
for n, uid in nick_dict.items():
|
|
410
|
+
return {"code": 200, "msg": "success", "data": {"uid": uid, "nickname": n}}
|
|
411
|
+
return {"code": 404, "msg": "未找到用户", "data": {}}
|
|
412
|
+
except Exception as e:
|
|
413
|
+
return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
|
|
414
|
+
|
|
415
|
+
def _get_user_playlist(nickname: str) -> dict:
|
|
416
|
+
"""
|
|
417
|
+
输入用户昵称,获取用户歌单(只保留必要字段)
|
|
418
|
+
:param nickname: 用户昵称
|
|
419
|
+
:return: 歌单列表json
|
|
420
|
+
"""
|
|
421
|
+
# 第一步:获取uid
|
|
422
|
+
uid_result = _get_uid(nickname)
|
|
423
|
+
if uid_result.get("code") != 200 or "uid" not in uid_result.get("data", {}):
|
|
424
|
+
return {"code": 404, "msg": f"未找到用户: {nickname}", "data": []}
|
|
425
|
+
uid = uid_result["data"]["uid"]
|
|
426
|
+
# 第二步:获取歌单
|
|
427
|
+
url = f"https://ncm.nekogan.com/user/playlist?uid={uid}"
|
|
428
|
+
try:
|
|
429
|
+
resp = requests.get(url, timeout=5)
|
|
430
|
+
resp.raise_for_status()
|
|
431
|
+
data = resp.json()
|
|
432
|
+
playlists = data.get("playlist", [])
|
|
433
|
+
result = []
|
|
434
|
+
for pl in playlists:
|
|
435
|
+
result.append({
|
|
436
|
+
"id": pl.get("id"),
|
|
437
|
+
"name": pl.get("name"),
|
|
438
|
+
"coverImgUrl": pl.get("coverImgUrl"),
|
|
439
|
+
"trackCount": pl.get("trackCount"),
|
|
440
|
+
"playCount": pl.get("playCount"),
|
|
441
|
+
"creator": {
|
|
442
|
+
"userId": pl.get("creator", {}).get("userId"),
|
|
443
|
+
"nickname": pl.get("creator", {}).get("nickname"),
|
|
444
|
+
"avatarUrl": pl.get("creator", {}).get("avatarUrl")
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
return {"code": 200, "msg": "success", "data": result}
|
|
448
|
+
except Exception as e:
|
|
449
|
+
return {"code": 500, "msg": f"请求失败: {e}", "data": []}
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _return_stylelist() -> dict:
|
|
453
|
+
"""
|
|
454
|
+
返回所有风格名称(不含id),从styleList.json读取。
|
|
455
|
+
:return: {"code": 200, "msg": "success", "data": [风格名列表]}
|
|
456
|
+
"""
|
|
457
|
+
try:
|
|
458
|
+
path = os.path.join(os.path.dirname(__file__), "styleList.json")
|
|
459
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
460
|
+
style_list = json.load(f)
|
|
461
|
+
names = [item.get("tagName", "") for item in style_list if item.get("tagName")]
|
|
462
|
+
return {"code": 200, "msg": "success", "data": names}
|
|
463
|
+
except Exception as e:
|
|
464
|
+
return {"code": 500, "msg": f"读取风格列表失败: {e}", "data": []}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _return_toplist_name() -> dict:
|
|
468
|
+
"""
|
|
469
|
+
返回所有排行榜名称(不含id),从toplist.json读取。
|
|
470
|
+
:return: {"code": 200, "msg": "success", "data": [榜单名列表]}
|
|
471
|
+
"""
|
|
472
|
+
try:
|
|
473
|
+
path = os.path.join(os.path.dirname(__file__), "toplist.json")
|
|
474
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
475
|
+
data = json.load(f)
|
|
476
|
+
toplists = data.get("toplists", [])
|
|
477
|
+
names = [item.get("name", "") for item in toplists if item.get("name")]
|
|
478
|
+
return {"code": 200, "msg": "success", "data": names}
|
|
479
|
+
except Exception as e:
|
|
480
|
+
return {"code": 500, "msg": f"读取榜单列表失败: {e}", "data": []}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _login_anonymous() -> dict:
|
|
485
|
+
url = "https://ncm.nekogan.com/register/anonimous"
|
|
486
|
+
try:
|
|
487
|
+
resp = requests.get(url, timeout=10)
|
|
488
|
+
resp.raise_for_status()
|
|
489
|
+
data = resp.json()
|
|
490
|
+
cookie = resp.cookies.get_dict()
|
|
491
|
+
global global_cookie
|
|
492
|
+
global_cookie = cookie
|
|
493
|
+
_save_cookie_to_file(cookie)
|
|
494
|
+
return {"code": 200, "msg": "游客登录成功", "cookie": cookie, "data": data}
|
|
495
|
+
except Exception as e:
|
|
496
|
+
return {"code": 500, "msg": f"游客登录失败: {e}", "cookie": {}, "data": {}}
|
|
497
|
+
|
|
498
|
+
def _login_refresh() -> dict:
|
|
499
|
+
url = "https://ncm.nekogan.com/login/refresh"
|
|
500
|
+
try:
|
|
501
|
+
resp = requests.get(url, timeout=10)
|
|
502
|
+
resp.raise_for_status()
|
|
503
|
+
data = resp.json()
|
|
504
|
+
cookie = resp.cookies.get_dict()
|
|
505
|
+
return {"code": 200, "msg": "登录状态刷新成功", "cookie": cookie, "data": data}
|
|
506
|
+
except Exception as e:
|
|
507
|
+
return {"code": 500, "msg": f"刷新登录失败: {e}", "cookie": {}, "data": {}}
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _start_netease_music(exe_path: str) -> dict:
|
|
511
|
+
"""
|
|
512
|
+
启动本地网易云音乐客户端
|
|
513
|
+
:param exe_path: 网易云音乐客户端的 exe 路径
|
|
514
|
+
:return: 启动结果 json
|
|
515
|
+
"""
|
|
516
|
+
try:
|
|
517
|
+
subprocess.Popen(exe_path)
|
|
518
|
+
return {"code": 200, "msg": "网易云音乐已启动", "exe_path": exe_path}
|
|
519
|
+
except Exception as e:
|
|
520
|
+
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
|
+
}
|