@neteasecloudmusicapienhanced/api 4.29.21 → 4.30.1

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.
@@ -5,67 +5,320 @@
5
5
  <meta charset="UTF-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>更新头像</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18
+ min-height: 100vh;
19
+ background: #f5f5f5;
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ padding: 20px;
24
+ }
25
+
26
+ .container {
27
+ background: white;
28
+ border-radius: 12px;
29
+ padding: 40px;
30
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
31
+ max-width: 400px;
32
+ width: 100%;
33
+ text-align: center;
34
+ }
35
+
36
+ h1 {
37
+ font-size: 24px;
38
+ font-weight: 600;
39
+ color: #333;
40
+ margin-bottom: 8px;
41
+ }
42
+
43
+ .subtitle {
44
+ font-size: 14px;
45
+ color: #666;
46
+ margin-bottom: 32px;
47
+ }
48
+
49
+ .avatar-wrapper {
50
+ position: relative;
51
+ width: 160px;
52
+ height: 160px;
53
+ margin: 0 auto 32px;
54
+ }
55
+
56
+ .avatar {
57
+ width: 100%;
58
+ height: 100%;
59
+ border-radius: 50%;
60
+ object-fit: cover;
61
+ border: 3px solid #fff;
62
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
63
+ }
64
+
65
+ .avatar-wrapper.loading .avatar {
66
+ opacity: 0.5;
67
+ }
68
+
69
+ .loading-overlay {
70
+ position: absolute;
71
+ top: 0;
72
+ left: 0;
73
+ width: 100%;
74
+ height: 100%;
75
+ border-radius: 50%;
76
+ background: rgba(255, 255, 255, 0.9);
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ opacity: 0;
81
+ transition: opacity 0.2s ease;
82
+ }
83
+
84
+ .avatar-wrapper.loading .loading-overlay {
85
+ opacity: 1;
86
+ }
87
+
88
+ .spinner {
89
+ width: 40px;
90
+ height: 40px;
91
+ border: 3px solid #e0e0e0;
92
+ border-top-color: #333;
93
+ border-radius: 50%;
94
+ animation: spin 0.8s linear infinite;
95
+ }
96
+
97
+ @keyframes spin {
98
+ to {
99
+ transform: rotate(360deg);
100
+ }
101
+ }
102
+
103
+ .upload-btn {
104
+ display: inline-block;
105
+ position: relative;
106
+ padding: 12px 28px;
107
+ background: #333;
108
+ color: white;
109
+ font-size: 15px;
110
+ font-weight: 500;
111
+ border-radius: 6px;
112
+ cursor: pointer;
113
+ transition: background 0.2s ease;
114
+ border: none;
115
+ }
116
+
117
+ .upload-btn:hover {
118
+ background: #555;
119
+ }
120
+
121
+ .upload-btn input[type="file"] {
122
+ position: absolute;
123
+ top: 0;
124
+ left: 0;
125
+ width: 100%;
126
+ height: 100%;
127
+ opacity: 0;
128
+ cursor: pointer;
129
+ }
130
+
131
+ .login-link {
132
+ display: block;
133
+ margin-top: 24px;
134
+ color: #666;
135
+ font-size: 14px;
136
+ text-decoration: none;
137
+ }
138
+
139
+ .login-link:hover {
140
+ color: #333;
141
+ text-decoration: underline;
142
+ }
143
+
144
+ .toast {
145
+ position: fixed;
146
+ bottom: 24px;
147
+ left: 50%;
148
+ transform: translateX(-50%) translateY(100px);
149
+ padding: 12px 20px;
150
+ border-radius: 6px;
151
+ font-size: 14px;
152
+ font-weight: 500;
153
+ color: white;
154
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
155
+ opacity: 0;
156
+ transition: all 0.3s ease;
157
+ z-index: 1000;
158
+ max-width: 90%;
159
+ text-align: center;
160
+ }
161
+
162
+ .toast.show {
163
+ transform: translateX(-50%) translateY(0);
164
+ opacity: 1;
165
+ }
166
+
167
+ .toast.success {
168
+ background: #10b981;
169
+ }
170
+
171
+ .toast.error {
172
+ background: #ef4444;
173
+ }
174
+
175
+ .toast.info {
176
+ background: #3b82f6;
177
+ }
178
+
179
+ .hint {
180
+ margin-top: 20px;
181
+ font-size: 12px;
182
+ color: #999;
183
+ }
184
+ </style>
8
185
  </head>
