"""网易云音乐二维码登录模块 (已修改为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}"} # --- 删除了所有旧的交互式代码 ---