feat: kw源账号功能 & kuwodes接口支持

This commit is contained in:
helloplhm-qwq 2024-01-13 19:25:29 +08:00
parent 65ad90a3cc
commit 21d19d6af7
No known key found for this signature in database
GPG Key ID: 6BE1B64B905567C7
2 changed files with 128 additions and 31 deletions

View File

@ -226,6 +226,31 @@ default = {
"useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36", "useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36",
}, },
}, },
"kw": {
"desc": "酷我音乐相关配置proto支持值['bd-api', 'kuwodes']",
"proto": "bd-api",
"user": {
"uid": "0",
"token": "",
"device_id": "0",
},
"des": {
"desc": "kuwodes接口mobi, nmobi一类的加密相关配置",
"f": "kuwo",
"need_encrypt": True,
"param填写注释": "{songId}为歌曲id, {map_quality}为map后的歌曲音质酷我规范, {raw_quality}为请求时的歌曲音质LX规范, {ext}为歌曲文件扩展名",
"params": "type=convert_url_with_sign&rid={songId}&quality={map_quality}&ext={ext}",
"host": "nmobi.kuwo.cn",
"path": "mobi.s",
"response_types": ['这里是reponse_type的所有支持值当设置为json时会使用到下面的两个值来获取url/bitrate如果为text则为传统的逐行解析方式', 'json', 'text'],
"response_type": "json",
"url_json_path": "data.url",
"bitrate_json_path": "data.bitrate",
"headers": {
"User-Agent": 'okhttp/3.10.0'
}
}
},
'cookiepool': { 'cookiepool': {
'kg': [ 'kg': [
{ {
@ -258,6 +283,13 @@ default = {
'useragent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36', 'useragent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36',
} }
], ],
'kw': [
{
"uid": "0",
"token": "",
"device_id": "0",
},
]
}, },
}, },
} }
@ -457,6 +489,7 @@ def push_to_list(key, obj):
save_data(config) save_data(config)
def write_config(key, value): def write_config(key, value):
config = None config = None
with open('config.json', 'r', encoding='utf-8') as f: with open('config.json', 'r', encoding='utf-8') as f:
@ -472,9 +505,11 @@ def write_config(key, value):
current[keys[-1]] = value current[keys[-1]] = value
variable.config = config variable.config = config
with open('config.json', 'w', encoding='utf-8') as f: with open('config.json', 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False, escape_forward_slashes=False) json.dump(config, f, indent=2, ensure_ascii=False,
escape_forward_slashes=False)
f.close() f.close()
def read_default_config(key): def read_default_config(key):
try: try:
config = default config = default
@ -495,6 +530,7 @@ def read_default_config(key):
except: except:
return None return None
def _read_config(key): def _read_config(key):
try: try:
config = variable.config config = variable.config
@ -515,6 +551,7 @@ def _read_config(key):
except (KeyError, TypeError): except (KeyError, TypeError):
return None return None
def read_config(key): def read_config(key):
try: try:
config = variable.config config = variable.config
@ -561,6 +598,7 @@ def write_data(key, value):
save_data(config) save_data(config)
def initConfig(): def initConfig():
try: try:
with open("./config.json", "r", encoding="utf-8") as f: with open("./config.json", "r", encoding="utf-8") as f:
@ -570,7 +608,8 @@ def initConfig():
logger.warning('配置文件并不是一个有效的字典,使用默认值') logger.warning('配置文件并不是一个有效的字典,使用默认值')
variable.config = default variable.config = default
with open("./config.json", "w", encoding="utf-8") as f: with open("./config.json", "w", encoding="utf-8") as f:
f.write(json.dumps(variable.config, indent=2, ensure_ascii=False, escape_forward_slashes=False)) f.write(json.dumps(variable.config, indent=2,
ensure_ascii=False, escape_forward_slashes=False))
f.close() f.close()
except: except:
if os.path.getsize("./config.json") != 0: if os.path.getsize("./config.json") != 0:
@ -627,10 +666,12 @@ value TEXT)''')
if (read_config('common.proxy.enable')): if (read_config('common.proxy.enable')):
if (read_config('common.proxy.http_value')): if (read_config('common.proxy.http_value')):
os.environ['http_proxy'] = read_config('common.proxy.http_value') os.environ['http_proxy'] = read_config('common.proxy.http_value')
logger.info('HTTP协议代理地址: ' + read_config('common.proxy.http_value')) logger.info('HTTP协议代理地址: ' +
read_config('common.proxy.http_value'))
if (read_config('common.proxy.https_value')): if (read_config('common.proxy.https_value')):
os.environ['https_proxy'] = read_config('common.proxy.https_value') os.environ['https_proxy'] = read_config('common.proxy.https_value')
logger.info('HTTPS协议代理地址: ' + read_config('common.proxy.https_value')) logger.info('HTTPS协议代理地址: ' +
read_config('common.proxy.https_value'))
logger.info('代理功能已开启,请确保代理地址正确,否则无法连接网络') logger.info('代理功能已开启,请确保代理地址正确,否则无法连接网络')
# cookie池 # cookie池
@ -663,6 +704,7 @@ value TEXT)''')
banlistRaw.append(b['ip']) banlistRaw.append(b['ip'])
return return
def ban_ip(ip_addr, ban_time=-1): def ban_ip(ip_addr, ban_time=-1):
if read_config('security.banlist.enable'): if read_config('security.banlist.enable'):
banList = read_data('banList') banList = read_data('banList')