9
186
 
10
187
  <body>
11
- <div>
12
- <a href="/qrlogin-nocookie.html">
13
- 如果没登录,请先登录
188
+ <div class="container">
189
+ <h1>更新头像</h1>
190
+ <p class="subtitle">选择一张图片作为您的新头像</p>
191
+
192
+ <div class="avatar-wrapper" id="avatarWrapper">
193
+ <img id="avatar" class="avatar" src="" alt="头像" />
194
+ <div class="loading-overlay">
195
+ <div class="spinner"></div>
196
+ </div>
197
+ </div>
198
+
199
+ <label class="upload-btn">
200
+ 选择图片
201
+ <input id="file" type="file" accept="image/*" />
202
+ </label>
203
+
204
+ <a href="/qrlogin-nocookie.html" class="login-link">
205
+ 还没有登录?点击登录
14
206
  </a>
207
+
208
+ <p class="hint">支持 JPG、PNG 格式,建议尺寸 200x200</p>
15
209
  </div>
16
- <input id="file" type="file" />
17
- <img id="avatar" style="height: 200px; width: 200px; border-radius: 50%" />
18
- <script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js
19
- "></script>
210
+
211
+ <div id="toast" class="toast"></div>
212
+
213
+ <script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
20
214
  <script>
21
215
  main()
22
216
 
23
217
  async function main() {
24
- document.querySelector('input[type="file"]').addEventListener(
25
- 'change',
26
- function (e) {
27
- var file = this.files[0]
28
- upload(file)
29
- },
30
- false,
31
- )
32
- const res = await axios({
33
- url: `/user/detail?uid=32953014&timestamp=${Date.now()}`,
34
- withCredentials: true, //跨域的话必须设置
35
- })
36
- document.querySelector('#avatar').src = res.data.profile.avatarUrl
218
+ const fileInput = document.querySelector('input[type="file"]');
219
+ const avatarWrapper = document.getElementById('avatarWrapper');
220
+
221
+ fileInput.addEventListener('change', function(e) {
222
+ const file = this.files[0];
223
+ if (file) {
224
+ upload(file);
225
+ }
226
+ }, false);
227
+
228
+ try {
229
+ showToast('正在加载头像...', 'info');
230
+ avatarWrapper.classList.add('loading');
231
+ const res = await axios({
232
+ url: `/user/detail?uid=32953014&timestamp=${Date.now()}`,
233
+ withCredentials: true
234
+ });
235
+ document.querySelector('#avatar').src = res.data.profile.avatarUrl;
236
+ hideToast();
237
+ } catch (error) {
238
+ hideToast();
239
+ showToast('加载头像失败,请刷新页面重试', 'error');
240
+ console.error('加载头像失败:', error);
241
+ } finally {
242
+ avatarWrapper.classList.remove('loading');
243
+ }
37
244
  }
38
245
 
39
246
  async function upload(file) {
40
- var formData = new FormData()
41
- formData.append('imgFile', file)
42
- const imgSize = await getImgSize(file)
43
- const res = await axios({
44
- method: 'post',
45
- url: `/avatar/upload?cookie=${localStorage.getItem('cookie')}&imgSize=${imgSize.width
46
- }&imgX=0&imgY=0&timestamp=${Date.now()}`,
47
- headers: {
48
- 'Content-Type': 'multipart/form-data',
49
- },
50
- data: formData,
51
- })
52
- document.querySelector('#avatar').src = res.data.data.url
247
+ const avatarWrapper = document.getElementById('avatarWrapper');
248
+
249
+ if (!file.type.startsWith('image/')) {
250
+ showToast('请选择图片文件', 'error');
251
+ return;
252
+ }
253
+
254
+ try {
255
+ showToast('正在上传头像...', 'info');
256
+ avatarWrapper.classList.add('loading');
257
+
258
+ var formData = new FormData();
259
+ formData.append('imgFile', file);
260
+ const imgSize = await getImgSize(file);
261
+
262
+ const res = await axios({
263
+ method: 'post',
264
+ url: `/avatar/upload?cookie=${localStorage.getItem('cookie')}&imgSize=${imgSize.width}&imgX=0&imgY=0&timestamp=${Date.now()}`,
265
+ headers: {
266
+ 'Content-Type': 'multipart/form-data',
267
+ },
268
+ data: formData,
269
+ });
270
+
271
+ document.querySelector('#avatar').src = res.data.data.url;
272
+ showToast('头像更新成功!', 'success');
273
+ } catch (error) {
274
+ console.error('上传失败:', error);
275
+ const errorMsg = error.response?.data?.message || error.message || '上传失败,请重试';
276
+ showToast(errorMsg, 'error');
277
+ } finally {
278
+ avatarWrapper.classList.remove('loading');
279
+ }
53
280
  }
