From 21d19d6af78d6b1f2d8c04355822e83e15caace5 Mon Sep 17 00:00:00 2001 From: helloplhm-qwq Date: Sat, 13 Jan 2024 19:25:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20kw=E6=BA=90=E8=B4=A6=E5=8F=B7=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20&=20kuwodes=E6=8E=A5=E5=8F=A3=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/config.py | 52 ++++++++++++++++++-- modules/kw/__init__.py | 107 +++++++++++++++++++++++++++++++---------- 2 files changed, 128 insertions(+), 31 deletions(-) diff --git a/common/config.py b/common/config.py index ac214a9..badb558 100644 --- a/common/config.py +++ b/common/config.py @@ -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", }, }, + "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': { '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', } ], + 'kw': [ + { + "uid": "0", + "token": "", + "device_id": "0", + }, + ] }, }, } @@ -457,6 +489,7 @@ def push_to_list(key, obj): save_data(config) + def write_config(key, value): config = None with open('config.json', 'r', encoding='utf-8') as f: @@ -472,9 +505,11 @@ def write_config(key, value): current[keys[-1]] = value variable.config = config 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() + def read_default_config(key): try: config = default @@ -495,6 +530,7 @@ def read_default_config(key): except: return None + def _read_config(key): try: config = variable.config @@ -515,6 +551,7 @@ def _read_config(key): except (KeyError, TypeError): return None + def read_config(key): try: config = variable.config @@ -561,6 +598,7 @@ def write_data(key, value): save_data(config) + def initConfig(): try: with open("./config.json", "r", encoding="utf-8") as f: @@ -570,7 +608,8 @@ def initConfig(): logger.warning('配置文件并不是一个有效的字典,使用默认值') variable.config = default 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() except: if os.path.getsize("./config.json") != 0: @@ -627,10 +666,12 @@ value TEXT)''') if (read_config('common.proxy.enable')): if (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')): 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('代理功能已开启,请确保代理地址正确,否则无法连接网络') # cookie池 @@ -661,7 +702,8 @@ value TEXT)''') if (banlist != [] and banlistRaw == []): for b in banlist: banlistRaw.append(b['ip']) - return + return + def ban_ip(ip_addr, ban_time=-1): if read_config('security.banlist.enable'): diff --git a/modules/kw/__init__.py b/modules/kw/__init__.py index 16b23de..d458c7d 100644 --- a/modules/kw/__init__.py +++ b/modules/kw/__init__.py @@ -7,51 +7,106 @@ # ---------------------------------------- # 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.utils import CreateObject +from .encrypt import base64_encrypt tools = { 'qualityMap': { '128k': '128kmp3', '320k': '320kmp3', 'flac': '2000kflac', + 'flac24bit': '4000kflac', }, 'qualityMapReverse': { - '128': '128k', - '320': '320k', - '2000': 'flac', + 128: '128k', + 320: '320k', + 2000: 'flac', + 4000: 'flac24bit', }, 'extMap': { '128k': 'mp3', '320k': 'mp3', 'flac': 'flac', + 'flac24bit': 'flac', } } 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]}''' - req = await Httpx.AsyncRequest(target_url, { - 'method': 'GET', - 'headers': { - 'User-Agent': 'okhttp/3.10.0', - 'channel': 'qq', - 'plat': 'ar', - 'net': 'wifi', - 'ver': '3.1.2', - 'uid': '', - 'devId': '0', - } - }) - try: - body = req.json() - data = body['data'] + proto = config.read_config('module.kw.proto') + if (proto == 'bd-api'): + user_info = config.read_config('module.kw.user') if (not variable.use_cookie_pool) else random.choice(config.read_config('module.cookiepool.kw')) + 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']}''' + req = await Httpx.AsyncRequest(target_url, { + 'method': 'GET', + 'headers': { + 'User-Agent': 'Dart/2.14 (dart:io)', + 'channel': 'qq', + 'plat': 'ar', + 'net': 'wifi', + 'ver': '3.1.2', + 'uid': user_info['uid'], + 'devId': user_info['device_id'], + } + }) + 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') - return { - 'url': data['url'].split('?')[0], - 'quality': tools['qualityMapReverse'][data['audioInfo']['bitrate']] + 'url': url.split('?')[0], + 'quality': tools['qualityMapReverse'][bitrate] } - except: - raise FailedException('failed') \ No newline at end of file + else: + raise FailedException('配置文件参数proto填写错误或不支持') \ No newline at end of file