mirror of
https://github.com/2061360308/NeteaseCloudMusic_PythonSDK.git
synced 2024-11-22 06:57:42 +00:00
chore:add towncier,bumpversion and publish workflow
This commit is contained in:
parent
cc1b12dc36
commit
d6c1f3ae45
7
.bumpversion.cfg
Normal file
7
.bumpversion.cfg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[bumpversion]
|
||||||
|
current_version = 0.1.7
|
||||||
|
commit = True
|
||||||
|
tag = True
|
||||||
|
|
||||||
|
[bumpversion:file:package/setup.py]
|
||||||
|
[bumpversion:file:package/NeteaseCloudMusic/__init__.py]
|
54
.github/workflows/publish.yml
vendored
Normal file
54
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
name: Publish Python 🐍 distributions 📦 to PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-n-publish:
|
||||||
|
name: Build and publish Python 🐍 distributions 📦 to PyPI
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.11
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install build towncrier
|
||||||
|
|
||||||
|
- name: Generate release notes to "RELEASE_NOTES" Environment Variable
|
||||||
|
run: |
|
||||||
|
echo "RELEASE_NOTES=$(towncrier --draft)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Generate version notes with towncrier
|
||||||
|
run: |
|
||||||
|
towncrier --yes
|
||||||
|
|
||||||
|
- name: Build a binary wheel and a source tarball
|
||||||
|
run: |
|
||||||
|
cd package
|
||||||
|
python setup.py build
|
||||||
|
|
||||||
|
- name: Publish distribution 📦 to PyPI
|
||||||
|
run: |
|
||||||
|
cd package
|
||||||
|
pip install twine
|
||||||
|
twine upload dist/*
|
||||||
|
env:
|
||||||
|
TWINE_USERNAME: __token__
|
||||||
|
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: Release ${{ github.ref }}
|
||||||
|
body: ${{ env.RELEASE_NOTES }}
|
38
README.md
38
README.md
@ -35,15 +35,48 @@ print(api_list())
|
|||||||
|
|
||||||
> 注意: request(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`两种写法。
|
||||||
|
|
||||||
|
> api已加入自动缓存,在测试接口时,如果频繁调用获取结果在query里应该加上timestamp参数来区分,例如
|
||||||
|
> ```python
|
||||||
|
> response = netease_cloud_music_api.request("song_url_v1", {"id": 33894312, "level": "exhigh", "timestamp": time.time()})
|
||||||
|
> ```
|
||||||
|
|
||||||
### 开发
|
### 开发
|
||||||
- 克隆项目 `git clone git@github.com:2061360308/NeteaseCloudMusic_PythonSDK.git`
|
- 克隆项目 `git clone git@github.com:2061360308/NeteaseCloudMusic_PythonSDK.git`
|
||||||
- 安装依赖 `pip install -r requirements.txt`
|
- 安装依赖 `pip install -r requirements.txt`
|
||||||
- 目录/文件说明
|
- 目录/文件说明
|
||||||
|
```
|
||||||
├── package 项目包根目录
|
├── package 项目包根目录
|
||||||
├── test_gender 生成测试代码的脚本
|
├── test_gender 生成测试代码的脚本
|
||||||
├── test.py 手动测试/ 使用示例
|
├── test.py 手动测试/ 使用示例
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新
|
||||||
|
项目使用towncrier自动生成更新日志
|
||||||
|
|
||||||
|
在 newsfragments 目录下,创建一个新的文本文件。这个文件的名字应该是一个唯一的编号,后缀是 .rst。
|
||||||
|
例如,如果你正在处理编号为 123 的问题,你可以创建一个名为 123.feature.rst 的文件。
|
||||||
|
|
||||||
|
注意在 towncrier 中,新闻片段的类型通常由文件名的后缀决定。以下是一些常见的新闻片段类型:
|
||||||
|
- .feature: 用于描述新的特性或者功能。
|
||||||
|
- .bugfix: 用于描述一个 bug 修复。
|
||||||
|
- .doc: 用于描述文档的更改。
|
||||||
|
- .removal: 用于描述移除的特性或者功能。
|
||||||
|
- .misc: 用于描述其他类型的更改。
|
||||||
|
-
|
||||||
|
在这个文件中,写下你的更改的描述。这个描述应该是简短的,通常只有一到两句话。
|
||||||
|
例如`Added support for the XYZ feature.`
|
||||||
|
|
||||||
|
### 发布新版本
|
||||||
|
使用bumpversion自动更新版本号,提交并发布标签
|
||||||
|
你需要安装bumpversion然后执行
|
||||||
|
```bash
|
||||||
|
bumpversion patch # for a patch level increase (e.g., 1.0.0 to 1.0.1)
|
||||||
|
bumpversion minor # for a minor level increase (e.g., 1.0.0 to 1.1.0)
|
||||||
|
bumpversion major # for a major level increase (e.g., 1.0.0 to 2.0.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
接下来会自动更新版本号并提交到远程仓库,然后发布一个新的标签
|
||||||
|
workflow会依据标签自动发布相应资源并且发布到pypi
|
||||||
|
|
||||||
### 改进
|
### 改进
|
||||||
> 下列API未支持
|
> 下列API未支持
|
||||||
@ -88,3 +121,8 @@ print(api_list())
|
|||||||
- /fm_trash
|
- /fm_trash
|
||||||
|
|
||||||
### 欢迎提交PR
|
### 欢迎提交PR
|
||||||
|
|
||||||
|
更正request库返回多个cookie时自动用分号拼接导致后续cookie不好解析的问题
|
||||||
|
添加了对cookie中Max-Age为0的处理
|
||||||
|
改进判断cookie是否过期的逻辑
|
||||||
|
添加了自动处理cookie的更新操作
|
||||||
|
1
newsfragments/cookie_expires.bugfix.rst
Normal file
1
newsfragments/cookie_expires.bugfix.rst
Normal file
@ -0,0 +1 @@
|
|||||||
|
重新利用Max-Age判断cookie是否过期
|
1
newsfragments/login_refresh.bugfix.rst
Normal file
1
newsfragments/login_refresh.bugfix.rst
Normal file
@ -0,0 +1 @@
|
|||||||
|
修复因为补充的encodeURIComponent函数无法识别非字符的True或False而导致的cookie项remberme为空从而使login_refresh接口返回400的问题
|
@ -44,6 +44,7 @@ if (typeof window === "undefined") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function encodeURIComponent(str) {
|
function encodeURIComponent(str) {
|
||||||
|
str = String(str); // 将输入转换为字符串 以免True,False等非字符无法转换
|
||||||
var result = "";
|
var result = "";
|
||||||
|
|
||||||
if (str !== undefined && str !== null) {
|
if (str !== undefined && str !== null) {
|
||||||
@ -37928,7 +37929,7 @@ const hug_comment_resourceTypeMap = config_namespaceObject.A;
|
|||||||
});
|
});
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ./package.json
|
;// CONCATENATED MODULE: ./package.json
|
||||||
const package_namespaceObject = {"i8":"0.1.1"};
|
const package_namespaceObject = {"i8":"0.1.2"};
|
||||||
;// CONCATENATED MODULE: ./module/inner_version.js
|
;// CONCATENATED MODULE: ./module/inner_version.js
|
||||||
|
|
||||||
/* harmony default export */ const inner_version = ((query, request) => {
|
/* harmony default export */ const inner_version = ((query, request) => {
|
||||||
@ -38338,30 +38339,19 @@ var crypto_js = __webpack_require__(1292);
|
|||||||
;// CONCATENATED MODULE: ./module/login_refresh.js
|
;// CONCATENATED MODULE: ./module/login_refresh.js
|
||||||
// 登录刷新
|
// 登录刷新
|
||||||
|
|
||||||
/* harmony default export */ const login_refresh = (async (query, request) => {
|
/* harmony default export */ const login_refresh = ((query, request) => {
|
||||||
let result = await request(
|
return request(
|
||||||
'POST',
|
"POST",
|
||||||
`https://music.163.com/weapi/login/token/refresh`,
|
`https://music.163.com/weapi/login/token/refresh`,
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
crypto: 'weapi',
|
crypto: "weapi",
|
||||||
ua: 'pc',
|
ua: "pc",
|
||||||
cookie: query.cookie,
|
cookie: query.cookie,
|
||||||
proxy: query.proxy,
|
proxy: query.proxy,
|
||||||
realIP: query.realIP,
|
realIP: query.realIP,
|
||||||
},
|
|
||||||
)
|
|
||||||
if (result.body.code === 200) {
|
|
||||||
result = {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
...result.body,
|
|
||||||
cookie: result.cookie.join(';'),
|
|
||||||
},
|
|
||||||
cookie: result.cookie,
|
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
return result
|
|
||||||
});
|
});
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ./module/login_status.js
|
;// CONCATENATED MODULE: ./module/login_status.js
|
||||||
@ -42852,6 +42842,7 @@ const weapi = (object) => {
|
|||||||
for (let i = 0; i < 16; i++) {
|
for (let i = 0; i < 16; i++) {
|
||||||
secretKey += base62.charAt(Math.round(Math.random() * 61))
|
secretKey += base62.charAt(Math.round(Math.random() * 61))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
params: aesEncrypt(
|
params: aesEncrypt(
|
||||||
aesEncrypt(text, 'cbc', presetKey, iv),
|
aesEncrypt(text, 'cbc', presetKey, iv),
|
||||||
@ -43181,6 +43172,27 @@ function beforeRequest(name, query) {
|
|||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
;// CONCATENATED MODULE: ./afterRequest/login_refresh.js
|
||||||
|
/* harmony default export */ const afterRequest_login_refresh = ((response) => {
|
||||||
|
response = JSON.parse(response);
|
||||||
|
let cookie = response.cookie;
|
||||||
|
if (Array.isArray(cookie)) {
|
||||||
|
cookie = cookie.join(";");
|
||||||
|
}
|
||||||
|
if (response.body.code === 200) {
|
||||||
|
response = {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
...response.body,
|
||||||
|
cookie: cookie,
|
||||||
|
},
|
||||||
|
cookie: cookie,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ./afterRequest/login_status.js
|
;// CONCATENATED MODULE: ./afterRequest/login_status.js
|
||||||
/* harmony default export */ const afterRequest_login_status = ((response) => {
|
/* harmony default export */ const afterRequest_login_status = ((response) => {
|
||||||
response = JSON.parse(response);
|
response = JSON.parse(response);
|
||||||
@ -43255,9 +43267,11 @@ function beforeRequest(name, query) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* harmony default export */ const afterRequestApi = ({
|
/* harmony default export */ const afterRequestApi = ({
|
||||||
'check_music':afterRequest_check_music,
|
'check_music':afterRequest_check_music,
|
||||||
'login_cellphone':afterRequest_login_cellphone,
|
'login_cellphone':afterRequest_login_cellphone,
|
||||||
|
'login_refresh':afterRequest_login_refresh,
|
||||||
'login_status':afterRequest_login_status,
|
'login_status':afterRequest_login_status,
|
||||||
'related_playlist':afterRequest_related_playlist,
|
'related_playlist':afterRequest_related_playlist,
|
||||||
'top_playlist':afterRequest_top_playlist,
|
'top_playlist':afterRequest_top_playlist,
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
from .main import NeteaseCloudMusicApi
|
from .main import NeteaseCloudMusicApi
|
||||||
from .help import api_help, api_list
|
from .help import api_help, api_list
|
||||||
|
from .utils import format_cookie_str, prase_cookie_str
|
||||||
|
|
||||||
|
__version__ = '0.1.7'
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
import socket
|
import socket
|
||||||
|
import time
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
import http.cookies
|
import http.cookies
|
||||||
import datetime
|
import datetime
|
||||||
@ -10,6 +11,8 @@ import pkg_resources
|
|||||||
import requests
|
import requests
|
||||||
from py_mini_racer import py_mini_racer
|
from py_mini_racer import py_mini_racer
|
||||||
from .help import api_list
|
from .help import api_list
|
||||||
|
from .utils import format_cookie_str, prase_cookie_str
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
|
||||||
class NeteaseCloudMusicApi:
|
class NeteaseCloudMusicApi:
|
||||||
@ -20,12 +23,14 @@ class NeteaseCloudMusicApi:
|
|||||||
|
|
||||||
# cache = TTLCache(maxsize=100, ttl=120) # 设置缓存大小为100,缓存项的生存时间为120秒
|
# cache = TTLCache(maxsize=100, ttl=120) # 设置缓存大小为100,缓存项的生存时间为120秒
|
||||||
|
|
||||||
def __init__(self, debug=False):
|
def __init__(self, debug=False, cache=False):
|
||||||
self.DEBUG = debug # 是否开启调试模式
|
self.DEBUG = debug # 是否开启调试模式
|
||||||
|
self.CACHE = cache # 是否开启缓存
|
||||||
|
|
||||||
self.special_api = {"/playlist/track/all": self.playlist_track_all,
|
self.special_api = {"/playlist/track/all": self.playlist_track_all,
|
||||||
"/login/cellphone": self.login_cellphone,
|
"/login/cellphone": self.login_cellphone,
|
||||||
"/inner/version": self.inner_version}
|
"/inner/version": self.inner_version,
|
||||||
|
"/login/refresh": self.login_refresh}
|
||||||
|
|
||||||
# 载入js代码
|
# 载入js代码
|
||||||
resource_path = pkg_resources.resource_filename(__name__, 'NeteaseCloudMusicApi.js')
|
resource_path = pkg_resources.resource_filename(__name__, 'NeteaseCloudMusicApi.js')
|
||||||
@ -74,6 +79,7 @@ class NeteaseCloudMusicApi:
|
|||||||
# 生成一个唯一的键,用于在缓存中查找结果
|
# 生成一个唯一的键,用于在缓存中查找结果
|
||||||
cache_key = (name, frozenset(query.items()) if query else None)
|
cache_key = (name, frozenset(query.items()) if query else None)
|
||||||
|
|
||||||
|
if self.CACHE:
|
||||||
# 检查缓存中是否已经有了结果
|
# 检查缓存中是否已经有了结果
|
||||||
if self.cache.get(cache_key):
|
if self.cache.get(cache_key):
|
||||||
return self.cache.get(cache_key)
|
return self.cache.get(cache_key)
|
||||||
@ -99,6 +105,7 @@ class NeteaseCloudMusicApi:
|
|||||||
else:
|
else:
|
||||||
result = self.call_api(name, query)
|
result = self.call_api(name, query)
|
||||||
|
|
||||||
|
if self.CACHE:
|
||||||
# 将结果存入缓存
|
# 将结果存入缓存
|
||||||
self.cache.set(cache_key, result)
|
self.cache.set(cache_key, result)
|
||||||
|
|
||||||
@ -123,46 +130,47 @@ class NeteaseCloudMusicApi:
|
|||||||
if self.__cookie is None:
|
if self.__cookie is None:
|
||||||
if os.path.isfile("cookie_storage"):
|
if os.path.isfile("cookie_storage"):
|
||||||
with open("cookie_storage", "r", encoding='utf-8') as f:
|
with open("cookie_storage", "r", encoding='utf-8') as f:
|
||||||
self.cookie = f.read()
|
content = f.read()
|
||||||
|
try:
|
||||||
|
cookie_storage = json.loads(content)
|
||||||
|
# 验证cookie是否过期
|
||||||
|
create_time_stamp = cookie_storage['create_time_stamp']
|
||||||
|
|
||||||
|
if time.time() - create_time_stamp > 1296010:
|
||||||
|
# cookie过期了
|
||||||
|
self.__cookie = ""
|
||||||
|
else:
|
||||||
|
# 判断cookie生成时间是否超过1天
|
||||||
|
if time.time() - create_time_stamp > 86400:
|
||||||
|
# 更新cookie
|
||||||
|
# Todo login_refresh接口返回cookie好像少一些值
|
||||||
|
# self.request("/login/refresh", {"cookie": cookie_storage['cookie'], "timestamp": time.time()})
|
||||||
|
self.cookie = cookie_storage['cookie']
|
||||||
|
else:
|
||||||
|
self.__cookie = cookie_storage['cookie']
|
||||||
|
except json.JSONDecodeError and KeyError:
|
||||||
|
self.__cookie = ""
|
||||||
else:
|
else:
|
||||||
self.__cookie = "" # 如果没有cookie文件,就设置为空
|
self.__cookie = "" # 如果没有cookie文件,就设置为空
|
||||||
|
|
||||||
return self.__cookie
|
return self.__cookie
|
||||||
|
|
||||||
@cookie.setter
|
@cookie.setter
|
||||||
def cookie(self, cookie):
|
def cookie(self, value):
|
||||||
if cookie is None:
|
if value is None:
|
||||||
cookie = ""
|
self.__cookie = ""
|
||||||
|
return
|
||||||
|
|
||||||
_cookie = cookie
|
"判断cookie是否合法, 简单检查一下关键的键"
|
||||||
|
necessary_keys = ["__csrf", "MUSIC_A_T", "MUSIC_R_T"]
|
||||||
|
cookie_dict = prase_cookie_str(value)
|
||||||
|
for key in necessary_keys:
|
||||||
|
if cookie_dict.get(key) is None:
|
||||||
|
raise Exception(f"cookie is illegal, missing key: {key}.")
|
||||||
|
|
||||||
'''判断cookie是否过期'''
|
self.__cookie = value
|
||||||
|
|
||||||
# 创建一个Morsel对象,它可以解析cookie字符串
|
|
||||||
morsel = http.cookies.SimpleCookie(cookie)
|
|
||||||
# 获取当前时间
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
|
|
||||||
# 只判断 __csrf 是否过期
|
|
||||||
if not morsel.get('__csrf'):
|
|
||||||
# __csrf 不存在,不是有效cookie
|
|
||||||
_cookie = ""
|
|
||||||
else:
|
|
||||||
# 将过期时间字符串转换为datetime对象
|
|
||||||
expires = morsel.get('__csrf')['expires']
|
|
||||||
expires_datetime = datetime.datetime.strptime(expires, "%a, %d %b %Y %H:%M:%S GMT")
|
|
||||||
|
|
||||||
# 判断cookie是否过期
|
|
||||||
if now > expires_datetime:
|
|
||||||
# 过期了
|
|
||||||
_cookie = ""
|
|
||||||
else:
|
|
||||||
# 未过期
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.__cookie = _cookie
|
|
||||||
with open("cookie_storage", "w+", encoding='utf-8') as f:
|
with open("cookie_storage", "w+", encoding='utf-8') as f:
|
||||||
f.write(_cookie)
|
f.write(json.dumps({"cookie": value, "create_time_stamp": time.time()}, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ip(self):
|
def ip(self):
|
||||||
@ -173,18 +181,17 @@ class NeteaseCloudMusicApi:
|
|||||||
def call_api(self, name, query):
|
def call_api(self, name, query):
|
||||||
request_param = self.ctx.call('NeteaseCloudMusicApi.beforeRequest', name, query) # 拿到请求头和请求参数
|
request_param = self.ctx.call('NeteaseCloudMusicApi.beforeRequest', name, query) # 拿到请求头和请求参数
|
||||||
|
|
||||||
|
# Todo 了解 py_mini_racer 返回没有自动编码 而 node可以
|
||||||
param_data = {}
|
param_data = {}
|
||||||
if request_param["data"] != "":
|
if request_param["data"] != "":
|
||||||
for item in request_param["data"].split("&"):
|
for item in request_param["data"].split("&"):
|
||||||
|
# param_data[item.split("=")[0]] = urllib.parse.quote(item.split("=")[1], safe='') # 不需要编码后反而出错
|
||||||
param_data[item.split("=")[0]] = item.split("=")[1]
|
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":
|
if request_param.get("method") == "GET":
|
||||||
response = requests.get(request_param["url"], params=param_data, headers=request_param["headers"])
|
response = requests.get(request_param["url"], params=param_data, headers=request_param["headers"])
|
||||||
else:
|
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"])
|
||||||
# response = requests.post(request_param["url"], data=param_data, headers=request_param["headers"])
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(response.text)
|
data = json.loads(response.text)
|
||||||
@ -197,12 +204,6 @@ class NeteaseCloudMusicApi:
|
|||||||
"status": response.status_code,
|
"status": response.status_code,
|
||||||
}
|
}
|
||||||
|
|
||||||
# print("headers", response.headers)
|
|
||||||
# print("headers_dict", dict(response.headers))
|
|
||||||
|
|
||||||
# with open("response_result.json", "w+", encoding='utf-8') as f:
|
|
||||||
# f.write(json.dumps(response_result, indent=2, ensure_ascii=False))
|
|
||||||
|
|
||||||
result = self.ctx.call('NeteaseCloudMusicApi.afterRequest',
|
result = self.ctx.call('NeteaseCloudMusicApi.afterRequest',
|
||||||
json.dumps(response_result),
|
json.dumps(response_result),
|
||||||
request_param.get('crypto', None),
|
request_param.get('crypto', None),
|
||||||
@ -248,8 +249,29 @@ class NeteaseCloudMusicApi:
|
|||||||
"""
|
"""
|
||||||
result = self.call_api("/login/cellphone", query)
|
result = self.call_api("/login/cellphone", query)
|
||||||
|
|
||||||
# 自动 填充cookie
|
# 自动 更新cookie
|
||||||
if result["code"] == 200:
|
if result["code"] == 200:
|
||||||
if result.get("data").get("cookie"):
|
if result.get("data").get("cookie"):
|
||||||
self.cookie = result.get("data").get("cookie")
|
# cookie_str = format_cookie_str(result.get("data").get("cookie"))
|
||||||
|
cookie_str = result.get("data").get("cookie")
|
||||||
|
result["data"]["cookie"] = cookie_str
|
||||||
|
self.cookie = cookie_str
|
||||||
|
return result
|
||||||
|
|
||||||
|
def login_refresh(self, query):
|
||||||
|
"""
|
||||||
|
刷新登录状态
|
||||||
|
:param query:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
result = self.call_api("/login/refresh", query)
|
||||||
|
|
||||||
|
# 自动 更新cookie
|
||||||
|
# if result["code"] == 200:
|
||||||
|
# if result.get("data").get("cookie"):
|
||||||
|
# # cookie_str = format_cookie_str(result.get("data").get("cookie"))
|
||||||
|
# cookie_str = result.get("data").get("cookie")
|
||||||
|
# result["data"]["cookie"] = cookie_str
|
||||||
|
# pprint(cookie_str)
|
||||||
|
# self.cookie = cookie_str
|
||||||
return result
|
return result
|
||||||
|
124
package/NeteaseCloudMusic/utils.py
Normal file
124
package/NeteaseCloudMusic/utils.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
def format_cookie_str(cookie_str: str) -> str:
|
||||||
|
"""
|
||||||
|
格式化cookie字符串
|
||||||
|
功能
|
||||||
|
处理以逗号分割的cookie字符串
|
||||||
|
删除max-age为0的cookie
|
||||||
|
注意:
|
||||||
|
传入cookie字符串必须是以 逗号+空格 或 分号+空格 分割的规范cookie字符串
|
||||||
|
:param cookie_str:
|
||||||
|
:return: 格式化后的cookie str
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not cookie_str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 解析cookie字符串
|
||||||
|
cookie_dict = prase_cookie_str(cookie_str)
|
||||||
|
|
||||||
|
cookie_valid_dict = {}
|
||||||
|
# 删除max-age为0的cookie
|
||||||
|
for item in cookie_dict.keys():
|
||||||
|
max_age = cookie_dict[item].get("Max-Age")
|
||||||
|
if max_age:
|
||||||
|
max_age = int(max_age)
|
||||||
|
if max_age != 0:
|
||||||
|
cookie_valid_dict[item] = cookie_dict[item]
|
||||||
|
|
||||||
|
# 重新拼接cookie字符串
|
||||||
|
cookie = ""
|
||||||
|
for item in cookie_valid_dict.keys():
|
||||||
|
cookie += item + "=" + cookie_valid_dict[item]["value"] + "; "
|
||||||
|
for key in cookie_valid_dict[item].keys():
|
||||||
|
if key != "value":
|
||||||
|
cookie += key + "=" + cookie_valid_dict[item][key] + "; "
|
||||||
|
|
||||||
|
return cookie[: -2]
|
||||||
|
|
||||||
|
|
||||||
|
def prase_cookie_str(cookie_str: str) -> dict:
|
||||||
|
"""
|
||||||
|
解析cookie字符串
|
||||||
|
功能
|
||||||
|
处理以逗号分割的cookie字符串
|
||||||
|
注意:
|
||||||
|
传入cookie字符串必须是以 逗号+空格 或 分号+空格 分割的规范cookie字符串
|
||||||
|
:param cookie_str:
|
||||||
|
:return: cookie dict
|
||||||
|
"""
|
||||||
|
if not cookie_str:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 解决用逗号分割的问题
|
||||||
|
cookie_part = []
|
||||||
|
for item in cookie_str.split(", "):
|
||||||
|
cookie_part.extend(item.split("; "))
|
||||||
|
|
||||||
|
cookies = []
|
||||||
|
for item in cookie_part:
|
||||||
|
if "=" not in item:
|
||||||
|
# 属于上一个cookie的一部分
|
||||||
|
cookies[-1] = cookies[-1] + item
|
||||||
|
else:
|
||||||
|
cookies.append(item)
|
||||||
|
|
||||||
|
cookie_dict = {}
|
||||||
|
|
||||||
|
current_name = ""
|
||||||
|
for cookie in cookies:
|
||||||
|
# print(cookie)
|
||||||
|
if cookie[-1] == "=":
|
||||||
|
name = cookie[:-1]
|
||||||
|
value = ""
|
||||||
|
else:
|
||||||
|
part = cookie.split("=")
|
||||||
|
name = part[0]
|
||||||
|
value = part[1]
|
||||||
|
|
||||||
|
if name in ["Max-Age", "Expires", "Path", "Domain"]:
|
||||||
|
cookie_dict[current_name][name] = value
|
||||||
|
else:
|
||||||
|
cookie_dict[name] = {"value": value}
|
||||||
|
current_name = name
|
||||||
|
|
||||||
|
return cookie_dict
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cookie = ("MUSIC_R_T=1579622000495; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; "
|
||||||
|
"Path=/openapi/clientlog; Domain=.music.163.com, MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, "
|
||||||
|
"02 Jan 2092 11:39:38 GMT; Path=/wapi/feedback; Domain=.music.163.com, __remember_me=true; "
|
||||||
|
"Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/; Domain=.music.163.com, "
|
||||||
|
"MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/api/feedback; "
|
||||||
|
"Domain=.music.163.com, MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; "
|
||||||
|
"Path=/eapi/clientlog; Domain=.music.163.com, MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, "
|
||||||
|
"02 Jan 2092 11:39:38 GMT; Path=/api/clientlog; Domain=.music.163.com, MUSIC_R_T=1579622000495; "
|
||||||
|
"Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/wapi/feedback; Domain=.music.163.com, "
|
||||||
|
"MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/weapi/clientlog; "
|
||||||
|
"Domain=.music.163.com, MUSIC_R_T=1579622000495; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; "
|
||||||
|
"Path=/neapi/feedback; Domain=.music.163.com, MUSIC_R_T=1579622000495; Max-Age=2147483647; Expires=Wed, "
|
||||||
|
"02 Jan 2092 11:39:38 GMT; Path=/eapi/clientlog; Domain=.music.163.com, MUSIC_R_T=1579622000495; "
|
||||||
|
"Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/api/feedback; Domain=.music.163.com, "
|
||||||
|
"MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/weapi/feedback; "
|
||||||
|
"Domain=.music.163.com, MUSIC_R_T=1579622000495; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; "
|
||||||
|
"Path=/eapi/feedback; Domain=.music.163.com, MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, "
|
||||||
|
"02 Jan 2092 11:39:38 GMT; Path=/neapi/clientlog; Domain=.music.163.com, MUSIC_R_T=1579622000495; "
|
||||||
|
"Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/wapi/clientlog; Domain=.music.163.com, "
|
||||||
|
"MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/wapi/clientlog; "
|
||||||
|
"Domain=.music.163.com, MUSIC_R_T=1579622000495; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; "
|
||||||
|
"Path=/weapi/feedback; Domain=.music.163.com, NMTID=00OLqRn-wykrFFXdElLpZA3_-aaQtIAAAGMbJSp9A; "
|
||||||
|
"Max-Age=315360000; Expires=Mon, 12 Dec 2033 08:25:31 GMT; Path=/; Domain=music.163.com, "
|
||||||
|
"__csrf=4d5e515015723fd3d7edaa5b05f35b4d; Max-Age=1296010; Expires=Sat, 30 Dec 2023 08:25:41 GMT; Path=/; "
|
||||||
|
"Domain=.music.163.com, MUSIC_R_T=1579622000495; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; "
|
||||||
|
"Path=/neapi/clientlog; Domain=.music.163.com, MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, "
|
||||||
|
"02 Jan 2092 11:39:38 GMT; Path=/openapi/clientlog; Domain=.music.163.com, MUSIC_A_T=1579621885297; "
|
||||||
|
"Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/eapi/feedback; Domain=.music.163.com, "
|
||||||
|
"MUSIC_R_T=1579622000495; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/weapi/clientlog; "
|
||||||
|
"Domain=.music.163.com, MUSIC_R_T=1579622000495; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; "
|
||||||
|
"Path=/api/clientlog; Domain=.music.163.com, MUSIC_SNS=; Max-Age=0; Expires=Fri, 15 Dec 2023 08:25:31 GMT; "
|
||||||
|
"Path=/, "
|
||||||
|
"MUSIC_U=00CCEEF2F7A9825637C9869A455B5CA8E009D905415134A7EDAC2E404A940B2D58BFF7129A413193E9A8FD6D1D497E1E8E9023611BA91D2CB28FA94B53DBE4C42699A30FF1448C8EFAF5B00E73ADD353C52F2E004C094AEAC4BA4D83C6FFBB4CD532DEEAEA8D5584E69AC78110BBA682829C263DA72F5AA290AD826A47F640CA3903AC652E4DDE946ACC4EB8A63E5853FA695B480A919CF84498B7084C5CA9A91297F2CF5AF45C2D545AC0D5C03A1641306BAC0FB6FFD57BC653A91F2483FB52BFE85DE39280B012BC036DF244883D9700480E5FDDCD5A417C60241AE08CD6AA84BFC1C8890AE4A08286144D9D1146E33C67CF7797CDB5A961C85DB5AC492B232601D80D4DB07E4F170F635ABC01E080B4D32408FA0698DAD95AD80CDB99F2F672638048F5116214319175B596BF68B2A7A0269746EABFD919D62C165353725E6B1253A7C5D6774D453DDDCC28524D3378; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/; Domain=.music.163.com, MUSIC_A_T=1579621885297; Max-Age=2147483647; Expires=Wed, 02 Jan 2092 11:39:38 GMT; Path=/neapi/feedback; Domain=.music.163.com")
|
||||||
|
pprint(prase_cookie_str(cookie))
|
@ -49,11 +49,17 @@ with open(os.path.join(here, 'README.md'), 'w+', encoding='utf-8') as f:
|
|||||||
f.write(README)
|
f.write(README)
|
||||||
print("copy README.md end")
|
print("copy README.md end")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with io.open(os.path.join(here, 'CHANGELOG.md'), encoding='utf-8') as f:
|
||||||
|
changelog = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
changelog = ''
|
||||||
|
|
||||||
# Import the README and use it as the long-description.
|
# 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!
|
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
|
||||||
try:
|
try:
|
||||||
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
|
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
|
||||||
long_description = '\n' + f.read()
|
long_description = '\n' + changelog + '\n' + f.read()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
long_description = DESCRIPTION
|
long_description = DESCRIPTION
|
||||||
|
|
||||||
@ -71,7 +77,6 @@ with open(os.path.join(here, 'NeteaseCloudMusic/config.json'), 'w+', encoding='u
|
|||||||
f.write(json.dumps(config, indent=2, ensure_ascii=False))
|
f.write(json.dumps(config, indent=2, ensure_ascii=False))
|
||||||
print("copy config.json end")
|
print("copy config.json end")
|
||||||
|
|
||||||
|
|
||||||
# Load the package's __version__.py module as a dictionary.
|
# Load the package's __version__.py module as a dictionary.
|
||||||
about = {}
|
about = {}
|
||||||
if not VERSION:
|
if not VERSION:
|
||||||
@ -119,6 +124,36 @@ class UploadCommand(Command):
|
|||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
class BuildCommand(Command):
|
||||||
|
"""Support setup.py build."""
|
||||||
|
|
||||||
|
description = 'Build 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))
|
||||||
|
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
# Where the magic happens:
|
# Where the magic happens:
|
||||||
setup(
|
setup(
|
||||||
name=NAME,
|
name=NAME,
|
||||||
|
29
test.py
29
test.py
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
from enum import Enum
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
import dotenv
|
import dotenv
|
||||||
|
|
||||||
@ -18,7 +20,7 @@ netease_cloud_music_api.DEBUG = True # 开启调试模式
|
|||||||
|
|
||||||
def song_url_v1_test():
|
def song_url_v1_test():
|
||||||
# 获取歌曲详情
|
# 获取歌曲详情
|
||||||
response = netease_cloud_music_api.request("song_url_v1", {"id": 33894312, "level": "exhigh"})
|
response = netease_cloud_music_api.request("song_url_v1", {"id": '1880562045', "level": "exhigh"})
|
||||||
pprint(response)
|
pprint(response)
|
||||||
|
|
||||||
|
|
||||||
@ -37,7 +39,7 @@ def search_default_test():
|
|||||||
|
|
||||||
def user_account_test():
|
def user_account_test():
|
||||||
# 获取用户账号信息
|
# 获取用户账号信息
|
||||||
response = netease_cloud_music_api.request("user_account")
|
response = netease_cloud_music_api.request("user_account", query={"timestamp": time.time()})
|
||||||
pprint(response)
|
pprint(response)
|
||||||
|
|
||||||
|
|
||||||
@ -72,12 +74,17 @@ def top_playlist_highquality_test():
|
|||||||
|
|
||||||
|
|
||||||
def captcha_sent_test():
|
def captcha_sent_test():
|
||||||
response = netease_cloud_music_api.request("/captcha/sent", {"phone": "15229954305"})
|
response = netease_cloud_music_api.request("/captcha/sent", {"phone": "15234941791", "timestamp": time.time()})
|
||||||
pprint(response)
|
pprint(response)
|
||||||
|
|
||||||
|
|
||||||
def login_cellphone_test():
|
def login_cellphone_test():
|
||||||
response = netease_cloud_music_api.request("/login/cellphone",{"phone": "15229954305", "captcha": "4273"})
|
response = netease_cloud_music_api.request("/login/cellphone",
|
||||||
|
{
|
||||||
|
"phone": "15234941791",
|
||||||
|
"captcha": "9159",
|
||||||
|
"timestamp": time.time()
|
||||||
|
})
|
||||||
pprint(response)
|
pprint(response)
|
||||||
|
|
||||||
|
|
||||||
@ -91,6 +98,16 @@ def top_mv_test():
|
|||||||
pprint(response)
|
pprint(response)
|
||||||
|
|
||||||
|
|
||||||
|
def playlist_track_all_test():
|
||||||
|
response = netease_cloud_music_api.request("playlist_track_all", {'id': '592179800'})
|
||||||
|
pprint(response)
|
||||||
|
|
||||||
|
|
||||||
|
def login_refresh():
|
||||||
|
response = netease_cloud_music_api.request("login_refresh", {"timestamp": time.time()})
|
||||||
|
pprint(response)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
pass
|
pass
|
||||||
# print(api_list())
|
# print(api_list())
|
||||||
@ -99,10 +116,12 @@ if __name__ == '__main__':
|
|||||||
# top_mv_test()
|
# top_mv_test()
|
||||||
# search_test()
|
# search_test()
|
||||||
# search_default_test()
|
# search_default_test()
|
||||||
# user_account_test()
|
|
||||||
# comment_new_test()
|
# comment_new_test()
|
||||||
# toplist_detail_test()
|
# toplist_detail_test()
|
||||||
# playlist_detail_test()
|
# playlist_detail_test()
|
||||||
# top_playlist_highquality_test()
|
# top_playlist_highquality_test()
|
||||||
# captcha_sent_test()
|
# captcha_sent_test()
|
||||||
# login_cellphone_test()
|
# login_cellphone_test()
|
||||||
|
user_account_test()
|
||||||
|
# playlist_track_all_test()
|
||||||
|
# login_refresh()
|
||||||
|
4
towncrier.toml
Normal file
4
towncrier.toml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[tool.towncrier]
|
||||||
|
version = "0.1.7"
|
||||||
|
filename = "CHANGELOG.md"
|
||||||
|
directory = "newsfragments"
|
Loading…
Reference in New Issue
Block a user