281
+
54
282
  function getImgSize(file) {
55
283
  return new Promise((resolve, reject) => {
56
- let reader = new FileReader()
57
- reader.readAsDataURL(file)
58
- reader.onload = function (theFile) {
59
- let image = new Image()
60
- image.src = theFile.target.result
61
- image.onload = function () {
284
+ let reader = new FileReader();
285
+ reader.readAsDataURL(file);
286
+ reader.onload = function(theFile) {
287
+ let image = new Image();
288
+ image.src = theFile.target.result;
289
+ image.onload = function() {
62
290
  resolve({
63
291
  width: this.width,
64
292
  height: this.height,
65
- })
66
- }
67
- }
68
- })
293
+ });
294
+ };
295
+ image.onerror = function() {
296
+ reject(new Error('图片加载失败'));
297
+ };
298
+ };
299
+ reader.onerror = function() {
300
+ reject(new Error('文件读取失败'));
301
+ };
302
+ });
303
+ }
304
+
305
+ function showToast(message, type = 'info') {
306
+ const toast = document.getElementById('toast');
307
+ toast.textContent = message;
308
+ toast.className = `toast ${type}`;
309
+
310
+ requestAnimationFrame(() => {
311
+ toast.classList.add('show');
312
+ });
313
+
314
+ setTimeout(() => {
315
+ toast.classList.remove('show');
316
+ }, 3000);
317
+ }
318
+
319
+ function hideToast() {
320
+ const toast = document.getElementById('toast');
321
+ toast.classList.remove('show');
69
322
  }
70
323
  </script>
71
324
  </body>
package/public/cloud.html CHANGED
@@ -4,26 +4,135 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>云盘上传</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ min-height: 100vh;
17
+ background: #f5f5f5;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 800px;
23
+ margin: 0 auto;
24
+ background: white;
25
+ border-radius: 12px;
26
+ padding: 32px;
27
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
28
+ }
29
+
30
+ h1 {
31
+ font-size: 24px;
32
+ font-weight: 600;
33
+ color: #333;
34
+ margin-bottom: 24px;
35
+ }
36
+
37
+ .login-link {
38
+ display: block;
39
+ margin-bottom: 24px;
40
+ color: #666;
41
+ font-size: 14px;
42
+ text-decoration: none;
43
+ }
44
+
45
+ .login-link:hover {
46
+ color: #333;
47
+ text-decoration: underline;
48
+ }
49
+
50
+ .upload-section {
51
+ margin-bottom: 32px;
52
+ }
53
+
54
+ .upload-btn {
55
+ display: inline-block;
56
+ padding: 12px 28px;
57
+ background: #333;
58
+ color: white;
59
+ font-size: 15px;
60
+ font-weight: 500;
61
+ border-radius: 6px;
62
+ cursor: pointer;
63
+ transition: background 0.2s ease;
64
+ border: none;
65
+ }
66
+
67
+ .upload-btn:hover {
68
+ background: #555;
69
+ }
70
+
71
+ .upload-btn input[type="file"] {
72
+ display: none;
73
+ }
74
+
75
+ .songs-list {
76
+ list-style: none;
77
+ }
78
+
79
+ .song-item {
80
+ padding: 12px 16px;
81
+ border-bottom: 1px solid #eee;
82
+ font-size: 14px;
83
+ color: #333;
84
+ }
85
+
86
+ .song-item:last-child {
87
+ border-bottom: none;
88
+ }
89
+
90
+ .empty-state {
91
+ text-align: center;
92
+ padding: 40px 20px;
93
+ color: #999;
94
+ font-size: 14px;
95
+ }
96
+
97
+ .loading {
98
+ text-align: center;
99
+ padding: 20px;
100
+ color: #666;
101
+ }
102
+ </style>
7
103
  </head>
