chore:add towncier,bumpversion and publish workflow

This commit is contained in:
2061360308 2023-12-19 22:33:19 +08:00
parent cc1b12dc36
commit d6c1f3ae45
12 changed files with 396 additions and 74 deletions

7
.bumpversion.cfg Normal file
View 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
View 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 }}

View File

@ -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的更新操作

View File

@ -0,0 +1 @@
重新利用Max-Age判断cookie是否过期

View File

@ -0,0 +1 @@
修复因为补充的encodeURIComponent函数无法识别非字符的True或False而导致的cookie项remberme为空从而使login_refresh接口返回400的问题

View File

@ -44,6 +44,7 @@ if (typeof window === "undefined") {
} }
function encodeURIComponent(str) { function encodeURIComponent(str) {
str = String(str); // 将输入转换为字符串 以免TrueFalse等非字符无法转换
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,

View File

@ -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'

View File

@ -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,9 +79,10 @@ 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): # 检查缓存中是否已经有了结果
return self.cache.get(cache_key) if self.cache.get(cache_key):
return self.cache.get(cache_key)
if query is None: if query is None:
query = {} query = {}
@ -99,8 +105,9 @@ 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)
return result return 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

View 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))

View File

@ -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
View File

@ -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
View File

@ -0,0 +1,4 @@
[tool.towncrier]
version = "0.1.7"
filename = "CHANGELOG.md"
directory = "newsfragments"