152 lines
4.9 KiB
Python
152 lines
4.9 KiB
Python
"""网易云音乐二维码登录模块 (已修改为API)
|
||
|
||
提供网易云音乐二维码登录功能,被 main.py 调用。
|
||
- 二维码生成 (key 和 base64)
|
||
- 登录状态检查 (返回 code 和 cookie)
|
||
"""
|
||
|
||
import json
|
||
import logging
|
||
import base64
|
||
import io
|
||
import re # [!! 已添加 !!]
|
||
from random import randrange # [!! 关键修复 !!] 导入 randrange
|
||
from typing import Optional, Dict, Any, Tuple
|
||
|
||
try:
|
||
# qrcode 库在 music_api.py 中被导入,这里我们也需要它
|
||
import qrcode
|
||
except ImportError:
|
||
print("错误:缺少 'qrcode' 库。请运行 `pip install qrcode`")
|
||
qrcode = None
|
||
|
||
try:
|
||
# 我们需要从 music_api 借用很多底层工具
|
||
from music_api import (
|
||
QRLoginManager, APIException, HTTPClient,
|
||
CryptoUtils, APIConstants
|
||
)
|
||
from cookie_manager import CookieManager, CookieException
|
||
except ImportError as e:
|
||
print(f"导入模块失败: {e}")
|
||
print("请确保 music_api.py 和 cookie_manager.py 文件存在且可用")
|
||
|
||
|
||
def api_generate_qr_key() -> Dict[str, Any]:
|
||
"""
|
||
[新API] 生成二维码Key和Base64图像
|
||
|
||
Returns:
|
||
{'success': bool, 'qr_key': str, 'qr_img_b64': str, 'message': str}
|
||
"""
|
||
if not qrcode:
|
||
return {'success': False, 'message': "qrcode 库未安装"}
|
||
|
||
try:
|
||
qr_manager = QRLoginManager()
|
||
unikey = qr_manager.generate_qr_key()
|
||
|
||
if not unikey:
|
||
raise APIException("生成二维码key失败")
|
||
|
||
# 生成二维码数据
|
||
qr_data = f'https://music.163.com/login?codekey={unikey}'
|
||
|
||
# 在内存中生成二维码图片
|
||
qr = qrcode.QRCode()
|
||
qr.add_data(qr_data)
|
||
qr.make(fit=True)
|
||
img = qr.make_image(fill_color="black", back_color="white")
|
||
|
||
# 将图片保存到 BytesIO
|
||
buffered = io.BytesIO()
|
||
img.save(buffered, format="PNG")
|
||
|
||
# 转换为 Base64 字符串
|
||
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
||
qr_img_b64 = f"data:image/png;base64,{img_str}"
|
||
|
||
return {
|
||
'success': True,
|
||
'qr_key': unikey,
|
||
'qr_img_b64': qr_img_b64
|
||
}
|
||
|
||
except Exception as e:
|
||
logging.error(f"生成二维码失败: {e}")
|
||
return {'success': False, 'message': str(e)}
|
||
|
||
|
||
def api_check_qr_status(unikey: str) -> Dict[str, Any]:
|
||
"""
|
||
[新API] 检查二维码登录状态
|
||
|
||
此函数复制并*修复*了 music_api.py 中 check_qr_login 的逻辑,
|
||
以便返回完整的 Cookie 字符串,而不只是 MUSIC_U。
|
||
|
||
Args:
|
||
unikey: 二维码key
|
||
|
||
Returns:
|
||
{'code': int, 'cookie': Optional[str], 'message': str}
|
||
code: 800=过期, 801=等待, 802=已扫码, 803=成功
|
||
"""
|
||
try:
|
||
http_client = HTTPClient()
|
||
crypto_utils = CryptoUtils()
|
||
config = APIConstants.DEFAULT_CONFIG.copy()
|
||
|
||
# [!! 关键修复 !!] 现在 randrange 已经被导入,这行代码可以正常工作
|
||
config["requestId"] = str(randrange(20000000, 30000000))
|
||
|
||
payload = {
|
||
'key': unikey,
|
||
'type': 1,
|
||
'header': json.dumps(config)
|
||
}
|
||
|
||
params = crypto_utils.encrypt_params(APIConstants.QR_LOGIN_API, payload)
|
||
|
||
# 我们需要完整的 response 对象来获取 headers
|
||
response = http_client.post_request_full(
|
||
APIConstants.QR_LOGIN_API, params, {}
|
||
)
|
||
|
||
result = json.loads(response.text)
|
||
code = result.get('code', -1)
|
||
|
||
cookie_string = None
|
||
|
||
if code == 803:
|
||
# 登录成功,提取cookie
|
||
raw_cookies = response.headers.get('Set-Cookie', '')
|
||
|
||
# 使用 re.sub 来处理 'path=/,' 这种导致错误分割的情况
|
||
raw_cookies = re.sub(r"path=/,", "path=/", raw_cookies)
|
||
cookie_list = [c.strip() for c in raw_cookies.split(',') if c.strip()]
|
||
|
||
final_cookie_parts = []
|
||
for item in cookie_list:
|
||
if not item:
|
||
continue
|
||
part = item.split(';')[0]
|
||
if part.strip():
|
||
final_cookie_parts.append(part.strip())
|
||
|
||
cookie_string = '; '.join(final_cookie_parts)
|
||
|
||
if "MUSIC_U" not in cookie_string:
|
||
return {'code': -1, 'cookie': None, 'message': '登录成功但未获取到 MUSIC_U'}
|
||
|
||
return {
|
||
'code': code,
|
||
'cookie': cookie_string,
|
||
'message': result.get('message', '')
|
||
}
|
||
|
||
except Exception as e:
|
||
# 在 `api_check_qr_status` 捕获异常时,记录详细的 traceback
|
||
logging.error(f"检查登录状态响应失败: {e}", exc_info=True)
|
||
return {'code': -1, 'cookie': None, 'message': f"检查失败: {e}"}
|
||
|
||
# --- 删除了所有旧的交互式代码 --- |