8
104
 
9
105
  <body>
10
- <div>
11
- <a href="/qrlogin-nocookie.html"> 如果没登录,请先登录 </a>
12
- </div>
13
- <input id="file" type="file" multiple />
14
- <div id="app">
15
- <ul>
16
- <li v-for="(item,index) in songs" :key="index">{{item.songName}}</li>
17
- </ul>
106
+ <div class="container">
107
+ <h1>云盘上传</h1>
108
+ <a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
109
+
110
+ <div class="upload-section">
111
+ <label class="upload-btn">
112
+ 选择文件(支持多选)
113
+ <input id="file" type="file" multiple accept="audio/*" />
114
+ </label>
115
+ </div>
116
+
117
+ <div id="app">
118
+ <div v-if="loading" class="loading">加载中...</div>
119
+ <ul v-else-if="songs.length > 0" class="songs-list">
120
+ <li v-for="(item, index) in songs" :key="index" class="song-item">
121
+ {{ item.songName }}
122
+ </li>
123
+ </ul>
124
+ <div v-else class="empty-state">暂无云盘歌曲</div>
125
+ </div>
18
126
  </div>
19
127
 
20
128
  <script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
21
- <script src="https://fastly.jsdelivr.net/npm/vue"></script>
129
+ <script src="https://fastly.jsdelivr.net/npm/vue@3"></script>
22
130
  <script>
23
131
  const app = Vue.createApp({
24
132
  data() {
25
133
  return {
26
134
  songs: [],
135
+ loading: false,
27
136
  }
28
137
  },
29
138
  created() {
@@ -31,19 +140,23 @@
31
140
  },
32
141
  methods: {
33
142
  getData() {
34
- console.info('getdata')
35
- const _this = this
143
+ this.loading = true
36
144
  axios({
37
- url: `/user/cloud?time=${Date.now()}&cookie=${localStorage.getItem(
38
- 'cookie',
39
- )}`,
40
- }).then((res) => {
41
- console.info(res.data)
42
- _this.songs = res.data.data
145
+ url: `/user/cloud?time=${Date.now()}&cookie=${localStorage.getItem('cookie')}`,
43
146
  })
147
+ .then((res) => {
148
+ this.songs = res.data.data || []
149
+ })
150
+ .catch((err) => {
151
+ console.error('获取云盘数据失败:', err)
152
+ })
153
+ .finally(() => {
154
+ this.loading = false
155
+ })
44
156
  },
45
157
  },
46
158
  }).mount('#app')
159
+
47
160
  const fileUpdateTime = {}
48
161
  let fileLength = 0
49
162
 
@@ -51,51 +164,46 @@
51
164
  document
52
165
  .querySelector('input[type="file"]')
53
166
  .addEventListener('change', function (e) {
54
- console.info(this.files)
55
- let currentIndx = 0
56
- fileLength = this.files.length
57
- for (const item of this.files) {
58
- currentIndx += 1
59
- upload(item, currentIndx)
167
+ const files = this.files
168
+ if (files.length === 0) return
169
+
170
+ fileLength = files.length
171
+ for (let i = 0; i < files.length; i++) {
172
+ upload(files[i], i + 1)
60
173
  }
61
174
  })
62
175
  }
63
176
  main()
64
177
 
