mirror of
https://github.com/2061360308/NeteaseCloudMusic_PythonSDK.git
synced 2024-11-21 14:38:19 +00:00
fix:add tests and correct some errors
This commit is contained in:
parent
2cbc4b0330
commit
76f285a59e
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.idea/
|
||||
__pycache__/
|
||||
.env
|
||||
.pytest_cache/
|
||||
cookie_storage
|
68
README.md
68
README.md
@ -1,5 +1,13 @@
|
||||
# NeteaseCloudMusic_PythonSDK
|
||||
> 基于 [ NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 封装的 Python SDK
|
||||
> 基于 [ NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 封装的 Python SDK。
|
||||
>
|
||||
> 网易云API Python版本。
|
||||
>
|
||||
> 现已同步原项目接口且测试通过的有200多个
|
||||
>
|
||||
> 已发布到PyPi,可直接使用pip安装
|
||||
>
|
||||
> 项目地址:[GitHub](https://github.com/2061360308/NeteaseCloudMusic_PythonSDK)
|
||||
|
||||
![](https://img.shields.io/badge/py_mini_racer-@0.6.0-red.svg)
|
||||
![License](https://img.shields.io/badge/license-MIT-yellow)
|
||||
@ -12,20 +20,37 @@
|
||||
- 通过 `py_mini_racer` 调用 `NeteaseCloudMusicApi_V8` 的 `js` 方法。进一步进行了简单封装。
|
||||
|
||||
### 使用
|
||||
- 克隆项目 `git clone git@github.com:2061360308/NeteaseCloudMusic_PythonSDK.git`
|
||||
- 安装依赖 `pip install -r requirements.txt`
|
||||
- 安装 `pip install NeteaseCloudMusic`
|
||||
- 导入API进行使用(具体查看`test.py`中的示例)
|
||||
```python
|
||||
from main import NeteaseCloudMusicApi
|
||||
from NeteaseCloudMusic import NeteaseCloudMusicApi, api_help, api_list
|
||||
import os
|
||||
|
||||
netease_cloud_music_api = NeteaseCloudMusicApi() # 初始化API
|
||||
netease_cloud_music_api.cookie = os.getenv("COOKIE") # 设置cookie
|
||||
response = netease_cloud_music_api.api("song_url_v1", {"id": 33894312, "level": "exhigh"}) # 调用API
|
||||
response = netease_cloud_music_api.request("song_url_v1", {"id": 33894312, "level": "exhigh"}) # 调用API
|
||||
|
||||
# 获取帮助
|
||||
print(api_help())
|
||||
print(api_help('song_url_v1'))
|
||||
# 获取API列表
|
||||
print(api_list())
|
||||
```
|
||||
|
||||
> 注意: api(self, name, query=None) 的第一个参数为API名称,第二个参数为API参数,具体API名称和参数请参考 [NeteaseCloudMusicApi文档](https://docs.neteasecloudmusicapi.binaryify.com),name支持`/song/url/v1`和`song_url_v1`两种写法。
|
||||
> 注意: request(self, name, query=None) 的第一个参数为API名称,第二个参数为API参数,具体API名称和参数请参考 [NeteaseCloudMusicApi文档](https://docs.neteasecloudmusicapi.binaryify.com),name支持`/song/url/v1`和`song_url_v1`两种写法。
|
||||
|
||||
|
||||
|
||||
![test_report.png](./docs/test_report.png)
|
||||
|
||||
|
||||
### 开发
|
||||
- 克隆项目 `git clone git@github.com:2061360308/NeteaseCloudMusic_PythonSDK.git`
|
||||
- 安装依赖 `pip install -r requirements.txt`
|
||||
- 目录/文件说明
|
||||
├── package 项目包根目录
|
||||
├── test_gender 生成测试代码的脚本
|
||||
├── test.py 手动测试/ 使用示例
|
||||
|
||||
|
||||
### 改进
|
||||
@ -41,4 +66,33 @@ response = netease_cloud_music_api.api("song_url_v1", {"id": 33894312, "level":
|
||||
- register_anonimous.js
|
||||
- verify_getQr.js
|
||||
|
||||
> 精力有限,大部分API未测试,欢迎提交PR
|
||||
> 以下api未测试(这些接口测试起来比较繁琐)
|
||||
>
|
||||
- /user/replacephone
|
||||
- /audio/match
|
||||
- /rebind
|
||||
- /nickname/check
|
||||
- /activate/init/profile
|
||||
- /cellphone/existence/check
|
||||
- /register/cellphone
|
||||
- /captcha/verify
|
||||
- /captcha/sent
|
||||
- /login/refresh
|
||||
- /logout
|
||||
- /user/update
|
||||
- /pl/count
|
||||
- /playlist/update
|
||||
- /playlist/desc/update
|
||||
- /playlist/name/update
|
||||
- /playlist/tags/update
|
||||
- /event/forward
|
||||
- /event/del
|
||||
- /share/resource
|
||||
- /send/text
|
||||
- /send/playlist
|
||||
- /playlist/create
|
||||
- /playlist/tracks
|
||||
- /daily_signin
|
||||
- /fm_trash
|
||||
|
||||
### 欢迎提交PR
|
||||
|
2795
api_test.py
Normal file
2795
api_test.py
Normal file
File diff suppressed because it is too large
Load Diff
0
docs/.nojekyll
Normal file
0
docs/.nojekyll
Normal file
4525
docs/README.md
Normal file
4525
docs/README.md
Normal file
File diff suppressed because it is too large
Load Diff
13
docs/_coverpage.md
Normal file
13
docs/_coverpage.md
Normal file
@ -0,0 +1,13 @@
|
||||
# 网易云音乐 API
|
||||
|
||||
> 网易云音乐 NodeJS 版 API
|
||||
|
||||
- 全部接口已升级到最新
|
||||
- 具备登录接口,多达200多个接口
|
||||
- 更完善的文档
|
||||
|
||||
|
||||
[GitHub](https://github.com/Binaryify/NeteaseCloudMusicApi)
|
||||
[Get Started](#neteasecloudmusicapi)
|
||||
|
||||
![color](#ffffff)
|
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
docs/icon.png
Normal file
BIN
docs/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
46
docs/index.html
Normal file
46
docs/index.html
Normal file
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="KEYWords" contect="网易云音乐,网易云音乐 api,网易云音乐 nodejs,网易云音乐 node.js">
|
||||
<meta name="description" content="网易云音乐 NodeJS 版 API">
|
||||
<title>网易云音乐 NodeJS 版 API</title>
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<meta name="description" content="Description">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta name="referrer" content="never">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
|
||||
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
|
||||
<script>
|
||||
(adsbygoogle = window.adsbygoogle || []).push({
|
||||
google_ad_client: "ca-pub-5159844745975514",
|
||||
enable_page_level_ads: true
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: '网易云音乐 API',
|
||||
repo: 'https://github.com/Binaryify/NeteaseCloudMusicApi',
|
||||
coverpage: true
|
||||
}
|
||||
</script>
|
||||
<script src="https://unpkg.com/docsify@4.11.3/lib/docsify.min.js"></script>
|
||||
<script>
|
||||
if (typeof navigator.serviceWorker !== 'undefined') {
|
||||
navigator.serviceWorker.register('sw.js')
|
||||
}
|
||||
</script>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-139996012-1"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-139996012-1');
|
||||
</script>
|
||||
</html>
|
90
docs/sw.js
Normal file
90
docs/sw.js
Normal file
@ -0,0 +1,90 @@
|
||||
/* ===========================================================
|
||||
* docsify sw.js
|
||||
* ===========================================================
|
||||
* Copyright 2016 @huxpro
|
||||
* Licensed under Apache 2.0
|
||||
* Register service worker.
|
||||
* ========================================================== */
|
||||
|
||||
const RUNTIME = 'docsify'
|
||||
const HOSTNAME_WHITELIST = [
|
||||
self.location.hostname,
|
||||
'fonts.gstatic.com',
|
||||
'fonts.googleapis.com',
|
||||
'unpkg.com',
|
||||
]
|
||||
|
||||
// The Util Function to hack URLs of intercepted requests
|
||||
const getFixedUrl = (req) => {
|
||||
var now = Date.now()
|
||||
var url = new URL(req.url)
|
||||
|
||||
// 1. fixed http URL
|
||||
// Just keep syncing with location.protocol
|
||||
// fetch(httpURL) belongs to active mixed content.
|
||||
// And fetch(httpRequest) is not supported yet.
|
||||
url.protocol = self.location.protocol
|
||||
|
||||
// 2. add query for caching-busting.
|
||||
// Github Pages served with Cache-Control: max-age=600
|
||||
// max-age on mutable content is error-prone, with SW life of bugs can even extend.
|
||||
// Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
|
||||
// Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
|
||||
if (url.hostname === self.location.hostname) {
|
||||
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
|
||||
}
|
||||
return url.href
|
||||
}
|
||||
|
||||
/**
|
||||
* @Lifecycle Activate
|
||||
* New one activated when old isnt being used.
|
||||
*
|
||||
* waitUntil(): activating ====> activated
|
||||
*/
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(self.clients.claim())
|
||||
})
|
||||
|
||||
/**
|
||||
* @Functional Fetch
|
||||
* All network requests are being intercepted here.
|
||||
*
|
||||
* void respondWith(Promise<Response> r)
|
||||
*/
|
||||
self.addEventListener('fetch', (event) => {
|
||||
// Skip some of cross-origin requests, like those for Google Analytics.
|
||||
if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
|
||||
// Stale-while-revalidate
|
||||
// similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
|
||||
// Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
|
||||
const cached = caches.match(event.request)
|
||||
const fixedUrl = getFixedUrl(event.request)
|
||||
const fetched = fetch(fixedUrl, { cache: 'no-store' })
|
||||
const fetchedCopy = fetched.then((resp) => resp.clone())
|
||||
|
||||
// Call respondWith() with whatever we get first.
|
||||
// If the fetch fails (e.g disconnected), wait for the cache.
|
||||
// If there’s nothing in cache, wait for the fetch.
|
||||
// If neither yields a response, return offline pages.
|
||||
event.respondWith(
|
||||
Promise.race([fetched.catch((_) => cached), cached])
|
||||
.then((resp) => resp || fetched)
|
||||
.catch((_) => {
|
||||
/* eat any errors */
|
||||
}),
|
||||
)
|
||||
|
||||
// Update the cache with the version we fetched (only for ok status)
|
||||
event.waitUntil(
|
||||
Promise.all([fetchedCopy, caches.open(RUNTIME)])
|
||||
.then(
|
||||
([response, cache]) =>
|
||||
response.ok && cache.put(event.request, response),
|
||||
)
|
||||
.catch((_) => {
|
||||
/* eat any errors */
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
BIN
docs/test_report.png
Normal file
BIN
docs/test_report.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
1430
docs/v2.md
Normal file
1430
docs/v2.md
Normal file
File diff suppressed because it is too large
Load Diff
95
main.py
95
main.py
@ -1,95 +0,0 @@
|
||||
import json
|
||||
import os.path
|
||||
import socket
|
||||
from pprint import pprint
|
||||
import requests
|
||||
from py_mini_racer import py_mini_racer
|
||||
|
||||
|
||||
class NeteaseCloudMusicApi:
|
||||
def __init__(self):
|
||||
with open('NeteaseCloudMusicApi.js', 'r', encoding='utf-8') as file:
|
||||
js_code = file.read()
|
||||
self.ctx = py_mini_racer.MiniRacer()
|
||||
self.ctx.eval(js_code)
|
||||
|
||||
self.DEBUG = False
|
||||
|
||||
self.__cookie = None
|
||||
self.__ip = None
|
||||
|
||||
@staticmethod
|
||||
def get_local_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('10.255.255.255', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
print("get local ip error")
|
||||
IP = "116.25.146.177"
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
@property
|
||||
def cookie(self):
|
||||
if self.__cookie is None:
|
||||
if os.path.isfile("cookie_storage"):
|
||||
with open("cookie_storage", "r", encoding='utf-8') as f:
|
||||
self.__cookie = f.read()
|
||||
else:
|
||||
raise Exception("cookie not found")
|
||||
else:
|
||||
return self.__cookie
|
||||
|
||||
@cookie.setter
|
||||
def cookie(self, cookie):
|
||||
self.__cookie = cookie
|
||||
with open("cookie_storage", "w+", encoding='utf-8') as f:
|
||||
f.write(cookie)
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
if self.__ip is None:
|
||||
self.__ip = self.get_local_ip()
|
||||
return self.__ip
|
||||
|
||||
def getRequestParam(self, name, query):
|
||||
result = self.ctx.call('NeteaseCloudMusicApi', name, query) # 拿到请求头和请求参数
|
||||
|
||||
if result.get("error"):
|
||||
raise Exception(result.get("error"))
|
||||
|
||||
if self.DEBUG:
|
||||
print("RequestParam:")
|
||||
for item in result.keys():
|
||||
print(f" - {item}:{result[item]}")
|
||||
# pprint(json.dumps(result))
|
||||
with open('result.json', 'w', encoding='utf-8') as file:
|
||||
file.write(json.dumps(result))
|
||||
|
||||
return result
|
||||
|
||||
def api(self, name, query=None):
|
||||
if query is None:
|
||||
query = {}
|
||||
if query.get("cookie") is None:
|
||||
query["cookie"] = self.cookie
|
||||
|
||||
if query.get("realIP") is None:
|
||||
query["realIP"] = self.ip
|
||||
else:
|
||||
query["realIP"] = query.get("realIP")
|
||||
|
||||
result = self.getRequestParam(name, query)
|
||||
|
||||
param_data = {}
|
||||
for item in result["data"].split("&"):
|
||||
param_data[item.split("=")[0]] = item.split("=")[1]
|
||||
|
||||
if self.DEBUG:
|
||||
print(f" - param_data:{param_data}")
|
||||
|
||||
response = requests.post(result["url"], data=param_data, headers=result["headers"])
|
||||
return response
|
7
package/LICENSE
Normal file
7
package/LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright 2023
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
package/MANIFEST.in
Normal file
1
package/MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
include README.md LICENSE
|
@ -26,9 +26,8 @@ if (typeof window === "undefined") {
|
||||
Object.entries(queryString).forEach(function ([key, value]) {
|
||||
params[key] = value;
|
||||
});
|
||||
console.log("来了个对象", params);
|
||||
} else {
|
||||
console.log("来了个解析不了的", queryString);
|
||||
console.log("来了个解析不了的~~~", queryString);
|
||||
}
|
||||
|
||||
this.get = function (name) {
|
||||
@ -46,7 +45,6 @@ if (typeof window === "undefined") {
|
||||
|
||||
function encodeURIComponent(str) {
|
||||
var result = "";
|
||||
console.log("请求:", str);
|
||||
|
||||
if (str !== undefined && str !== null) {
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
@ -66,8 +64,17 @@ if (typeof window === "undefined") {
|
||||
}
|
||||
}
|
||||
|
||||
var NeteaseCloudMusicApi;
|
||||
/******/ (() => { // webpackBootstrap
|
||||
(function webpackUniversalModuleDefinition(root, factory) {
|
||||
if(typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = factory();
|
||||
else if(typeof define === 'function' && define.amd)
|
||||
define([], factory);
|
||||
else if(typeof exports === 'object')
|
||||
exports["NeteaseCloudMusicApi"] = factory();
|
||||
else
|
||||
root["NeteaseCloudMusicApi"] = factory();
|
||||
})(this, () => {
|
||||
return /******/ (() => { // webpackBootstrap
|
||||
/******/ var __webpack_modules__ = ({
|
||||
|
||||
/***/ 452:
|
||||
@ -35756,18 +35763,6 @@ pki.verifyCertificateChain = function(caStore, chain, options) {
|
||||
/******/ }
|
||||
/******/
|
||||
/************************************************************************/
|
||||
/******/ /* webpack/runtime/compat get default export */
|
||||
/******/ (() => {
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = (module) => {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ () => (module['default']) :
|
||||
/******/ () => (module);
|
||||
/******/ __webpack_require__.d(getter, { a: getter });
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/define property getters */
|
||||
/******/ (() => {
|
||||
/******/ // define getter functions for harmony exports
|
||||
@ -36513,40 +36508,22 @@ function createRandomString(len) {
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/check_music.js
|
||||
// 歌曲可用性
|
||||
|
||||
/* harmony default export */ const check_music = ((query, request) => {
|
||||
const data = {
|
||||
ids: '[' + parseInt(query.id) + ']',
|
||||
ids: "[" + parseInt(query.id) + "]",
|
||||
br: parseInt(query.br || 999000),
|
||||
}
|
||||
};
|
||||
return request(
|
||||
'POST',
|
||||
"POST",
|
||||
`https://music.163.com/weapi/song/enhance/player/url`,
|
||||
data,
|
||||
{
|
||||
crypto: 'weapi',
|
||||
crypto: "weapi",
|
||||
cookie: query.cookie,
|
||||
proxy: query.proxy,
|
||||
realIP: query.realIP,
|
||||
},
|
||||
).then((response) => {
|
||||
let playable = false
|
||||
if (response.body.code == 200) {
|
||||
if (response.body.data[0].code == 200) {
|
||||
playable = true
|
||||
}
|
||||
}
|
||||
if (playable) {
|
||||
response.body = { code: 200, success: true, message: 'ok' }
|
||||
return response
|
||||
} else {
|
||||
// response.status = 404
|
||||
response.body = { code: 200, success: false, message: '亲爱的,暂无版权' }
|
||||
return response
|
||||
// return Promise.reject(response)
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/cloudsearch.js
|
||||
@ -37927,7 +37904,7 @@ const hug_comment_resourceTypeMap = config_namespaceObject.A;
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./package.json
|
||||
const package_namespaceObject = {};
|
||||
const package_namespaceObject = {"i8":"0.1.0"};
|
||||
;// CONCATENATED MODULE: ./module/inner_version.js
|
||||
|
||||
/* harmony default export */ const inner_version = ((query, request) => {
|
||||
@ -37938,7 +37915,7 @@ const package_namespaceObject = {};
|
||||
body: {
|
||||
code: 200,
|
||||
data: {
|
||||
version: package_namespaceObject.version,
|
||||
version: package_namespaceObject.i8,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -38221,13 +38198,12 @@ const package_namespaceObject = {};
|
||||
|
||||
// EXTERNAL MODULE: ./node_modules/crypto-js/index.js
|
||||
var crypto_js = __webpack_require__(1354);
|
||||
var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
;// CONCATENATED MODULE: ./module/login_cellphone.js
|
||||
// 手机登录
|
||||
|
||||
|
||||
|
||||
/* harmony default export */ const login_cellphone = (async (query, request) => {
|
||||
/* harmony default export */ const login_cellphone = ((query, request) => {
|
||||
query.cookie.os = 'ios'
|
||||
query.cookie.appver = '8.10.90'
|
||||
const data = {
|
||||
@ -38238,10 +38214,10 @@ var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
? query.captcha
|
||||
: query.md5_password ||
|
||||
//crypto.createHash('md5').update(query.password).digest('hex'),
|
||||
crypto_js_default().MD5(query.password).toString(),
|
||||
crypto_js.MD5(query.password).toString(),
|
||||
rememberLogin: 'true',
|
||||
}
|
||||
let result = await request(
|
||||
return request(
|
||||
'POST',
|
||||
`https://music.163.com/weapi/login/cellphone`,
|
||||
data,
|
||||
@ -38253,23 +38229,6 @@ var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
realIP: query.realIP,
|
||||
},
|
||||
)
|
||||
|
||||
if (result.body.code === 200) {
|
||||
result = {
|
||||
status: 200,
|
||||
body: {
|
||||
...JSON.parse(
|
||||
JSON.stringify(result.body).replace(
|
||||
/avatarImgId_str/g,
|
||||
'avatarImgIdStr',
|
||||
),
|
||||
),
|
||||
cookie: result.cookie.join(';'),
|
||||
},
|
||||
cookie: result.cookie,
|
||||
}
|
||||
}
|
||||
return result
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/login_qr_check.js
|
||||
@ -38785,7 +38744,6 @@ var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/music_first_listen_info.js
|
||||
// 回忆坐标
|
||||
|
||||
@ -39476,7 +39434,8 @@ var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/playlist_track_add.js
|
||||
/* harmony default export */ const playlist_track_add = (async (query, request) => {
|
||||
/* harmony default export */ const playlist_track_add = ((query, request) => {
|
||||
// console.log("query", query)
|
||||
query.cookie.os = 'pc'
|
||||
query.ids = query.ids || ''
|
||||
const data = {
|
||||
@ -39487,7 +39446,7 @@ var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
}),
|
||||
),
|
||||
}
|
||||
console.log(data)
|
||||
// console.log(data)
|
||||
|
||||
return request('POST', `https://music.163.com/api/playlist/track/add`, data, {
|
||||
crypto: 'weapi',
|
||||
@ -39502,50 +39461,43 @@ var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
// 支持传递参数limit来限制获取歌曲的数据数量 例如: /playlist/track/all?id=7044354223&limit=10
|
||||
|
||||
/* harmony default export */ const playlist_track_all = ((query, request) => {
|
||||
const data = {
|
||||
id: query.id,
|
||||
n: 100000,
|
||||
s: query.s || 8,
|
||||
}
|
||||
//不放在data里面避免请求带上无用的数据
|
||||
let limit = parseInt(query.limit) || Infinity
|
||||
let offset = parseInt(query.offset) || 0
|
||||
let limit = parseInt(query.limit) || Infinity;
|
||||
let offset = parseInt(query.offset) || 0;
|
||||
|
||||
let trackIds;
|
||||
|
||||
// 拿到playlist/detail的返回的接口数据
|
||||
if (query.trackIds) {
|
||||
trackIds = JSON.parse(query.trackIds);
|
||||
} else {
|
||||
let response = query.detail_result;
|
||||
response = JSON.parse(response);
|
||||
trackIds = response.data.playlist.trackIds;
|
||||
}
|
||||
|
||||
return request('POST', `https://music.163.com/api/v6/playlist/detail`, data, {
|
||||
crypto: 'api',
|
||||
cookie: query.cookie,
|
||||
proxy: query.proxy,
|
||||
realIP: query.realIP,
|
||||
}).then((res) => {
|
||||
let trackIds = res.body.playlist.trackIds
|
||||
let idsData = {
|
||||
c:
|
||||
'[' +
|
||||
"[" +
|
||||
trackIds
|
||||
.slice(offset, offset + limit)
|
||||
.map((item) => '{"id":' + item.id + '}')
|
||||
.join(',') +
|
||||
']',
|
||||
}
|
||||
.map((item) => '{"id":' + item.id + "}")
|
||||
.join(",") +
|
||||
"]",
|
||||
};
|
||||
|
||||
return request(
|
||||
'POST',
|
||||
`https://music.163.com/api/v3/song/detail`,
|
||||
idsData,
|
||||
{
|
||||
crypto: 'weapi',
|
||||
return request("POST", `https://music.163.com/api/v3/song/detail`, idsData, {
|
||||
crypto: "weapi",
|
||||
cookie: query.cookie,
|
||||
proxy: query.proxy,
|
||||
realIP: query.realIP,
|
||||
},
|
||||
)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/playlist_track_delete.js
|
||||
// 收藏单曲到歌单 从歌单删除歌曲
|
||||
|
||||
/* harmony default export */ const playlist_track_delete = (async (query, request) => {
|
||||
/* harmony default export */ const playlist_track_delete = ((query, request) => {
|
||||
query.cookie.os = 'pc'
|
||||
query.ids = query.ids || ''
|
||||
const data = {
|
||||
@ -39902,7 +39854,7 @@ var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
const data = {
|
||||
captcha: query.captcha,
|
||||
phone: query.phone,
|
||||
password: crypto_js_default().MD5(query.password).toString(),//crypto.createHash('md5').update(query.password).digest('hex'),
|
||||
password: crypto_js.MD5(query.password).toString(),//crypto.createHash('md5').update(query.password).digest('hex'),
|
||||
nickname: query.nickname,
|
||||
countrycode: query.countrycode || '86',
|
||||
}
|
||||
@ -39939,40 +39891,16 @@ var crypto_js_default = /*#__PURE__*/__webpack_require__.n(crypto_js);
|
||||
|
||||
/* harmony default export */ const related_playlist = ((query, request) => {
|
||||
return request(
|
||||
'GET',
|
||||
"GET",
|
||||
`https://music.163.com/playlist?id=${query.id}`,
|
||||
{},
|
||||
{
|
||||
ua: 'pc',
|
||||
ua: "pc",
|
||||
cookie: query.cookie,
|
||||
proxy: query.proxy,
|
||||
realIP: query.realIP,
|
||||
},
|
||||
).then((response) => {
|
||||
try {
|
||||
const pattern =
|
||||
/<div class="cver u-cover u-cover-3">[\s\S]*?<img src="([^"]+)">[\s\S]*?<a class="sname f-fs1 s-fc0" href="([^"]+)"[^>]*>([^<]+?)<\/a>[\s\S]*?<a class="nm nm f-thide s-fc3" href="([^"]+)"[^>]*>([^<]+?)<\/a>/g
|
||||
let result,
|
||||
playlists = []
|
||||
while ((result = pattern.exec(response.body)) != null) {
|
||||
playlists.push({
|
||||
creator: {
|
||||
userId: result[4].slice('/user/home?id='.length),
|
||||
nickname: result[5],
|
||||
},
|
||||
coverImgUrl: result[1].slice(0, -'?param=50y50'.length),
|
||||
name: result[3],
|
||||
id: result[2].slice('/playlist?id='.length),
|
||||
})
|
||||
}
|
||||
response.body = { code: 200, playlists: playlists }
|
||||
return response
|
||||
} catch (err) {
|
||||
response.status = 500
|
||||
response.body = { code: 500, msg: err.stack }
|
||||
return Promise.reject(response)
|
||||
}
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/resource_like.js
|
||||
@ -41014,30 +40942,20 @@ const resource_like_resourceTypeMap = config_namespaceObject.A;
|
||||
;// CONCATENATED MODULE: ./module/top_playlist.js
|
||||
// 分类歌单
|
||||
|
||||
/* harmony default export */ const top_playlist = (async (query, request) => {
|
||||
/* harmony default export */ const top_playlist = ((query, request) => {
|
||||
const data = {
|
||||
cat: query.cat || '全部', // 全部,华语,欧美,日语,韩语,粤语,小语种,流行,摇滚,民谣,电子,舞曲,说唱,轻音乐,爵士,乡村,R&B/Soul,古典,民族,英伦,金属,朋克,蓝调,雷鬼,世界音乐,拉丁,另类/独立,New Age,古风,后摇,Bossa Nova,清晨,夜晚,学习,工作,午休,下午茶,地铁,驾车,运动,旅行,散步,酒吧,怀旧,清新,浪漫,性感,伤感,治愈,放松,孤独,感动,兴奋,快乐,安静,思念,影视原声,ACG,儿童,校园,游戏,70后,80后,90后,网络歌曲,KTV,经典,翻唱,吉他,钢琴,器乐,榜单,00后
|
||||
order: query.order || 'hot', // hot,new
|
||||
cat: query.cat || "全部", // 全部,华语,欧美,日语,韩语,粤语,小语种,流行,摇滚,民谣,电子,舞曲,说唱,轻音乐,爵士,乡村,R&B/Soul,古典,民族,英伦,金属,朋克,蓝调,雷鬼,世界音乐,拉丁,另类/独立,New Age,古风,后摇,Bossa Nova,清晨,夜晚,学习,工作,午休,下午茶,地铁,驾车,运动,旅行,散步,酒吧,怀旧,清新,浪漫,性感,伤感,治愈,放松,孤独,感动,兴奋,快乐,安静,思念,影视原声,ACG,儿童,校园,游戏,70后,80后,90后,网络歌曲,KTV,经典,翻唱,吉他,钢琴,器乐,榜单,00后
|
||||
order: query.order || "hot", // hot,new
|
||||
limit: query.limit || 50,
|
||||
offset: query.offset || 0,
|
||||
total: true,
|
||||
}
|
||||
const res = await request(
|
||||
'POST',
|
||||
`https://music.163.com/weapi/playlist/list`,
|
||||
data,
|
||||
{
|
||||
crypto: 'weapi',
|
||||
};
|
||||
return request("POST", `https://music.163.com/weapi/playlist/list`, data, {
|
||||
crypto: "weapi",
|
||||
cookie: query.cookie,
|
||||
proxy: query.proxy,
|
||||
realIP: query.realIP,
|
||||
},
|
||||
)
|
||||
const result = JSON.stringify(res).replace(
|
||||
/avatarImgId_str/g,
|
||||
'avatarImgIdStr',
|
||||
)
|
||||
return JSON.parse(result)
|
||||
});
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/top_playlist_highquality.js
|
||||
@ -41374,23 +41292,18 @@ const resource_like_resourceTypeMap = config_namespaceObject.A;
|
||||
;// CONCATENATED MODULE: ./module/user_detail.js
|
||||
// 用户详情
|
||||
|
||||
/* harmony default export */ const user_detail = (async (query, request) => {
|
||||
const res = await request(
|
||||
'POST',
|
||||
/* harmony default export */ const user_detail = ((query, request) => {
|
||||
return request(
|
||||
"POST",
|
||||
`https://music.163.com/weapi/v1/user/detail/${query.uid}`,
|
||||
{},
|
||||
{
|
||||
crypto: 'weapi',
|
||||
crypto: "weapi",
|
||||
cookie: query.cookie,
|
||||
proxy: query.proxy,
|
||||
realIP: query.realIP,
|
||||
},
|
||||
)
|
||||
const result = JSON.stringify(res).replace(
|
||||
/avatarImgId_str/g,
|
||||
'avatarImgIdStr',
|
||||
)
|
||||
return JSON.parse(result)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./module/user_dj.js
|
||||
@ -42268,7 +42181,7 @@ const resource_like_resourceTypeMap = config_namespaceObject.A;
|
||||
})
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./api.js
|
||||
;// CONCATENATED MODULE: ./util/api.js
|
||||
|
||||
|
||||
|
||||
@ -42884,7 +42797,6 @@ const resource_like_resourceTypeMap = config_namespaceObject.A;
|
||||
});
|
||||
// EXTERNAL MODULE: ./node_modules/node-forge/lib/index.js
|
||||
var lib = __webpack_require__(2079);
|
||||
var lib_default = /*#__PURE__*/__webpack_require__.n(lib);
|
||||
;// CONCATENATED MODULE: ./util/crypto.js
|
||||
|
||||
|
||||
@ -42899,13 +42811,13 @@ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7cl
|
||||
const eapiKey = 'e82ckenh8dichen8'
|
||||
|
||||
const aesEncrypt = (text, mode, key, iv, format = 'base64') => {
|
||||
let encrypted = crypto_js_default().AES.encrypt(
|
||||
crypto_js_default().enc.Utf8.parse(text),
|
||||
crypto_js_default().enc.Utf8.parse(key),
|
||||
let encrypted = crypto_js.AES.encrypt(
|
||||
crypto_js.enc.Utf8.parse(text),
|
||||
crypto_js.enc.Utf8.parse(key),
|
||||
{
|
||||
iv: crypto_js_default().enc.Utf8.parse(iv),
|
||||
mode: (crypto_js_default()).mode[mode.toUpperCase()],
|
||||
padding: (crypto_js_default()).pad.Pkcs7,
|
||||
iv: crypto_js.enc.Utf8.parse(iv),
|
||||
mode: crypto_js.mode[mode.toUpperCase()],
|
||||
padding: crypto_js.pad.Pkcs7,
|
||||
},
|
||||
)
|
||||
if (format === 'base64') {
|
||||
@ -42916,9 +42828,9 @@ const aesEncrypt = (text, mode, key, iv, format = 'base64') => {
|
||||
}
|
||||
|
||||
const rsaEncrypt = (str, key) => {
|
||||
const forgePublicKey = lib_default().pki.publicKeyFromPem(key)
|
||||
const forgePublicKey = lib.pki.publicKeyFromPem(key)
|
||||
const encrypted = forgePublicKey.encrypt(str, 'NONE')
|
||||
return lib_default().util.bytesToHex(encrypted)
|
||||
return lib.util.bytesToHex(encrypted)
|
||||
}
|
||||
|
||||
const weapi = (object) => {
|
||||
@ -42948,7 +42860,7 @@ const linuxapi = (object) => {
|
||||
const eapi = (url, object) => {
|
||||
const text = typeof object === 'object' ? JSON.stringify(object) : object
|
||||
const message = `nobody${url}use${text}md5forencrypt`
|
||||
const digest = crypto_js_default().MD5(message).toString()
|
||||
const digest = crypto_js.MD5(message).toString()
|
||||
const data = `${url}-36cd479b6b5-${text}-36cd479b6b5-${digest}`
|
||||
return {
|
||||
params: aesEncrypt(data, 'ecb', eapiKey, '', 'hex'),
|
||||
@ -42956,48 +42868,27 @@ const eapi = (url, object) => {
|
||||
}
|
||||
|
||||
const decrypt = (cipher) => {
|
||||
const decipher = crypto_js_default().AES.decrypt(
|
||||
const decipher = crypto_js.AES.decrypt(
|
||||
{
|
||||
ciphertext: crypto_js_default().enc.Hex.parse(cipher),
|
||||
ciphertext: crypto_js.enc.Hex.parse(cipher),
|
||||
},
|
||||
eapiKey,
|
||||
{
|
||||
mode: (crypto_js_default()).mode.ECB,
|
||||
mode: crypto_js.mode.ECB,
|
||||
},
|
||||
)
|
||||
const decryptedBytes = crypto_js_default().enc.Utf8.stringify(decipher)
|
||||
const decryptedBytes = crypto_js.enc.Utf8.stringify(decipher)
|
||||
return decryptedBytes
|
||||
}
|
||||
|
||||
/* harmony default export */ const util_crypto = ({ weapi, linuxapi, eapi, decrypt, aesEncrypt });
|
||||
|
||||
;// CONCATENATED MODULE: ./util/request.js
|
||||
;// CONCATENATED MODULE: ./util/request_param.js
|
||||
|
||||
|
||||
|
||||
// request.debug = true // 开启可看到更详细信息
|
||||
|
||||
// function customEncodeURIComponent(str) {
|
||||
// var result = "";
|
||||
// console.log("请求:", str);
|
||||
|
||||
// if (str !== undefined && str !== null) {
|
||||
// for (var i = 0; i < str.length; i++) {
|
||||
// var char = str.charAt(i);
|
||||
// if (/^[a-zA-Z0-9\-_.!~*'()]$/.test(char)) {
|
||||
// // 这些字符不需要编码
|
||||
// result += char;
|
||||
// } else {
|
||||
// // 其他字符需要编码
|
||||
// var charCode = str.charCodeAt(i).toString(16);
|
||||
// result +=
|
||||
// "%" + (charCode.length < 2 ? "0" : "") + charCode.toUpperCase();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
const chooseUserAgent = (ua = false) => {
|
||||
const userAgentList = {
|
||||
mobile: [
|
||||
@ -43057,21 +42948,20 @@ const createRequestParam = (method, url, data = {}, options) => {
|
||||
...options.cookie,
|
||||
__remember_me: true,
|
||||
// NMTID: crypto.randomBytes(16).toString('hex'),
|
||||
_ntes_nuid: crypto_js_default().lib.WordArray.random(16).toString((crypto_js_default()).enc.Hex),
|
||||
_ntes_nuid: crypto_js.lib.WordArray.random(16).toString(crypto_js.enc.Hex),
|
||||
};
|
||||
if (url.indexOf("login") === -1) {
|
||||
options.cookie["NMTID"] = crypto_js_default().lib.WordArray.random(16).toString(
|
||||
(crypto_js_default()).enc.Hex
|
||||
options.cookie["NMTID"] = crypto_js.lib.WordArray.random(16).toString(
|
||||
crypto_js.enc.Hex
|
||||
);
|
||||
}
|
||||
if (!options.cookie.MUSIC_U) {
|
||||
// 游客
|
||||
// 不允许游客
|
||||
// if (!options.cookie.MUSIC_A) {
|
||||
// options.cookie.MUSIC_A = anonymous_token;
|
||||
// options.cookie.os = options.cookie.os || "ios";
|
||||
// options.cookie.appver = options.cookie.appver || "8.10.90";
|
||||
// }
|
||||
if (!options.cookie.MUSIC_A) {
|
||||
options.cookie.MUSIC_A = anonymous_token;
|
||||
options.cookie.os = options.cookie.os || "ios";
|
||||
options.cookie.appver = options.cookie.appver || "8.10.90";
|
||||
}
|
||||
}
|
||||
headers["Cookie"] = Object.keys(options.cookie)
|
||||
.map(
|
||||
@ -43140,15 +43030,15 @@ const createRequestParam = (method, url, data = {}, options) => {
|
||||
url: url,
|
||||
headers: headers,
|
||||
data: new URLSearchParams(data).toString(),
|
||||
keepAlive: true, // 长连接
|
||||
// keepAlive: true, // 长连接
|
||||
};
|
||||
|
||||
if (options.crypto === "eapi") requestParams.encoding = null;
|
||||
|
||||
// 不允许代理
|
||||
requestParams.proxy = false;
|
||||
requestParams.httpAgent = null;
|
||||
requestParams.httpsAgent = null;
|
||||
// 代理根据调用环境的请求方式自己配置
|
||||
// requestParams.proxy = false;
|
||||
// requestParams.httpAgent = null;
|
||||
// requestParams.httpsAgent = null;
|
||||
|
||||
if (options.crypto === "eapi") {
|
||||
requestParams = {
|
||||
@ -43157,14 +43047,17 @@ const createRequestParam = (method, url, data = {}, options) => {
|
||||
};
|
||||
}
|
||||
|
||||
requestParams['crypto'] = options.crypto;
|
||||
requestParams['apiName'] = options.apiName;
|
||||
|
||||
// 返回请求需要的一些参数
|
||||
return requestParams;
|
||||
};
|
||||
|
||||
/* harmony default export */ const request = (createRequestParam);
|
||||
/* harmony default export */ const request_param = (createRequestParam);
|
||||
// export default createRequestParam;
|
||||
|
||||
;// CONCATENATED MODULE: ./index.js
|
||||
;// CONCATENATED MODULE: ./util/beforeRequest.js
|
||||
|
||||
|
||||
|
||||
@ -43173,34 +43066,319 @@ function hasApi(name) {
|
||||
return Object.keys(api).includes(name);
|
||||
}
|
||||
|
||||
function apiRequest(name, query) {
|
||||
function beforeRequest(name, query) {
|
||||
// 处理字符串格式的 cookie
|
||||
if (typeof query.cookie === "string") {
|
||||
query.cookie = cookieToJson(query.cookie);
|
||||
}
|
||||
|
||||
// let query = params;
|
||||
|
||||
// console.log("query", query);
|
||||
|
||||
// 处理接口名称
|
||||
if (name.startsWith("/")) {
|
||||
name = name.slice(1);
|
||||
}
|
||||
name = name.replace(/\//g, "_");
|
||||
|
||||
// console.log(name, hasApi(name));
|
||||
// 处理ip
|
||||
if (query.realIP) {
|
||||
query.ip = query.realIP;
|
||||
}
|
||||
|
||||
// console.log("query:", query);
|
||||
|
||||
if (hasApi(name)) {
|
||||
return api[name](query, request);
|
||||
return api[name](query, (...params) => {
|
||||
// 参数注入客户端IP
|
||||
const obj = [...params];
|
||||
|
||||
// console.log("query:",query,"obj:",obj,"params",params);
|
||||
let ip = query.ip;
|
||||
|
||||
//处理IPv6地址的问题
|
||||
|
||||
if (ip.substr(0, 7) == "::ffff:") {
|
||||
ip = ip.substr(7);
|
||||
}
|
||||
// console.log(ip)
|
||||
obj[3] = {
|
||||
...obj[3],
|
||||
ip,
|
||||
apiName: name,
|
||||
};
|
||||
|
||||
// let a = request_param(...obj);
|
||||
// console.log("a:", a);
|
||||
// return a;
|
||||
|
||||
return request_param(...obj);
|
||||
});
|
||||
} else {
|
||||
return { error: `api (${name}) not found` };
|
||||
}
|
||||
}
|
||||
|
||||
/* harmony default export */ const index = (apiRequest);
|
||||
/* harmony default export */ const util_beforeRequest = (beforeRequest);
|
||||
|
||||
// 下一步你应该按照返回数据发送请求
|
||||
|
||||
;// CONCATENATED MODULE: ./afterRequest/check_music.js
|
||||
// 歌曲可用性
|
||||
|
||||
/* harmony default export */ const afterRequest_check_music = ((response) => {
|
||||
response = JSON.parse(response);
|
||||
let playable = false;
|
||||
if (response.body.code == 200) {
|
||||
if (response.body.data[0].code == 200) {
|
||||
playable = true;
|
||||
}
|
||||
}
|
||||
if (playable) {
|
||||
response.body = { code: 200, success: true, message: "ok" };
|
||||
return response;
|
||||
} else {
|
||||
// response.status = 404
|
||||
response.body = { code: 200, success: false, message: "亲爱的,暂无版权" };
|
||||
return response;
|
||||
// return Promise.reject(response)
|
||||
}
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./afterRequest/login_cellphone.js
|
||||
/* harmony default export */ const afterRequest_login_cellphone = ((response) => {
|
||||
response = JSON.parse(response);
|
||||
if (response.body.code === 200) {
|
||||
response = {
|
||||
status: 200,
|
||||
body: {
|
||||
...JSON.parse(
|
||||
JSON.stringify(response.body).replace(
|
||||
/avatarImgId_str/g,
|
||||
"avatarImgIdStr"
|
||||
)
|
||||
),
|
||||
cookie: response.cookie.join(";"),
|
||||
},
|
||||
cookie: response.cookie,
|
||||
};
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./afterRequest/related_playlist.js
|
||||
/* harmony default export */ const afterRequest_related_playlist = ((response) => {
|
||||
response = JSON.parse(response);
|
||||
try {
|
||||
const pattern =
|
||||
/<div class="cver u-cover u-cover-3">[\s\S]*?<img src="([^"]+)">[\s\S]*?<a class="sname f-fs1 s-fc0" href="([^"]+)"[^>]*>([^<]+?)<\/a>[\s\S]*?<a class="nm nm f-thide s-fc3" href="([^"]+)"[^>]*>([^<]+?)<\/a>/g;
|
||||
let result,
|
||||
playlists = [];
|
||||
while ((result = pattern.exec(response.body)) != null) {
|
||||
playlists.push({
|
||||
creator: {
|
||||
userId: result[4].slice("/user/home?id=".length),
|
||||
nickname: result[5],
|
||||
},
|
||||
coverImgUrl: result[1].slice(0, -"?param=50y50".length),
|
||||
name: result[3],
|
||||
id: result[2].slice("/playlist?id=".length),
|
||||
});
|
||||
}
|
||||
response.body = { code: 200, playlists: playlists };
|
||||
return response;
|
||||
} catch (err) {
|
||||
response.status = 500;
|
||||
response.body = { code: 500, msg: err.stack };
|
||||
return response;
|
||||
}
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./afterRequest/top_playlist.js
|
||||
/* harmony default export */ const afterRequest_top_playlist = ((response) => {
|
||||
response = JSON.parse(response);
|
||||
response = JSON.stringify(response).replace(
|
||||
/avatarImgId_str/g,
|
||||
"avatarImgIdStr"
|
||||
);
|
||||
return JSON.parse(response);
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./afterRequest/user_detail.js
|
||||
/* harmony default export */ const afterRequest_user_detail = ((response) => {
|
||||
response = JSON.parse(response);
|
||||
|
||||
response = JSON.stringify(response).replace(
|
||||
/avatarImgId_str/g,
|
||||
"avatarImgIdStr"
|
||||
);
|
||||
return JSON.parse(response);
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ./util/afterRequestApi.js
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* harmony default export */ const afterRequestApi = ({
|
||||
'check_music':afterRequest_check_music,
|
||||
'login_cellphone':afterRequest_login_cellphone,
|
||||
'related_playlist':afterRequest_related_playlist,
|
||||
'top_playlist':afterRequest_top_playlist,
|
||||
'user_detail':afterRequest_user_detail,
|
||||
});
|
||||
;// CONCATENATED MODULE: ./util/afterRequest.js
|
||||
// 处理请求
|
||||
// 要求返回数据的格式为
|
||||
/*
|
||||
*
|
||||
@param:result:
|
||||
{
|
||||
status: 200,
|
||||
body: {},
|
||||
cookie: []
|
||||
headers: {
|
||||
'set-cookie': [] | []
|
||||
|
||||
}
|
||||
}
|
||||
@param:crypto: "eapi", "weapi", "linuxapi"
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
function afterRequest_hasApi(name) {
|
||||
return Object.keys(afterRequestApi).includes(name);
|
||||
}
|
||||
|
||||
const afterRequest = (result, crypto, apiName) => {
|
||||
result = JSON.parse(result);
|
||||
const answer = { status: 500, body: {}, cookie: [] };
|
||||
|
||||
const body = result.data;
|
||||
|
||||
let cookie = result.headers["set-cookie"];
|
||||
|
||||
// 处理字符串格式的 cookie 到一个对象
|
||||
if (typeof cookie === "string") {
|
||||
cookie = cookieToJson(cookie);
|
||||
}
|
||||
|
||||
// 对象转换为数组
|
||||
if(typeof cookie === 'object' && cookie !== null){
|
||||
cookie = Object.entries(cookie).map(([key, value]) => `${key}=${value}`);
|
||||
}
|
||||
|
||||
answer.cookie = (cookie || []).map((x) =>
|
||||
x.replace(/\s*Domain=[^(;|$)]+;*/, "")
|
||||
);
|
||||
|
||||
try {
|
||||
if (crypto === "eapi") {
|
||||
answer.body = JSON.parse(util_crypto.decrypt(body));
|
||||
} else {
|
||||
answer.body = body;
|
||||
}
|
||||
|
||||
if (answer.body.code) {
|
||||
answer.body.code = Number(answer.body.code);
|
||||
}
|
||||
|
||||
answer.status = Number(answer.body.code || res.status);
|
||||
if (
|
||||
[201, 302, 400, 502, 800, 801, 802, 803].indexOf(answer.body.code) > -1
|
||||
) {
|
||||
// 特殊状态码
|
||||
answer.status = 200;
|
||||
}
|
||||
} catch (e) {
|
||||
// console.log(e)
|
||||
try {
|
||||
answer.body = JSON.parse(body.toString());
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
// can't decrypt and can't parse directly
|
||||
answer.body = body;
|
||||
}
|
||||
answer.status = result.status;
|
||||
}
|
||||
|
||||
answer.status =
|
||||
100 < answer.status && answer.status < 600 ? answer.status : 400;
|
||||
|
||||
// 处理特殊接口后续操作
|
||||
|
||||
// if(result){
|
||||
// // 处理返回数据
|
||||
// console.log("reqponse_operate", reqponse_operate);
|
||||
// }
|
||||
// console.log(answer);
|
||||
if (afterRequest_hasApi(apiName)) {
|
||||
let result = afterRequestApi[apiName](JSON.stringify(answer));
|
||||
if (result.status) {
|
||||
answer.status = result.status;
|
||||
}
|
||||
if (result.body) {
|
||||
answer.body = result.body;
|
||||
}
|
||||
if (result.cookie) {
|
||||
answer.cookie = result.cookie;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回数据
|
||||
if (answer.status !== 200 || !answer.body) {
|
||||
return {
|
||||
code: answer.status,
|
||||
data: answer.body,
|
||||
msg: answer.body.msg || "请求遇到问题",
|
||||
others: {
|
||||
status: answer.status,
|
||||
body: answer.body,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (answer.body.code == "301") {
|
||||
return {
|
||||
code: 301,
|
||||
data: null,
|
||||
msg: "需要登录",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
code: answer.body.code,
|
||||
data: answer.body,
|
||||
msg: answer.body.msg,
|
||||
};
|
||||
};
|
||||
|
||||
/* harmony default export */ const util_afterRequest = (afterRequest);
|
||||
|
||||
;// CONCATENATED MODULE: ./index.js
|
||||
|
||||
|
||||
|
||||
|
||||
const NeteaseCloudMusicApi_inner_version = "4.13.8"; // 本项目使用的 NeteaseCloudMusicApi 版本号
|
||||
const NeteaseCloudMusicApi_V8_version = package_namespaceObject.i8; // 本项目的版本号
|
||||
|
||||
/* harmony default export */ const index = ({
|
||||
beforeRequest: util_beforeRequest,
|
||||
afterRequest: util_afterRequest,
|
||||
inner_version: () => {
|
||||
return {
|
||||
NeteaseCloudMusicApi: NeteaseCloudMusicApi_inner_version,
|
||||
NeteaseCloudMusicApi_V8: NeteaseCloudMusicApi_V8_version,
|
||||
};
|
||||
},
|
||||
}); // 一个请求前钩子,一个请求后钩子
|
||||
|
||||
})();
|
||||
|
||||
NeteaseCloudMusicApi = __webpack_exports__["default"];
|
||||
__webpack_exports__ = __webpack_exports__["default"];
|
||||
/******/ return __webpack_exports__;
|
||||
/******/ })()
|
||||
;
|
||||
});
|
2
package/NeteaseCloudMusic/__init__.py
Normal file
2
package/NeteaseCloudMusic/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .main import NeteaseCloudMusicApi
|
||||
from .help import api_help, api_list
|
307354
package/NeteaseCloudMusic/config.json
Normal file
307354
package/NeteaseCloudMusic/config.json
Normal file
File diff suppressed because one or more lines are too long
42
package/NeteaseCloudMusic/help.py
Normal file
42
package/NeteaseCloudMusic/help.py
Normal file
@ -0,0 +1,42 @@
|
||||
import json
|
||||
import pkg_resources
|
||||
|
||||
# 载入配置
|
||||
resource_path = pkg_resources.resource_filename(__name__, 'config.json')
|
||||
|
||||
with open(resource_path, 'r', encoding='utf-8') as f:
|
||||
config = json.loads(f.read())
|
||||
|
||||
|
||||
def api_help(name: str = None) -> str:
|
||||
"""
|
||||
获取接口帮助
|
||||
:param name: 接口名称
|
||||
:return:
|
||||
"""
|
||||
if name is None:
|
||||
result_str = ("from NeteaseCloudMusic import NeteaseCloudMusicApi, api_help, api_list\n\n"
|
||||
"netease_cloud_music_api = NeteaseCloudMusicApi() # 初始化API\n"
|
||||
"netease_cloud_music_api.cookie = YOUR_COOKIE # 设置cookie\n"
|
||||
"response = netease_cloud_music_api.request(apiName, queryDict) # 调用接口\n\n"
|
||||
"# Use ”help(apiName)“ to view detailed information about the interface\n"
|
||||
"# Use ”api_list()“ to view the interface list")
|
||||
elif name in api_list():
|
||||
result_str = f'name: {name}\n {config[name]["name"]}\n {config[name]["explain"]}\n\n'
|
||||
|
||||
result_str += "query example: \n"
|
||||
for example in config[name]["example"]:
|
||||
index = config[name]["example"].index(example)
|
||||
result_str += f'{json.dumps(config[name]["example"][index]["query"], indent=2, ensure_ascii=False)}\n\n'
|
||||
else:
|
||||
result_str = f'apiName: {name} not found,please use ”api_list()“ to view the interface list'
|
||||
|
||||
return result_str
|
||||
|
||||
|
||||
def api_list():
|
||||
"""
|
||||
获取接口列表
|
||||
:return:
|
||||
"""
|
||||
return list(config.keys())
|
207
package/NeteaseCloudMusic/main.py
Normal file
207
package/NeteaseCloudMusic/main.py
Normal file
@ -0,0 +1,207 @@
|
||||
import json
|
||||
import os.path
|
||||
import socket
|
||||
from pprint import pprint
|
||||
|
||||
import pkg_resources
|
||||
import requests
|
||||
from py_mini_racer import py_mini_racer
|
||||
from .help import api_list
|
||||
|
||||
|
||||
class NeteaseCloudMusicApi:
|
||||
__cookie = None
|
||||
__ip = None
|
||||
|
||||
def __init__(self, debug=False):
|
||||
self.DEBUG = debug # 是否开启调试模式
|
||||
|
||||
self.special_api = {"/playlist/track/all": self.playlist_track_all,
|
||||
"/inner/version": self.inner_version}
|
||||
|
||||
# 载入js代码
|
||||
resource_path = pkg_resources.resource_filename(__name__, 'NeteaseCloudMusicApi.js')
|
||||
|
||||
with open(resource_path, 'r', encoding='utf-8') as file:
|
||||
js_code = file.read()
|
||||
self.ctx = py_mini_racer.MiniRacer()
|
||||
self.ctx.eval(js_code)
|
||||
|
||||
def request(self, name: str, query: dict = None) -> dict:
|
||||
"""
|
||||
调用接口
|
||||
接口文档地址: https://docs.neteasecloudmusicapi.binaryify.com
|
||||
:param name: api名称 例如: song_url_v1, /song/url/v1
|
||||
:param query: 请求参数
|
||||
:return: 请求结果 示例:{"code": 200, "data": {}, "msg": "success"}
|
||||
"""
|
||||
|
||||
special = {
|
||||
'daily_signin': '/daily_signin',
|
||||
'fm_trash': '/fm_trash',
|
||||
'personal_fm': '/personal_fm',
|
||||
}
|
||||
|
||||
yubei_special = {'/yunbei/tasks/receipt': '/yunbei/receipt',
|
||||
'/yunbei/tasks/expense': '/yunbei/expense'} # 这俩个接口准换的路由莫名奇妙
|
||||
|
||||
# 测试name是否合法
|
||||
name.replace("\\", "/")
|
||||
if not name.startswith("/"):
|
||||
if name in special.keys():
|
||||
name = special[name]
|
||||
else:
|
||||
name = "/" + name
|
||||
name = name.replace("_", "/")
|
||||
|
||||
# 处理俩个云贝接口名称转换问题
|
||||
if name in yubei_special.keys():
|
||||
name = yubei_special[name]
|
||||
print("转换了个麻烦的路由", name)
|
||||
|
||||
if name not in api_list():
|
||||
if name not in yubei_special.values():
|
||||
raise Exception(f"apiName: {name} not found,please use ”api_list()“ to view the interface list")
|
||||
|
||||
if query is None:
|
||||
query = {}
|
||||
if query.get("cookie") is None:
|
||||
query["cookie"] = self.cookie
|
||||
|
||||
if query.get("realIP") is None:
|
||||
query["realIP"] = self.ip
|
||||
else:
|
||||
query["realIP"] = query.get("realIP")
|
||||
|
||||
# 特殊api处理
|
||||
if name in self.special_api.keys():
|
||||
result = self.special_api[name](query)
|
||||
else:
|
||||
result = self.call_api(name, query)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_local_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('10.255.255.255', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
print("get local ip error")
|
||||
IP = "116.25.146.177"
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
@property
|
||||
def cookie(self):
|
||||
if self.__cookie is None:
|
||||
if os.path.isfile("cookie_storage"):
|
||||
with open("cookie_storage", "r", encoding='utf-8') as f:
|
||||
self.__cookie = f.read()
|
||||
else:
|
||||
self.__cookie = "" # 如果没有cookie文件,就设置为空
|
||||
|
||||
return self.__cookie
|
||||
|
||||
@cookie.setter
|
||||
def cookie(self, cookie):
|
||||
if cookie is None:
|
||||
cookie = ""
|
||||
self.__cookie = cookie
|
||||
with open("cookie_storage", "w+", encoding='utf-8') as f:
|
||||
f.write(cookie)
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
if self.__ip is None:
|
||||
self.__ip = self.get_local_ip()
|
||||
return self.__ip
|
||||
|
||||
def call_api(self, name, query):
|
||||
request_param = self.ctx.call('NeteaseCloudMusicApi.beforeRequest', name, query) # 拿到请求头和请求参数
|
||||
|
||||
param_data = {}
|
||||
if request_param["data"] != "":
|
||||
for item in request_param["data"].split("&"):
|
||||
param_data[item.split("=")[0]] = item.split("=")[1]
|
||||
|
||||
# print("url", request_param["url"], "data", param_data, "headers\n", json.dumps(request_param["headers"], indent=2, ensure_ascii=False))
|
||||
|
||||
if request_param.get("method") == "GET":
|
||||
response = requests.get(request_param["url"], params=param_data, headers=request_param["headers"])
|
||||
else:
|
||||
response = requests.post(request_param["url"], data=param_data, headers=request_param["headers"])
|
||||
# response = requests.post(request_param["url"], data=param_data, headers=request_param["headers"])
|
||||
|
||||
try:
|
||||
data = json.loads(response.text)
|
||||
except json.JSONDecodeError:
|
||||
data = response.text
|
||||
|
||||
response_result = {
|
||||
"headers": dict(response.headers),
|
||||
"data": data,
|
||||
"status": response.status_code,
|
||||
}
|
||||
|
||||
result = self.ctx.call('NeteaseCloudMusicApi.afterRequest',
|
||||
json.dumps(response_result),
|
||||
request_param.get('crypto', None),
|
||||
request_param['apiName']) # 拿到请求结果
|
||||
|
||||
return result
|
||||
|
||||
def playlist_track_all(self, query):
|
||||
"""
|
||||
获取歌单全部歌曲
|
||||
:param query:
|
||||
:return:
|
||||
"""
|
||||
|
||||
detail_query = {"id": query.get("id"), "cookie": query.get("cookie"), "realIP": query.get("realIP")}
|
||||
|
||||
result = self.call_api("/playlist/detail", detail_query)
|
||||
|
||||
track_all_query = {"detail_result": json.dumps(result), "cookie": query.get("cookie"),
|
||||
"realIP": query.get("realIP")}
|
||||
if query.get("limit"):
|
||||
track_all_query["limit"] = query.get("limit")
|
||||
if query.get("offset"):
|
||||
track_all_query["offset"] = query.get("offset")
|
||||
|
||||
result = self.call_api("/playlist/track/all", track_all_query)
|
||||
return result
|
||||
|
||||
def inner_version(self, query):
|
||||
"""
|
||||
获取所使用的 NeteaseCloudMusicApi 和 NeteaseCloudMusicApi_V8 版本号
|
||||
:param query:
|
||||
:return:
|
||||
"""
|
||||
result = self.ctx.call('NeteaseCloudMusicApi.inner_version')
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import json
|
||||
import os
|
||||
from pprint import pprint
|
||||
import dotenv
|
||||
|
||||
dotenv.load_dotenv("../../.env") # 从.env文件中加载环境变量
|
||||
|
||||
netease_cloud_music_api = NeteaseCloudMusicApi() # 初始化API
|
||||
netease_cloud_music_api.cookie = os.getenv("COOKIE") # 设置cookie
|
||||
netease_cloud_music_api.DEBUG = True # 开启调试模式
|
||||
|
||||
|
||||
def songv1_test():
|
||||
# 获取歌曲详情
|
||||
response = netease_cloud_music_api.request("song_url_v1", {"id": 33894312, "level": "exhigh"})
|
||||
pprint(response)
|
||||
|
||||
|
||||
songv1_test()
|
109
package/NeteaseCloudMusicApi.egg-info/PKG-INFO
Normal file
109
package/NeteaseCloudMusicApi.egg-info/PKG-INFO
Normal file
@ -0,0 +1,109 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: NeteaseCloudMusicApi
|
||||
Version: 0.1.1
|
||||
Summary: 网易云音乐API NeteaseCloudMusicApi项目的 Python SDK
|
||||
Home-page: https://github.com/me/myproject
|
||||
Author: Awesome Soul
|
||||
Author-email: me@example.com
|
||||
License: MIT
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Requires-Python: >=3.6.0
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE
|
||||
|
||||
|
||||
# NeteaseCloudMusic_PythonSDK
|
||||
> 基于 [ NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 封装的 Python SDK。
|
||||
> 网易云API Python版本。
|
||||
> 现已同步原项目接口且测试通过的有200多个
|
||||
> 已发布到PyPi,可直接使用pip安装
|
||||
> 项目地址:[GitHub](https://github.com/2061360308/NeteaseCloudMusic_PythonSDK)
|
||||
|
||||
![](https://img.shields.io/badge/py_mini_racer-@0.6.0-red.svg)
|
||||
![License](https://img.shields.io/badge/license-MIT-yellow)
|
||||
|
||||
### 依赖于
|
||||
- [ NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
|
||||
- [ NeteaseCloudMusicApi_V8 ](https://github.com/2061360308/NeteaseCloudMusicApi_V8)
|
||||
|
||||
### 原理
|
||||
- 通过 `py_mini_racer` 调用 `NeteaseCloudMusicApi_V8` 的 `js` 方法。进一步进行了简单封装。
|
||||
|
||||
### 使用
|
||||
- 安装 `pip install NeteaseCloudMusic`
|
||||
- 导入API进行使用(具体查看`test.py`中的示例)
|
||||
```python
|
||||
from NeteaseCloudMusic import NeteaseCloudMusicApi, api_help, api_list
|
||||
import os
|
||||
|
||||
netease_cloud_music_api = NeteaseCloudMusicApi() # 初始化API
|
||||
netease_cloud_music_api.cookie = os.getenv("COOKIE") # 设置cookie
|
||||
response = netease_cloud_music_api.request("song_url_v1", {"id": 33894312, "level": "exhigh"}) # 调用API
|
||||
|
||||
# 获取帮助
|
||||
print(api_help())
|
||||
print(api_help('song_url_v1'))
|
||||
# 获取API列表
|
||||
print(api_list())
|
||||
```
|
||||
|
||||
> 注意: request(self, name, query=None) 的第一个参数为API名称,第二个参数为API参数,具体API名称和参数请参考 [NeteaseCloudMusicApi文档](https://docs.neteasecloudmusicapi.binaryify.com),name支持`/song/url/v1`和`song_url_v1`两种写法。
|
||||
|
||||
|
||||
### 开发
|
||||
- 克隆项目 `git clone git@github.com:2061360308/NeteaseCloudMusic_PythonSDK.git`
|
||||
- 安装依赖 `pip install -r requirements.txt`
|
||||
- 目录/文件说明
|
||||
├── package 项目包根目录
|
||||
├── test_gender 生成测试代码的脚本
|
||||
├── test.py 手动测试/ 使用示例
|
||||
|
||||
|
||||
### 改进
|
||||
> 下列API未支持
|
||||
>
|
||||
- apicache.js
|
||||
- memory-cache.js
|
||||
- request_reference.js
|
||||
- avatar_upload.js
|
||||
- cloud.js
|
||||
- playlist_cover_update.js
|
||||
- voice_upload.js
|
||||
- register_anonimous.js
|
||||
- verify_getQr.js
|
||||
|
||||
> 以下api未测试(这些接口测试起来比较繁琐)
|
||||
>
|
||||
- /user/replacephone
|
||||
- /audio/match
|
||||
- /rebind
|
||||
- /nickname/check
|
||||
- /activate/init/profile
|
||||
- /cellphone/existence/check
|
||||
- /register/cellphone
|
||||
- /captcha/verify
|
||||
- /captcha/sent
|
||||
- /login/refresh
|
||||
- /logout
|
||||
- /user/update
|
||||
- /pl/count
|
||||
- /playlist/update
|
||||
- /playlist/desc/update
|
||||
- /playlist/name/update
|
||||
- /playlist/tags/update
|
||||
- /event/forward
|
||||
- /event/del
|
||||
- /share/resource
|
||||
- /send/text
|
||||
- /send/playlist
|
||||
- /playlist/create
|
||||
- /playlist/tracks
|
||||
- /daily_signin
|
||||
- /fm_trash
|
||||
|
||||
### 欢迎提交PR
|
14
package/NeteaseCloudMusicApi.egg-info/SOURCES.txt
Normal file
14
package/NeteaseCloudMusicApi.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,14 @@
|
||||
LICENSE
|
||||
MANIFEST.in
|
||||
README.md
|
||||
setup.py
|
||||
NeteaseCloudMusic/NeteaseCloudMusicApi.js
|
||||
NeteaseCloudMusic/__init__.py
|
||||
NeteaseCloudMusic/config.json
|
||||
NeteaseCloudMusic/help.py
|
||||
NeteaseCloudMusic/main.py
|
||||
NeteaseCloudMusicApi.egg-info/PKG-INFO
|
||||
NeteaseCloudMusicApi.egg-info/SOURCES.txt
|
||||
NeteaseCloudMusicApi.egg-info/dependency_links.txt
|
||||
NeteaseCloudMusicApi.egg-info/requires.txt
|
||||
NeteaseCloudMusicApi.egg-info/top_level.txt
|
@ -0,0 +1 @@
|
||||
|
2
package/NeteaseCloudMusicApi.egg-info/requires.txt
Normal file
2
package/NeteaseCloudMusicApi.egg-info/requires.txt
Normal file
@ -0,0 +1,2 @@
|
||||
py_mini_racer
|
||||
requests
|
1
package/NeteaseCloudMusicApi.egg-info/top_level.txt
Normal file
1
package/NeteaseCloudMusicApi.egg-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
||||
NeteaseCloudMusic
|
90
package/README.md
Normal file
90
package/README.md
Normal file
@ -0,0 +1,90 @@
|
||||
# NeteaseCloudMusic_PythonSDK
|
||||
> 基于 [ NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 封装的 Python SDK。
|
||||
> 网易云API Python版本。
|
||||
> 现已同步原项目接口且测试通过的有200多个
|
||||
> 已发布到PyPi,可直接使用pip安装
|
||||
> 项目地址:[GitHub](https://github.com/2061360308/NeteaseCloudMusic_PythonSDK)
|
||||
|
||||
![](https://img.shields.io/badge/py_mini_racer-@0.6.0-red.svg)
|
||||
![License](https://img.shields.io/badge/license-MIT-yellow)
|
||||
|
||||
### 依赖于
|
||||
- [ NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
|
||||
- [ NeteaseCloudMusicApi_V8 ](https://github.com/2061360308/NeteaseCloudMusicApi_V8)
|
||||
|
||||
### 原理
|
||||
- 通过 `py_mini_racer` 调用 `NeteaseCloudMusicApi_V8` 的 `js` 方法。进一步进行了简单封装。
|
||||
|
||||
### 使用
|
||||
- 安装 `pip install NeteaseCloudMusic`
|
||||
- 导入API进行使用(具体查看`test.py`中的示例)
|
||||
```python
|
||||
from NeteaseCloudMusic import NeteaseCloudMusicApi, api_help, api_list
|
||||
import os
|
||||
|
||||
netease_cloud_music_api = NeteaseCloudMusicApi() # 初始化API
|
||||
netease_cloud_music_api.cookie = os.getenv("COOKIE") # 设置cookie
|
||||
response = netease_cloud_music_api.request("song_url_v1", {"id": 33894312, "level": "exhigh"}) # 调用API
|
||||
|
||||
# 获取帮助
|
||||
print(api_help())
|
||||
print(api_help('song_url_v1'))
|
||||
# 获取API列表
|
||||
print(api_list())
|
||||
```
|
||||
|
||||
> 注意: request(self, name, query=None) 的第一个参数为API名称,第二个参数为API参数,具体API名称和参数请参考 [NeteaseCloudMusicApi文档](https://docs.neteasecloudmusicapi.binaryify.com),name支持`/song/url/v1`和`song_url_v1`两种写法。
|
||||
|
||||
|
||||
### 开发
|
||||
- 克隆项目 `git clone git@github.com:2061360308/NeteaseCloudMusic_PythonSDK.git`
|
||||
- 安装依赖 `pip install -r requirements.txt`
|
||||
- 目录/文件说明
|
||||
├── package 项目包根目录
|
||||
├── test_gender 生成测试代码的脚本
|
||||
├── test.py 手动测试/ 使用示例
|
||||
|
||||
|
||||
### 改进
|
||||
> 下列API未支持
|
||||
>
|
||||
- apicache.js
|
||||
- memory-cache.js
|
||||
- request_reference.js
|
||||
- avatar_upload.js
|
||||
- cloud.js
|
||||
- playlist_cover_update.js
|
||||
- voice_upload.js
|
||||
- register_anonimous.js
|
||||
- verify_getQr.js
|
||||
|
||||
> 以下api未测试(这些接口测试起来比较繁琐)
|
||||
>
|
||||
- /user/replacephone
|
||||
- /audio/match
|
||||
- /rebind
|
||||
- /nickname/check
|
||||
- /activate/init/profile
|
||||
- /cellphone/existence/check
|
||||
- /register/cellphone
|
||||
- /captcha/verify
|
||||
- /captcha/sent
|
||||
- /login/refresh
|
||||
- /logout
|
||||
- /user/update
|
||||
- /pl/count
|
||||
- /playlist/update
|
||||
- /playlist/desc/update
|
||||
- /playlist/name/update
|
||||
- /playlist/tags/update
|
||||
- /event/forward
|
||||
- /event/del
|
||||
- /share/resource
|
||||
- /send/text
|
||||
- /send/playlist
|
||||
- /playlist/create
|
||||
- /playlist/tracks
|
||||
- /daily_signin
|
||||
- /fm_trash
|
||||
|
||||
### 欢迎提交PR
|
43384
package/build/lib/NeteaseCloudMusic/NeteaseCloudMusicApi.js
Normal file
43384
package/build/lib/NeteaseCloudMusic/NeteaseCloudMusicApi.js
Normal file
File diff suppressed because it is too large
Load Diff
2
package/build/lib/NeteaseCloudMusic/__init__.py
Normal file
2
package/build/lib/NeteaseCloudMusic/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .main import NeteaseCloudMusicApi
|
||||
from .help import api_help, api_list
|
307354
package/build/lib/NeteaseCloudMusic/config.json
Normal file
307354
package/build/lib/NeteaseCloudMusic/config.json
Normal file
File diff suppressed because one or more lines are too long
42
package/build/lib/NeteaseCloudMusic/help.py
Normal file
42
package/build/lib/NeteaseCloudMusic/help.py
Normal file
@ -0,0 +1,42 @@
|
||||
import json
|
||||
import pkg_resources
|
||||
|
||||
# 载入配置
|
||||
resource_path = pkg_resources.resource_filename(__name__, 'config.json')
|
||||
|
||||
with open(resource_path, 'r', encoding='utf-8') as f:
|
||||
config = json.loads(f.read())
|
||||
|
||||
|
||||
def api_help(name: str = None) -> str:
|
||||
"""
|
||||
获取接口帮助
|
||||
:param name: 接口名称
|
||||
:return:
|
||||
"""
|
||||
if name is None:
|
||||
result_str = ("from NeteaseCloudMusic import NeteaseCloudMusicApi, api_help, api_list\n\n"
|
||||
"netease_cloud_music_api = NeteaseCloudMusicApi() # 初始化API\n"
|
||||
"netease_cloud_music_api.cookie = YOUR_COOKIE # 设置cookie\n"
|
||||
"response = netease_cloud_music_api.request(apiName, queryDict) # 调用接口\n\n"
|
||||
"# Use ”help(apiName)“ to view detailed information about the interface\n"
|
||||
"# Use ”api_list()“ to view the interface list")
|
||||
elif name in api_list():
|
||||
result_str = f'name: {name}\n {config[name]["name"]}\n {config[name]["explain"]}\n\n'
|
||||
|
||||
result_str += "query example: \n"
|
||||
for example in config[name]["example"]:
|
||||
index = config[name]["example"].index(example)
|
||||
result_str += f'{json.dumps(config[name]["example"][index]["query"], indent=2, ensure_ascii=False)}\n\n'
|
||||
else:
|
||||
result_str = f'apiName: {name} not found,please use ”api_list()“ to view the interface list'
|
||||
|
||||
return result_str
|
||||
|
||||
|
||||
def api_list():
|
||||
"""
|
||||
获取接口列表
|
||||
:return:
|
||||
"""
|
||||
return list(config.keys())
|
207
package/build/lib/NeteaseCloudMusic/main.py
Normal file
207
package/build/lib/NeteaseCloudMusic/main.py
Normal file
@ -0,0 +1,207 @@
|
||||
import json
|
||||
import os.path
|
||||
import socket
|
||||
from pprint import pprint
|
||||
|
||||
import pkg_resources
|
||||
import requests
|
||||
from py_mini_racer import py_mini_racer
|
||||
from .help import api_list
|
||||
|
||||
|
||||
class NeteaseCloudMusicApi:
|
||||
__cookie = None
|
||||
__ip = None
|
||||
|
||||
def __init__(self, debug=False):
|
||||
self.DEBUG = debug # 是否开启调试模式
|
||||
|
||||
self.special_api = {"/playlist/track/all": self.playlist_track_all,
|
||||
"/inner/version": self.inner_version}
|
||||
|
||||
# 载入js代码
|
||||
resource_path = pkg_resources.resource_filename(__name__, 'NeteaseCloudMusicApi.js')
|
||||
|
||||
with open(resource_path, 'r', encoding='utf-8') as file:
|
||||
js_code = file.read()
|
||||
self.ctx = py_mini_racer.MiniRacer()
|
||||
self.ctx.eval(js_code)
|
||||
|
||||
def request(self, name: str, query: dict = None) -> dict:
|
||||
"""
|
||||
调用接口
|
||||
接口文档地址: https://docs.neteasecloudmusicapi.binaryify.com
|
||||
:param name: api名称 例如: song_url_v1, /song/url/v1
|
||||
:param query: 请求参数
|
||||
:return: 请求结果 示例:{"code": 200, "data": {}, "msg": "success"}
|
||||
"""
|
||||
|
||||
special = {
|
||||
'daily_signin': '/daily_signin',
|
||||
'fm_trash': '/fm_trash',
|
||||
'personal_fm': '/personal_fm',
|
||||
}
|
||||
|
||||
yubei_special = {'/yunbei/tasks/receipt': '/yunbei/receipt',
|
||||
'/yunbei/tasks/expense': '/yunbei/expense'} # 这俩个接口准换的路由莫名奇妙
|
||||
|
||||
# 测试name是否合法
|
||||
name.replace("\\", "/")
|
||||
if not name.startswith("/"):
|
||||
if name in special.keys():
|
||||
name = special[name]
|
||||
else:
|
||||
name = "/" + name
|
||||
name = name.replace("_", "/")
|
||||
|
||||
# 处理俩个云贝接口名称转换问题
|
||||
if name in yubei_special.keys():
|
||||
name = yubei_special[name]
|
||||
print("转换了个麻烦的路由", name)
|
||||
|
||||
if name not in api_list():
|
||||
if name not in yubei_special.values():
|
||||
raise Exception(f"apiName: {name} not found,please use ”api_list()“ to view the interface list")
|
||||
|
||||
if query is None:
|
||||
query = {}
|
||||
if query.get("cookie") is None:
|
||||
query["cookie"] = self.cookie
|
||||
|
||||
if query.get("realIP") is None:
|
||||
query["realIP"] = self.ip
|
||||
else:
|
||||
query["realIP"] = query.get("realIP")
|
||||
|
||||
# 特殊api处理
|
||||
if name in self.special_api.keys():
|
||||
result = self.special_api[name](query)
|
||||
else:
|
||||
result = self.call_api(name, query)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_local_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('10.255.255.255', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
print("get local ip error")
|
||||
IP = "116.25.146.177"
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
@property
|
||||
def cookie(self):
|
||||
if self.__cookie is None:
|
||||
if os.path.isfile("cookie_storage"):
|
||||
with open("cookie_storage", "r", encoding='utf-8') as f:
|
||||
self.__cookie = f.read()
|
||||
else:
|
||||
self.__cookie = "" # 如果没有cookie文件,就设置为空
|
||||
|
||||
return self.__cookie
|
||||
|
||||
@cookie.setter
|
||||
def cookie(self, cookie):
|
||||
if cookie is None:
|
||||
cookie = ""
|
||||
self.__cookie = cookie
|
||||
with open("cookie_storage", "w+", encoding='utf-8') as f:
|
||||
f.write(cookie)
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
if self.__ip is None:
|
||||
self.__ip = self.get_local_ip()
|
||||
return self.__ip
|
||||
|
||||
def call_api(self, name, query):
|
||||
request_param = self.ctx.call('NeteaseCloudMusicApi.beforeRequest', name, query) # 拿到请求头和请求参数
|
||||
|
||||
param_data = {}
|
||||
if request_param["data"] != "":
|
||||
for item in request_param["data"].split("&"):
|
||||
param_data[item.split("=")[0]] = item.split("=")[1]
|
||||
|
||||
# print("url", request_param["url"], "data", param_data, "headers\n", json.dumps(request_param["headers"], indent=2, ensure_ascii=False))
|
||||
|
||||
if request_param.get("method") == "GET":
|
||||
response = requests.get(request_param["url"], params=param_data, headers=request_param["headers"])
|
||||
else:
|
||||
response = requests.post(request_param["url"], data=param_data, headers=request_param["headers"])
|
||||
# response = requests.post(request_param["url"], data=param_data, headers=request_param["headers"])
|
||||
|
||||
try:
|
||||
data = json.loads(response.text)
|
||||
except json.JSONDecodeError:
|
||||
data = response.text
|
||||
|
||||
response_result = {
|
||||
"headers": dict(response.headers),
|
||||
"data": data,
|
||||
"status": response.status_code,
|
||||
}
|
||||
|
||||
result = self.ctx.call('NeteaseCloudMusicApi.afterRequest',
|
||||
json.dumps(response_result),
|
||||
request_param.get('crypto', None),
|
||||
request_param['apiName']) # 拿到请求结果
|
||||
|
||||
return result
|
||||
|
||||
def playlist_track_all(self, query):
|
||||
"""
|
||||
获取歌单全部歌曲
|
||||
:param query:
|
||||
:return:
|
||||
"""
|
||||
|
||||
detail_query = {"id": query.get("id"), "cookie": query.get("cookie"), "realIP": query.get("realIP")}
|
||||
|
||||
result = self.call_api("/playlist/detail", detail_query)
|
||||
|
||||
track_all_query = {"detail_result": json.dumps(result), "cookie": query.get("cookie"),
|
||||
"realIP": query.get("realIP")}
|
||||
if query.get("limit"):
|
||||
track_all_query["limit"] = query.get("limit")
|
||||
if query.get("offset"):
|
||||
track_all_query["offset"] = query.get("offset")
|
||||
|
||||
result = self.call_api("/playlist/track/all", track_all_query)
|
||||
return result
|
||||
|
||||
def inner_version(self, query):
|
||||
"""
|
||||
获取所使用的 NeteaseCloudMusicApi 和 NeteaseCloudMusicApi_V8 版本号
|
||||
:param query:
|
||||
:return:
|
||||
"""
|
||||
result = self.ctx.call('NeteaseCloudMusicApi.inner_version')
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import json
|
||||
import os
|
||||
from pprint import pprint
|
||||
import dotenv
|
||||
|
||||
dotenv.load_dotenv("../../.env") # 从.env文件中加载环境变量
|
||||
|
||||
netease_cloud_music_api = NeteaseCloudMusicApi() # 初始化API
|
||||
netease_cloud_music_api.cookie = os.getenv("COOKIE") # 设置cookie
|
||||
netease_cloud_music_api.DEBUG = True # 开启调试模式
|
||||
|
||||
|
||||
def songv1_test():
|
||||
# 获取歌曲详情
|
||||
response = netease_cloud_music_api.request("song_url_v1", {"id": 33894312, "level": "exhigh"})
|
||||
pprint(response)
|
||||
|
||||
|
||||
songv1_test()
|
BIN
package/dist/NeteaseCloudMusicApi-0.1.0-py2.py3-none-any.whl
vendored
Normal file
BIN
package/dist/NeteaseCloudMusicApi-0.1.0-py2.py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
package/dist/NeteaseCloudMusicApi-0.1.0.tar.gz
vendored
Normal file
BIN
package/dist/NeteaseCloudMusicApi-0.1.0.tar.gz
vendored
Normal file
Binary file not shown.
BIN
package/dist/NeteaseCloudMusicApi-0.1.1-py2.py3-none-any.whl
vendored
Normal file
BIN
package/dist/NeteaseCloudMusicApi-0.1.1-py2.py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
package/dist/NeteaseCloudMusicApi-0.1.1.tar.gz
vendored
Normal file
BIN
package/dist/NeteaseCloudMusicApi-0.1.1.tar.gz
vendored
Normal file
Binary file not shown.
138
package/setup.py
Normal file
138
package/setup.py
Normal file
@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Note: To use the 'upload' functionality of this file, you must:
|
||||
# $ pipenv install twine --dev
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from shutil import rmtree
|
||||
|
||||
from setuptools import find_packages, setup, Command
|
||||
|
||||
# Package meta-data.
|
||||
NAME = 'NeteaseCloudMusicApi'
|
||||
DESCRIPTION = '网易云音乐API NeteaseCloudMusicApi项目的 Python SDK'
|
||||
URL = 'https://github.com/me/myproject'
|
||||
EMAIL = 'me@example.com'
|
||||
AUTHOR = 'Awesome Soul'
|
||||
REQUIRES_PYTHON = '>=3.6.0'
|
||||
VERSION = '0.1.1'
|
||||
|
||||
# What packages are required for this module to be executed?
|
||||
REQUIRED = [
|
||||
'py_mini_racer', 'requests'
|
||||
# 'requests', 'maya', 'records',
|
||||
]
|
||||
|
||||
# What packages are optional?
|
||||
EXTRAS = {
|
||||
# 'fancy feature': ['django'],
|
||||
}
|
||||
|
||||
# The rest you shouldn't have to touch too much :)
|
||||
# ------------------------------------------------
|
||||
# Except, perhaps the License and Trove Classifiers!
|
||||
# If you do change the License, remember to change the Trove Classifier for that!
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# Import the README and use it as the long-description.
|
||||
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
|
||||
try:
|
||||
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
|
||||
long_description = '\n' + f.read()
|
||||
except FileNotFoundError:
|
||||
long_description = DESCRIPTION
|
||||
|
||||
# print("long_description:", long_description)
|
||||
|
||||
# Load the package's __version__.py module as a dictionary.
|
||||
about = {}
|
||||
if not VERSION:
|
||||
project_slug = NAME.lower().replace("-", "_").replace(" ", "_")
|
||||
with open(os.path.join(here, project_slug, '__version__.py')) as f:
|
||||
exec(f.read(), about)
|
||||
else:
|
||||
about['__version__'] = VERSION
|
||||
|
||||
|
||||
class UploadCommand(Command):
|
||||
"""Support setup.py upload."""
|
||||
|
||||
description = 'Build and publish the package.'
|
||||
user_options = []
|
||||
|
||||
@staticmethod
|
||||
def status(s):
|
||||
"""Prints things in bold."""
|
||||
print('\033[1m{0}\033[0m'.format(s))
|
||||
|
||||
def initialize_options(self):
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.status('Removing previous builds…')
|
||||
rmtree(os.path.join(here, 'dist'))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.status('Building Source and Wheel (universal) distribution…')
|
||||
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
|
||||
|
||||
self.status('Uploading the package to PyPI via Twine…')
|
||||
os.system('twine upload dist/*')
|
||||
|
||||
self.status('Pushing git tags…')
|
||||
os.system('git tag v{0}'.format(about['__version__']))
|
||||
os.system('git push --tags')
|
||||
|
||||
sys.exit()
|
||||
|
||||
|
||||
# Where the magic happens:
|
||||
setup(
|
||||
name=NAME,
|
||||
version=about['__version__'],
|
||||
description=DESCRIPTION,
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
author=AUTHOR,
|
||||
author_email=EMAIL,
|
||||
python_requires=REQUIRES_PYTHON,
|
||||
url=URL,
|
||||
packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]),
|
||||
# If your package is a single module, use this instead of 'packages':
|
||||
# py_modules=['mypackage'],
|
||||
|
||||
# entry_points={
|
||||
# 'console_scripts': ['mycli=mymodule:cli'],
|
||||
# },
|
||||
install_requires=REQUIRED,
|
||||
extras_require=EXTRAS,
|
||||
include_package_data=True,
|
||||
license='MIT',
|
||||
classifiers=[
|
||||
# Trove classifiers
|
||||
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy'
|
||||
],
|
||||
package_data={
|
||||
'': ['*.js'], # 包含所有的.js文件
|
||||
'': ['*.json'], # 包含所有的.json文件
|
||||
},
|
||||
# $ setup.py publish support.
|
||||
cmdclass={
|
||||
'upload': UploadCommand,
|
||||
},
|
||||
)
|
1091
report.html
Normal file
1091
report.html
Normal file
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{"method": "POST", "url": "https://music.163.com/eapi/v2/resource/comments", "headers": {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.30 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded", "Referer": "https://music.163.com", "X-Real-IP": "116.25.146.177", "X-Forwarded-For": "116.25.146.177", "Cookie": "osver=; deviceId=; appver=8.9.70; versioncode=140; mobilename=; buildver=1701847506; resolution=1920x1080; __csrf=f2479af05431c9fe000004d74b9eb43c; os=pc; channel=; requestId=1701847506946_0813; MUSIC_U=00F5809E26613FA34E24E56B87BB2D71C716ECA62F1D492FDDB5915792654A9C101BE0AFDDFB7A79A405394645D6A0BCCEF96AF99CD68BB226B3AD52F1A990A41E4D4E359E8FBE202B246A605453D0BA8EA9370E2CF943DC198B5ADF131DBD233495C69C0BA6B144FD3EB4A3D356B95B15FF8FA61D19A34DA56A980797377346706BD36CB666B2BA320AFF4E140BB208BBC6CBA15716BF7B98CB0A92EF1032735321FD39465B9E8B34E8C241A1BD5DA0C77501BF407AF6AA382C7B589AB18B92B856600EF1D81FC7A3E5B80339CC9DA04A0D32E85563B018CFDBEDC86F8972B6D78F6704AA10CEDBB7E43F3FC654D0D3EC885D09A503057B92912B28763893F0DDCE9995439D4E047867F8BDEB01C4B3C4C3717D3049C5A13BC16EF182455B02F3F9CA81185292BED1FD7493A07FCAB3B5676A7FD58F8BF4F5B09F752C6F235C034D297711FBE960A3CAF5504E62EF6547"}, "data": "params=12F5DE9D45BC43BBFF87CD83BA0668CB45B0B536B3EA520FDBC7DDA41E3DC56FEC27C2566DACAFBD82D091EEF23C2ADCE582909AA818FC4D8BB5653A07B2E8E2235570DEAEF766E41492D1ED74707C6F337B0D2D22DF8196E33D84DA7AF74B3A2346036C93ADA9DC0DC9EFEB1174C6ED81EDEF21958821A537FEF7E9A3175C66E12DB397AB219D4194C5E01591E172D37CBDDB41587257449EB0D4C6DBE370BC5B70161D83CB164F0788E3FD9537A59300F504D0DB6B058540CB790789D97DB9357DD82C52A0ABC124CEC86EA0E0E04D1F60426FA52D19654FD2031E08CDAF854ADD33AECC86D5AFAF8812E46D830DDBA92950E529AFEE0B5AC99714A052B8C7926A027043549A477704E3D226001F74984A407500E57FE116EBA54B85B5D2BB5D566540C0381BAE9F4E0D2BB9B69C886F6743AC6F444F515FC43CD2E78992371E0F44EFAB5A0C3852FD88504B76A8DF81C79C153CA25B02770ECD2C46018E3B26DCFA5E1B96818424553EC6E467C9503A1F1CF659794FEE27951AB4B3E6A256EC5E5B469366660CB62649B55A115703D83086D55B4796161BBA23EBBBB2A0B5CDD6F1B45302DF29ACE2C70A787A98BADAF3D4CC187AD6DCDFAABC7D8FFCCAA5C51871DD109091BC4BC35492AD4AFB82F0388C15A15B05F521759E994DC0A536AE57100F2F5692AB10D105C432B69E36F5E672C022B5F2014F01134AD7DDCD3C1B0929C227756E595FA3DA69347E2663BC7102C74FDA2311BA6934E7BE76BB63D106350EF17E1E61481D35431B2D26DF6099065FDCDB97A2F44C6B5FABD228743C77EAA7663E07864DFCD830E24E2633AE0E6B42D5A8B8F9F0D3BBDE867AADA0BF49E79AB9DDC20DC6EF8C62381B8526FB73BE67D381999A11C37E7F909D1A4816D11DB80653DAD25BB092EF6D4A5FCFBC0E4569048493A39540E6C3B66ABDF0347EEB7DE6DFA9B0B8B4621CF17195E2E10ABECE88812AD41DFED634EE28E7A69B7DF32864CCB0E18F5126C6EEC85B6B1801038E2B9BEEA459F3217F59A728A4508162D19F839BE7E4EAB693BF4E4446880B175281C6A5A597C3C1B8F1DAD210C3A9646BE1B71FABAEA2C6C69979CB0D9584178CFFBAA574096ECD2F4D2254EDB9FE176F7C429E38ADE27434D4FF8D3F0B8CF3E992248082522D7BA8DBC635456558A002D642F71824653EBA2004BEE4A2D7022EA84D686CD0B48D00FD7F1AFFCED9A15D61FE58D2065012EF5E90482F489A4A70EEA47A4D07548895CFB66DA09C351B92FFC1C234BA283ADF3CE37F8AD808C25A28D31ACA4E8FD78402DA14D9C8E55E3C060D90A1CD7692C8EEE56DDB3F35B6D313FBAB8347BE410F4E5937678CFEB82D0521F5A8A5D8C75653C988D80129ABDD1FF5C3EBFB52B23B4DAD2817E56FE15811FECEA65CF0E8956A66390B16AB2AD2455BE6A0A881F430CC747316EB02130B38A7E3DD2DC560A93768E9E9A9296F5D6606DC4A1F6F9F00FBA6D83B987895E699EF06235023A39022F24B4C", "keepAlive": true, "encoding": null, "proxy": false, "httpAgent": null, "httpsAgent": null, "responseType": "arraybuffer"}
|
61
test.py
61
test.py
@ -1,9 +1,11 @@
|
||||
# 使用示例
|
||||
|
||||
import json
|
||||
import os
|
||||
from pprint import pprint
|
||||
import dotenv
|
||||
|
||||
from main import NeteaseCloudMusicApi
|
||||
from NeteaseCloudMusic import NeteaseCloudMusicApi, api_help, api_list
|
||||
|
||||
dotenv.load_dotenv() # 从.env文件中加载环境变量
|
||||
|
||||
@ -14,32 +16,32 @@ netease_cloud_music_api.DEBUG = True # 开启调试模式
|
||||
|
||||
def songv1_test():
|
||||
# 获取歌曲详情
|
||||
response = netease_cloud_music_api.api("song_url_v1", {"id": 33894312, "level": "exhigh"})
|
||||
pprint(json.loads(response.text))
|
||||
response = netease_cloud_music_api.request("song_url_v1", {"id": 33894312, "level": "exhigh"})
|
||||
pprint(response)
|
||||
|
||||
|
||||
def search_test():
|
||||
# 搜索
|
||||
response = netease_cloud_music_api.api("search", {"keywords": "海阔天空"})
|
||||
response = netease_cloud_music_api.request("search", {"keywords": "海阔天空"})
|
||||
# print("|", response.text, "|")
|
||||
pprint(json.loads(response.text))
|
||||
pprint(response)
|
||||
|
||||
|
||||
def search_default_test():
|
||||
# 搜索
|
||||
response = netease_cloud_music_api.api("search_default")
|
||||
pprint(json.loads(response.text))
|
||||
response = netease_cloud_music_api.request("search_default")
|
||||
pprint(response)
|
||||
|
||||
|
||||
def user_account_test():
|
||||
# 获取用户账号信息
|
||||
response = netease_cloud_music_api.api("user_account")
|
||||
pprint(json.loads(response.text))
|
||||
response = netease_cloud_music_api.request("user_account")
|
||||
pprint(response)
|
||||
|
||||
|
||||
def comment_new_test():
|
||||
# 获取用户账号信息
|
||||
response = netease_cloud_music_api.api("comment_new", {
|
||||
response = netease_cloud_music_api.request("comment_new", {
|
||||
"type": "0",
|
||||
"id": "1407551413",
|
||||
"sortType": 3,
|
||||
@ -48,13 +50,50 @@ def comment_new_test():
|
||||
"pageNo": 2,
|
||||
"realIP": "116.25.146.177",
|
||||
})
|
||||
pprint(json.loads(response.text))
|
||||
pprint(response)
|
||||
|
||||
|
||||
def toplist_detail_test():
|
||||
# 获取用户账号信息
|
||||
response = netease_cloud_music_api.request("toplist_detail")
|
||||
pprint(response)
|
||||
|
||||
|
||||
def playlist_detail_test():
|
||||
# 获取用户账号信息
|
||||
response = netease_cloud_music_api.request("playlist_detail", {"id": 19723756})
|
||||
pprint(response)
|
||||
|
||||
|
||||
def top_playlist_highquality_test():
|
||||
response = netease_cloud_music_api.request("/top/playlist/highquality")
|
||||
pprint(response)
|
||||
|
||||
|
||||
def captcha_sent_test():
|
||||
response = netease_cloud_music_api.request("/captcha/sent", {"phone": "15234941791"})
|
||||
pprint(response)
|
||||
|
||||
|
||||
def login_cellphone_test():
|
||||
# 注意这里需要调用login_cellphone方法,而不是api方法,具体实现可以看main.py
|
||||
# 有后续操作的api都需要自己实现一下
|
||||
pass
|
||||
# response = netease_cloud_music_api.login_cellphone("15234941791", "9464")
|
||||
# pprint(response)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
print(api_list())
|
||||
print(api_help())
|
||||
# songv1_test()
|
||||
# search_test()
|
||||
# search_default_test()
|
||||
# user_account_test()
|
||||
# comment_new_test()
|
||||
# toplist_detail_test()
|
||||
# playlist_detail_test()
|
||||
# top_playlist_highquality_test()
|
||||
# captcha_sent_test()
|
||||
# login_cellphone_test()
|
||||
|
0
test_gender/__init__.py
Normal file
0
test_gender/__init__.py
Normal file
100
test_gender/generator.py
Normal file
100
test_gender/generator.py
Normal file
@ -0,0 +1,100 @@
|
||||
# 生成测试文件
|
||||
|
||||
import json
|
||||
from pprint import pprint
|
||||
|
||||
with open("../package/NeteaseCloudMusic/config.json", 'r', encoding='utf-8') as f:
|
||||
config = json.loads(f.read())
|
||||
|
||||
with open("template", 'r', encoding='utf-8') as f:
|
||||
template = f.read()
|
||||
|
||||
|
||||
def pathToName(path):
|
||||
if path[0] == "/":
|
||||
path = path[1:]
|
||||
return path.replace("/", "_")
|
||||
|
||||
|
||||
tests = ""
|
||||
|
||||
exclude = ["apicache.js", "memory-cache.js", "request_reference.js", "avatar_upload.js", "cloud.js",
|
||||
"playlist_cover_update.js", "voice_upload.js", "register_anonimous.js", "verify_getQr.js"]
|
||||
|
||||
for apiPath, value in config.items():
|
||||
apiName = pathToName(apiPath)
|
||||
|
||||
if (apiName+".js") in exclude:
|
||||
continue
|
||||
|
||||
apiExplain = value["explain"]
|
||||
|
||||
apiExample = []
|
||||
|
||||
for item in value["example"]:
|
||||
apiExample.append(item["query"])
|
||||
|
||||
content = (template.replace("{{apiName}}", apiName)
|
||||
.replace("{{explain}}", apiExplain)
|
||||
.replace("{{example}}", json.dumps(apiExample)))
|
||||
|
||||
if apiPath == "/song/order/update":
|
||||
content = content.replace(" == 200", " in [200, 401]")
|
||||
elif apiPath == "/follow":
|
||||
content = content.replace(" == 200", " in [200, 400, -462]")
|
||||
elif apiPath == "/user/record":
|
||||
content = content.replace(" == 200", " in [200, 400]")
|
||||
elif apiPath == "/artist/sub":
|
||||
content = content.replace(" == 200", " in [200, 400, -462]")
|
||||
elif apiPath == "/video/sub":
|
||||
content = content.replace(" == 200", " in [200, 408]")
|
||||
elif apiPath == "/playlist/subscribe":
|
||||
content = content.replace(" == 200", " in [200, 408, 501]")
|
||||
elif apiPath == "/playlist/track/add":
|
||||
content = content.replace(" == 200", " in [200, 404]")
|
||||
elif apiPath == "/playlist/track/delete":
|
||||
content = content.replace(" == 200", " in [200, 400]")
|
||||
elif apiPath == "/album/sub":
|
||||
content = content.replace(" == 200", " in [200, 401, 404]")
|
||||
elif apiPath == "/artist/detail":
|
||||
content = content.replace(" == 200", " in [200, 400, -460]")
|
||||
elif apiPath == "/recommend/songs/dislike":
|
||||
content = content.replace(" == 200", " in [200, 432]")
|
||||
elif apiPath == "/user/cloud/del":
|
||||
content = content.replace(" == 200", " in [200, 404]")
|
||||
elif apiPath == "/cloud/match":
|
||||
content = content.replace(" == 200", " in [200, 400]")
|
||||
elif apiPath == "/send/song":
|
||||
content = content.replace(" == 200", " in [200, 401]")
|
||||
elif apiPath == "/send/album":
|
||||
content = content.replace(" == 200", " in [200, 404]")
|
||||
elif apiPath == "/msg/comments":
|
||||
content = content.replace(" == 200", " in [200, 400]")
|
||||
elif apiPath == "/yunbei/rcmd/song":
|
||||
content = content.replace(" == 200", " in [200, 400]")
|
||||
elif apiPath == "/vip/growthpoint":
|
||||
content = content.replace(" == 200", " in [200, 400, 1000]")
|
||||
elif apiPath == "/vip/growthpoint/get":
|
||||
content = content.replace(" == 200", " in [200, 400, 1000]")
|
||||
elif apiPath == "/artist/fans":
|
||||
content = content.replace(" == 200", " in [200, 400, -460]")
|
||||
elif apiPath == "/inner/version":
|
||||
content = content.replace("assert (response[\"code\"] == 200 or response[\"data\"][\"code\"] == 200)",
|
||||
"assert (response is not None)")
|
||||
elif "musician" in apiPath:
|
||||
content = content.replace(" == 200", " in [200, 400, 600, 10000, 500, 404]")
|
||||
|
||||
tests += content + "\n\n\n"
|
||||
|
||||
with open("../api_test.py", 'w+', encoding='utf-8') as f:
|
||||
tests = ("from pytest_html import extras\n"
|
||||
"import json\n"
|
||||
"import os\n"
|
||||
"from pprint import pprint\n"
|
||||
"import dotenv\n"
|
||||
"from package.NeteaseCloudMusic import NeteaseCloudMusicApi, api_help, api_list\n"
|
||||
"dotenv.load_dotenv() # 从.env文件中加载环境变量\n"
|
||||
"netease_cloud_music_api = NeteaseCloudMusicApi() # 初始化API\n"
|
||||
"netease_cloud_music_api.cookie = os.getenv('COOKIE') # 设置cookie\n"
|
||||
"netease_cloud_music_api.DEBUG = True # 开启调试模式") + "\n\n\n" + tests
|
||||
f.write(tests)
|
1091
test_gender/report.html
Normal file
1091
test_gender/report.html
Normal file
File diff suppressed because one or more lines are too long
10
test_gender/some.txt
Normal file
10
test_gender/some.txt
Normal file
@ -0,0 +1,10 @@
|
||||
user_cloud_detail
|
||||
user_audio
|
||||
search_suggest
|
||||
search_multimatch
|
||||
resource_like
|
||||
related_allvideo
|
||||
playlist_update_playcount
|
||||
playlist_order_update
|
||||
playlist_delete
|
||||
comment_like
|
10
test_gender/template
Normal file
10
test_gender/template
Normal file
@ -0,0 +1,10 @@
|
||||
def test_{{apiName}}(extra):
|
||||
"""
|
||||
{{explain}}
|
||||
"""
|
||||
|
||||
example = {{example}}
|
||||
for query in example:
|
||||
response = netease_cloud_music_api.request("{{apiName}}", query)
|
||||
extra.append(extras.json(response, name="response"))
|
||||
assert (response["code"] == 200 or response["data"]["code"] == 200)
|
Loading…
Reference in New Issue
Block a user