View File

@ -7,51 +7,106 @@
# ---------------------------------------- # ----------------------------------------
# This file is part of the "lx-music-api-server" project. # This file is part of the "lx-music-api-server" project.
from common import Httpx import random
from common import Httpx, config, variable
from common.exceptions import FailedException from common.exceptions import FailedException
from common.utils import CreateObject
from .encrypt import base64_encrypt
tools = { tools = {
'qualityMap': { 'qualityMap': {
'128k': '128kmp3', '128k': '128kmp3',
'320k': '320kmp3', '320k': '320kmp3',
'flac': '2000kflac', 'flac': '2000kflac',
'flac24bit': '4000kflac',
}, },
'qualityMapReverse': { 'qualityMapReverse': {
'128': '128k', 128: '128k',
'320': '320k', 320: '320k',
'2000': 'flac', 2000: 'flac',
4000: 'flac24bit',
}, },
'extMap': { 'extMap': {
'128k': 'mp3', '128k': 'mp3',
'320k': 'mp3', '320k': 'mp3',
'flac': 'flac', 'flac': 'flac',
'flac24bit': 'flac',
} }
} }
async def url(songId, quality): async def url(songId, quality):
target_url = f'''https://bd-api.kuwo.cn/api/service/music/downloadInfo/{songId}?isMv=0&format={tools['extMap'][quality]}&br={tools['qualityMap'][quality]}''' proto = config.read_config('module.kw.proto')
req = await Httpx.AsyncRequest(target_url, { if (proto == 'bd-api'):
'method': 'GET', user_info = config.read_config('module.kw.user') if (not variable.use_cookie_pool) else random.choice(config.read_config('module.cookiepool.kw'))
'headers': { target_url = f'''https://bd-api.kuwo.cn/api/service/music/downloadInfo/{songId}?isMv=0&format={tools['extMap'][quality]}&br={tools['qualityMap'][quality]}&uin={user_info['uid']}&token={user_info['token']}'''
'User-Agent': 'okhttp/3.10.0', req = await Httpx.AsyncRequest(target_url, {
'channel': 'qq', 'method': 'GET',
'plat': 'ar', 'headers': {
'net': 'wifi', 'User-Agent': 'Dart/2.14 (dart:io)',
'ver': '3.1.2', 'channel': 'qq',
'uid': '', 'plat': 'ar',
'devId': '0', 'net': 'wifi',
} 'ver': '3.1.2',
}) 'uid': user_info['uid'],
try: 'devId': user_info['device_id'],
body = req.json() }
data = body['data'] })
try:
body = req.json()
data = body['data']
if (body['code'] != 200) or (data['audioInfo']['bitrate'] == 1): if (body['code'] != 200) or (data['audioInfo']['bitrate'] == 1):
raise FailedException('failed')
return {
'url': data['url'].split('?')[0],
'quality': tools['qualityMapReverse'][data['audioInfo']['bitrate']]
}
except:
raise FailedException('failed')
elif (proto == 'kuwodes'):
des_info = config.read_config('module.kw.des')
params = des_info['params'].format(
songId = songId,
map_quality = tools['qualityMap'][quality],
ext = tools['extMap'][quality],
raw_quality = quality,
)
target_url = f'https://{des_info["host"]}/{des_info["path"]}?f={des_info["f"]}&' + ('q=' + base64_encrypt(params) if (des_info["need_encrypt"]) else params)
req = await Httpx.AsyncRequest(target_url, {
'method': 'GET',
'headers': des_info['headers']
})
url = ''
bitrate = 1
if (des_info["response_type"] == 'json'):
body = req.json()
for p in des_info['url_json_path'].split('.'):
url = body.get(p)
if (url == None):
raise FailedException('failed')
for p in des_info['bitrate_json_path'].split('.'):
bitrate = body.get(p)
if (bitrate == None):
raise FailedException('failed')
elif (des_info['response_type'] == 'text'):
body = req.text
for l in body.split('\n'):
l = l.strip()
if (l.startswith('url=')):
url = l.split('=')[1]
elif (l.startswith('bitrate=')):
bitrate = int(l.split('=')[1])
else:
raise FailedException('配置文件参数response_type填写错误或不支持')
bitrate = int(bitrate)
if (url == '' or bitrate == 1):
raise FailedException('failed')
if (not url.startswith('http')):
raise FailedException('failed') raise FailedException('failed')
return { return {
'url': data['url'].split('?')[0], 'url': url.split('?')[0],
'quality': tools['qualityMapReverse'][data['audioInfo']['bitrate']] 'quality': tools['qualityMapReverse'][bitrate]
} }
except: else:
raise FailedException('failed') raise FailedException('配置文件参数proto填写错误或不支持')