diff --git a/.gitignore b/.gitignore index fdf60c0..771f77c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ test.py logs config.json *.log +*.bak +*.tmp # VSCode .history diff --git a/common/config.py b/common/config.py index d165e25..1ff630e 100644 --- a/common/config.py +++ b/common/config.py @@ -168,6 +168,7 @@ default = { "_clientver-desc": "客户端versioncode,pidversionsecret可能随此值而变化", "pidversionsecret": "57ae12eb6890223e355ccfcb74edf70d", "_pidversionsecret-desc": "获取URL时所用的key值计算验证值", + "pid": "2", }, "tracker": { "desc": "trackerapi请求配置,不懂请保持默认,修改请统一为字符串格式", @@ -187,6 +188,15 @@ default = { "token": "", "userid": "0", "mid": "114514", + "lite_sign_in": { + "desc": "是否启用概念版自动签到,仅在appid=3116时运行", + "enable": False, + "interval": 86400, + "mixsongmid": { + "desc": "mix_songmid的获取方式, 默认auto, 可以改成一个数字手动", + "value": "auto" + } + } } }, "tx": { @@ -257,6 +267,15 @@ default = { 'userid': '0', 'token': '', 'mid': '114514', + "lite_sign_in": { + "desc": "是否启用概念版自动签到,仅在appid=3116时运行", + "enable": False, + "interval": 86400, + "mixsongmid": { + "desc": "mix_songmid的获取方式, 默认auto, 可以改成一个数字手动", + "value": "auto" + } + } }, ], 'tx': [ diff --git a/common/lx_script.py b/common/lx_script.py index 6eae50e..40fd5f4 100644 --- a/common/lx_script.py +++ b/common/lx_script.py @@ -10,7 +10,6 @@ from . import Httpx from . import config from . import scheduler -from .variable import iscn from .log import log from aiohttp.web import Response import ujson as json @@ -70,12 +69,12 @@ async def get_script(): async def generate_script_response(request): if (request.query.get('key') != config.read_config('security.key.value') and config.read_config('security.key.enable')): - return Response(body = json.dumps({'code': 6, 'msg': 'key验证失败', 'data': None}, indent=2, ensure_ascii=False), content_type='application/json', status = 400) + return {'code': 6, 'msg': 'key验证失败', 'data': None}, 403 try: with open('./lx-music-source-example.js', 'r', encoding='utf-8') as f: script = f.read() except: - return Response(body = json.dumps({'code': 4, 'msg': '本地无源脚本', 'data': None}, indent=2, ensure_ascii=False), content_type='application/json', status = 500) + return {'code': 4, 'msg': '本地无源脚本', 'data': None}, 400 scriptLines = script.split('\n') newScriptLines = [] for line in scriptLines: diff --git a/main.py b/main.py index ca9a800..12e8ba4 100644 --- a/main.py +++ b/main.py @@ -91,10 +91,14 @@ async def handle_before_request(app, handler): return handleResult({'code': 6, 'msg': '未找到您所请求的资源', 'data': None}, 404) resp = await handler(request) - if (isinstance(resp, str)): - resp = Response(body = resp, content_type='text/plain', status = 200) - elif (isinstance(resp, (list, dict))): + if (isinstance(resp, (str, list, dict))): resp = handleResult(resp) + elif (isinstance(resp, tuple) and len(resp) == 2): # flask like response + body, status = resp + if (isinstance(body, (str, list, dict))): + resp = handleResult(body, status) + else: + resp = Response(body = str(body), content_type='text/plain', status = status) elif (not isinstance(resp, Response)): resp = Response(body = str(resp), content_type='text/plain', status = 200) aiologger.info(f'{request.remote_addr + ("" if (request.remote == request.remote_addr) else f"|proxy@{request.remote}")} - {request.method} "{request.path}", {resp.status}') diff --git a/modules/kg/__init__.py b/modules/kg/__init__.py index 0c2ae0b..b237971 100644 --- a/modules/kg/__init__.py +++ b/modules/kg/__init__.py @@ -17,6 +17,7 @@ from .lyric import lyricSearchByHash as _lyricSearch from .mv import getMvInfo as _getMvInfo from .mv import getMvPlayURL as _getMvUrl from .search import getSongSearchResult as _songsearch +from . import lite_signin from common.exceptions import FailedException from common import Httpx from common import utils diff --git a/modules/kg/lite_signin.py b/modules/kg/lite_signin.py new file mode 100644 index 0000000..5c43bcf --- /dev/null +++ b/modules/kg/lite_signin.py @@ -0,0 +1,167 @@ +# ---------------------------------------- +# - mode: python - +# - author: helloplhm-qwq - +# - name: lite_signin.py - +# - project: lx-music-api-server - +# - license: MIT - +# ---------------------------------------- +# This file is part of the "lx-music-api-server" project. + +from common.exceptions import FailedException +from .utils import buildRequestParams, sign +from common import Httpx, config, utils, variable, scheduler, log +import random +import binascii +import time + +logger = log.log('kugou_lite_sign_in') + + +async def randomMixSongMid(): + ''' + 通过TOP500榜单获取随机歌曲的mixsongmid + ''' + # 声明榜单url + rankUrl = 'http://mobilecdnbj.kugou.com/api/v3/rank/song?version=9108&ranktype=1&plat=0&pagesize=100&area_code=1&page=1&rankid=8888&with_res_tag=0&show_portrait_mv=1' + # 请求 + res = await Httpx.AsyncRequest(rankUrl, { + "method": 'GET' + }) + data = res.json() + if (data.get('status') != 1): + raise FailedException('排行榜获取失败') + + # 随机选择一首歌曲 + randomSong = random.choice(data['data']['info']) + + # 因为排行榜api不会返回mixsongmid + # 所以需要进行一次搜索接口来获取 + search_req = await Httpx.AsyncRequest(utils.encodeURI(f'https://songsearch.kugou.com/song_search_v2?' + buildRequestParams({ + "keyword": randomSong['filename'], + "page": 1, + "pagesize": 1, + "userid": 0, + "clientver": "", + "platform": "WebFilter", + "filter": 2, + "iscorrection": 1, + "privilege_filter": 0 + })), { + "headers": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.142.86 Safari/537.36", + "Referer": "https://www.kugou.com", + } + }) + + body = search_req.json() + + if (body.get('status') != 1): + raise FailedException('歌曲搜索失败') + if (body['data']['total'] == 0 or body['data']['lists'] == []): + raise FailedException('歌曲搜索失败') + + return body['data']['lists'][0]['MixSongID'] + + +async def do_account_signin(user_info): + ''' + 签到主函数,传入userinfo,响应None就是成功,报错即为不成功 + ''' + # 检查用户配置文件,获取mixsongmid + mixid = user_info['lite_sign_in']['mixsongmid']['value'] + if (mixid == 'auto'): + mixid = await randomMixSongMid() + + # 声明变量 + headers = { + 'User-Agent': f'Android712-AndroidPhone-{config.read_config("module.kg.client.clientver")}-18-0-NetMusic-wifi', + 'KG-THash': '3e5ec6b', + 'KG-Rec': '1', + 'KG-RC': '1', + "x-router": "youth.kugou.com" + } + body = """{"mixsongid":__id__}""".replace("__id__", str(mixid)) + + # params = "userid={}&token={}&appid=3116&clientver=10518&clienttime={}&mid={}&uuid={}&dfid=-".format(read_config("common.kg.userid"), read_config("common.kg.token"), int(time.time()), read_config("common.kg.mid"), str(binascii.hexlify(random.randbytes(16)), encoding = "utf-8")) + params = { + "userid": user_info['userid'], + "token": user_info['token'], + "appid": 3116, + "clientver": config.read_config('module.kg.client.clientver'), + "clienttime": int(time.time()), + "mid": user_info['mid'], + "uuid": str(binascii.hexlify(random.randbytes(16)), encoding="utf-8"), + "dfid": "-" + } + + params['signature'] = sign( + params, body, config.read_config('module.kg.client.signatureKey')) + + # 发送请求 + req = await Httpx.AsyncRequest(f"https://gateway.kugou.com/v2/report/listen_song?" + + buildRequestParams(params), { + "method": "POST", + "body": body, + "headers": headers + }) + req = req.json() + + if req['status'] == 1: + return + else: + raise FailedException(req['error_msg']) + + +def task_handler(): + # not lite client configure + if (int(config.read_config('module.kg.client.appid')) != 3116): + return + + # no user + if ((not variable.use_cookie_pool) and (not config.read_config('module.kg.user.token'))): + return + + # devide cookiepool + if (variable.use_cookie_pool): + pool = config.read_config('module.cookiepool.kg') + for user in pool: + index = pool.index(user) + if (user.get('lite_sign_in') is None): + user['lite_sign_in'] = { + "desc": "是否启用概念版自动签到,仅在appid=3116时运行", + "enable": False, + "interval": 86400, + "mixsongmid": { + "desc": "mix_songmid的获取方式, 默认auto, 可以改成一个数字手动", + "value": "auto" + } + } + pool[index] = user + config.write_config('module.cookiepool.kg', pool) + logger.info(f'用户池用户(index = {index})配置缺失lite_sign_in字段,已自动写入') + + # refresh + pool = config.read_config('module.cookiepool.kg') + # add signin schedule task + for user in pool: + if (user.get('lite_sign_in').get('enable')): + scheduler.append(f'kugou_lite_sign_in_{user["userid"]}', do_account_signin, user['lite_sign_in']['interval'], {'user_info': user}) + else: + user_info = config.read_config('module.kg.user') + if (user_info.get('lite_sign_in') is None): + user_info['lite_sign_in'] = { + "desc": "是否启用概念版自动签到,仅在appid=3116时运行", + "enable": False, + "interval": 86400, + "mixsongmid": { + "desc": "mix_songmid的获取方式, 默认auto, 可以改成一个数字手动", + "value": "auto" + } + } + config.write_config('module.kg.user', user_info) + logger.info('用户配置缺失lite_sign_in字段,已自动写入') + + if (user_info.get('lite_sign_in').get('enable')): + scheduler.append(f'kugou_lite_sign_in', do_account_signin, user_info['lite_sign_in']['interval'], {'user_info': user_info}) + +task_handler() diff --git a/modules/kg/player.py b/modules/kg/player.py index f4d8dee..d6ebaf2 100644 --- a/modules/kg/player.py +++ b/modules/kg/player.py @@ -46,7 +46,7 @@ async def url(songId, quality): 'album_audio_id': albumaudioid, 'behavior': 'play', 'clienttime': int(time.time()), - 'pid': 2, + 'pid': tools.pid, 'key': getKey(thash, user_info), 'dfid': '-', 'pidversion': 3001 diff --git a/modules/kg/utils.py b/modules/kg/utils.py index c61149b..4cb4cac 100644 --- a/modules/kg/utils.py +++ b/modules/kg/utils.py @@ -23,6 +23,7 @@ tools = createObject({ "version": config.read_config("module.kg.tracker.version"), "extra_params": config.read_config("module.kg.tracker.extra_params"), "appid": config.read_config("module.kg.client.appid"), + "pid": config.read_config("module.kg.client.pid"), 'qualityHashMap': { '128k': 'hash_128', '320k': 'hash_320', @@ -43,7 +44,7 @@ def buildSignatureParams(dictionary, body = ""): joined_str = ''.join([f'{k}={v}' for k, v in dictionary.items()]) return joined_str + body -def buildRequestParams(dictionary): +def buildRequestParams(dictionary: dict): joined_str = '&'.join([f'{k}={v}' for k, v in dictionary.items()]) return joined_str