65
- function upload(file, currentIndx) {
178
+ function upload(file, currentIndex) {
66
179
  var formData = new FormData()
67
180
  formData.append('songFile', file)
181
+
68
182
  axios({
69
183
  method: 'post',
70
- url: `/cloud?time=${Date.now()}&cookie=${localStorage.getItem(
71
- 'cookie',
72
- )}`,
184
+ url: `/cloud?time=${Date.now()}&cookie=${localStorage.getItem('cookie')}`,
73
185
  headers: {
74
186
  'Content-Type': 'multipart/form-data',
75
187
  },
76
188
  data: formData,
77
189
  })
78
190
  .then((res) => {
79
- console.info(`${file.name} 上传成功`)
80
- if (currentIndx >= fileLength) {
81
- console.info('上传完毕')
191
+ console.log(`${file.name} 上传成功`)
192
+ if (currentIndex >= fileLength) {
193
+ console.log('所有文件上传完毕')
82
194
  }
83
195
  app.getData()
84
196
  })
85
- .catch(async (err) => {
86
- console.info(err)
87
- console.info(fileUpdateTime)
88
- fileUpdateTime[file.name]
89
- ? (fileUpdateTime[file.name] += 1)
90
- : (fileUpdateTime[file.name] = 1)
197
+ .catch((err) => {
198
+ console.error(`${file.name} 上传失败:`, err)
199
+ fileUpdateTime[file.name] = (fileUpdateTime[file.name] || 0) + 1
91
200
  if (fileUpdateTime[file.name] >= 4) {
92
- console.error(`丢,这首歌怎么都传不上:${file.name}`)
201
+ console.error(`文件 ${file.name} 上传失败次数过多,已停止重试`)
93
202
  return
94
203
  } else {
95
- console.error(`${file.name} 失败 ${fileUpdateTime[file.name]} 次`)
204
+ console.error(`${file.name} 上传失败 ${fileUpdateTime[file.name]} 次,正在重试...`)
205
+ upload(file, currentIndex)
96
206
  }
97
- // await login()
98
- upload(file, currentIndx)
99
207
  })
100
208
  }
101
209
  </script>
@@ -3633,6 +3633,14 @@ type='1009' 获取其 id, 如`/search?keywords= 代码时间 &type=1009`
3633
3633
 
3634
3634
  **调用例子 :** `/musician/tasks/new`
3635
3635
 
3636
+ ### 音乐人黑胶会员任务
3637
+
3638
+ 说明 : 音乐人登录后调用此接口 , 可获取音乐人黑胶会员任务。返回的数据中`missionStatus`字段为任务状态,100 表示任务完成。
3639
+
3640
+ **接口地址 :** `/musician/vip/tasks`
3641
+
3642
+ **调用例子 :** `/musician/vip/tasks`
3643
+
3636
3644
  ### 账号云豆数
3637
3645
 
3638
3646
  说明 : 音乐人登录后调用此接口 , 可获取账号云豆数
@@ -4875,6 +4883,43 @@ let data = encodeURIComponent(
4875
4883
 
4876
4884
  **调用例子:** `/vip/sign/info`
4877
4885
 
4886
+
4887
+ ### 用户的创建歌单列表
4888
+
4889
+ 说明 : 调用此接口, 传入用户id, 获取用户的创建歌单列表
4890
+
4891
+ **必选参数 :**
4892
+
4893
+ `uid`: 用户 id
4894
+
4895
+ **可选参数 :**
4896
+
4897
+ `limit` : 返回数量 , 默认为 100
4898
+
4899
+ `offset` : 偏移数量,用于分页 ,如 :( 页数 -1)\*30, 其中 30 为 limit 的值 , 默认为 0
4900
+
4901
+ **接口地址 :** `/user/playlist/create`
4902
+
4903
+ **调用例子 :** `/user/playlist/create?uid=32953014`
4904
+
4905
+ ### 用户的收藏歌单列表
4906
+
4907
+ 说明 : 调用此接口, 传入用户id, 获取用户的收藏歌单列表
4908
+
4909
+ **必选参数 :**
4910
+
4911
+ `uid`: 用户 id
4912
+
4913
+ **可选参数 :**
4914
+
4915
+ `limit` : 返回数量 , 默认为 100
4916
+
4917
+ `offset` : 偏移数量,用于分页 ,如 :( 页数 -1)\*30, 其中 30 为 limit 的值 , 默认为 0
4918
+
4919
+ **接口地址 :** `/user/playlist/collect`
4920
+
4921
+ **调用例子 :** `/user/playlist/collect?uid=32953014`
4922
+
4878
4923
  ## 离线访问此文档
4879
4924
 
4880
4925
  此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
@@ -36,12 +36,12 @@
36
36
  }
37
37
  </script>
38
38
  <!-- Global site tag (gtag.js) - Google Analytics -->
39
- <script async src="https://www.googletagmanager.com/gtag/js?id=UA-139996012-1"></script>
39
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-BPRGR23JEG"></script>
40
40
  <script>
41
41
  window.dataLayer = window.dataLayer || [];
42
- function gtag(){dataLayer.push(arguments);}
42
+ function gtag() { dataLayer.push(arguments); }
43
43
  gtag('js', new Date());
44
44
 
45
- gtag('config', 'UA-139996012-1');
45
+ gtag('config', 'G-BPRGR23JEG');
46
46
  </script>
47
47
  </html>