mirror of
https://github.com/Suxiaoqinx/Netease_url.git
synced 2025-09-14 03:26:46 +08:00
365 lines
13 KiB
Python
365 lines
13 KiB
Python
"""网易云音乐二维码登录模块
|
||
|
||
提供网易云音乐二维码登录功能,包括:
|
||
- 二维码生成和显示
|
||
- 登录状态检查
|
||
- Cookie获取和保存
|
||
- 用户友好的交互界面
|
||
"""
|
||
|
||
import sys
|
||
import time
|
||
import logging
|
||
from typing import Optional, Dict, Any, Tuple
|
||
from pathlib import Path
|
||
|
||
try:
|
||
from music_api import QRLoginManager, APIException
|
||
from cookie_manager import CookieManager, CookieException
|
||
except ImportError as e:
|
||
print(f"导入模块失败: {e}")
|
||
print("请确保 music_api.py 和 cookie_manager.py 文件存在且可用")
|
||
sys.exit(1)
|
||
|
||
|
||
class QRLoginClient:
|
||
"""二维码登录客户端"""
|
||
|
||
def __init__(self, cookie_file: str = "cookie.txt"):
|
||
"""
|
||
初始化二维码登录客户端
|
||
|
||
Args:
|
||
cookie_file: Cookie保存文件路径
|
||
"""
|
||
self.cookie_manager = CookieManager(cookie_file)
|
||
self.qr_manager = QRLoginManager()
|
||
self.logger = logging.getLogger(__name__)
|
||
|
||
# 配置日志
|
||
if not self.logger.handlers:
|
||
handler = logging.StreamHandler()
|
||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||
handler.setFormatter(formatter)
|
||
self.logger.addHandler(handler)
|
||
self.logger.setLevel(logging.INFO)
|
||
|
||
def check_existing_login(self) -> bool:
|
||
"""检查是否已有有效登录
|
||
|
||
Returns:
|
||
是否已登录
|
||
"""
|
||
try:
|
||
if self.cookie_manager.is_cookie_valid():
|
||
self.logger.info("检测到有效的登录Cookie")
|
||
return True
|
||
else:
|
||
self.logger.info("未检测到有效的登录Cookie")
|
||
return False
|
||
except Exception as e:
|
||
self.logger.error(f"检查登录状态失败: {e}")
|
||
return False
|
||
|
||
def interactive_login(self) -> Tuple[bool, Optional[str]]:
|
||
"""交互式二维码登录
|
||
|
||
Returns:
|
||
(登录是否成功, 错误信息)
|
||
"""
|
||
try:
|
||
print("\n=== 网易云音乐二维码登录 ===")
|
||
|
||
# 检查现有登录状态
|
||
if self.check_existing_login():
|
||
choice = input("检测到已有有效登录,是否重新登录?(y/N): ").strip().lower()
|
||
if choice not in ['y', 'yes', '是']:
|
||
print("使用现有登录状态")
|
||
return True, None
|
||
|
||
print("\n开始二维码登录流程...")
|
||
|
||
# 生成二维码
|
||
print("正在生成二维码...")
|
||
qr_result = self.qr_manager.create_qr_login()
|
||
|
||
if not qr_result['success']:
|
||
error_msg = f"生成二维码失败: {qr_result.get('message', '未知错误')}"
|
||
self.logger.error(error_msg)
|
||
return False, error_msg
|
||
|
||
qr_key = qr_result['qr_key']
|
||
print(f"\n二维码已生成!")
|
||
print(f"请使用网易云音乐手机APP扫描二维码进行登录")
|
||
print(f"二维码有效期: 3分钟")
|
||
print("\n等待扫码中...")
|
||
|
||
# 轮询检查登录状态
|
||
max_attempts = 60 # 最多等待5分钟
|
||
attempt = 0
|
||
|
||
while attempt < max_attempts:
|
||
try:
|
||
# 检查登录状态
|
||
status_result = self.qr_manager.check_qr_login(qr_key)
|
||
|
||
if status_result['success']:
|
||
if status_result['status'] == 'success':
|
||
# 登录成功
|
||
cookie = status_result.get('cookie', '')
|
||
if cookie:
|
||
# 保存Cookie
|
||
success = self.save_cookie(cookie)
|
||
if success:
|
||
print("\n✅ 登录成功!Cookie已保存")
|
||
return True, None
|
||
else:
|
||
error_msg = "登录成功但Cookie保存失败"
|
||
self.logger.error(error_msg)
|
||
return False, error_msg
|
||
else:
|
||
error_msg = "登录成功但未获取到Cookie"
|
||
self.logger.error(error_msg)
|
||
return False, error_msg
|
||
|
||
elif status_result['status'] == 'waiting':
|
||
# 等待扫码
|
||
if attempt % 10 == 0: # 每10次显示一次提示
|
||
print(f"等待扫码中... ({attempt + 1}/{max_attempts})")
|
||
|
||
elif status_result['status'] == 'scanned':
|
||
# 已扫码,等待确认
|
||
print("二维码已扫描,请在手机上确认登录")
|
||
|
||
elif status_result['status'] == 'expired':
|
||
# 二维码过期
|
||
error_msg = "二维码已过期,请重新尝试"
|
||
print(f"\n❌ {error_msg}")
|
||
return False, error_msg
|
||
|
||
elif status_result['status'] == 'error':
|
||
# 登录错误
|
||
error_msg = f"登录失败: {status_result.get('message', '未知错误')}"
|
||
print(f"\n❌ {error_msg}")
|
||
return False, error_msg
|
||
|
||
else:
|
||
self.logger.warning(f"检查登录状态失败: {status_result.get('message', '未知错误')}")
|
||
|
||
# 等待5秒后重试
|
||
time.sleep(5)
|
||
attempt += 1
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n用户取消登录")
|
||
return False, "用户取消登录"
|
||
except Exception as e:
|
||
self.logger.error(f"检查登录状态时发生错误: {e}")
|
||
time.sleep(5)
|
||
attempt += 1
|
||
|
||
# 超时
|
||
error_msg = "登录超时,请重新尝试"
|
||
print(f"\n❌ {error_msg}")
|
||
return False, error_msg
|
||
|
||
except APIException as e:
|
||
error_msg = f"API调用失败: {e}"
|
||
self.logger.error(error_msg)
|
||
return False, error_msg
|
||
except Exception as e:
|
||
error_msg = f"登录过程中发生未知错误: {e}"
|
||
self.logger.error(error_msg)
|
||
return False, error_msg
|
||
|
||
def save_cookie(self, cookie: str) -> bool:
|
||
"""保存Cookie到文件
|
||
|
||
Args:
|
||
cookie: Cookie字符串
|
||
|
||
Returns:
|
||
是否保存成功
|
||
"""
|
||
try:
|
||
# 备份现有Cookie(如果存在)
|
||
if self.cookie_manager.cookie_file.exists():
|
||
try:
|
||
backup_path = self.cookie_manager.backup_cookie()
|
||
self.logger.info(f"已备份现有Cookie到: {backup_path}")
|
||
except Exception as e:
|
||
self.logger.warning(f"备份Cookie失败: {e}")
|
||
|
||
# 保存新Cookie
|
||
success = self.cookie_manager.write_cookie(cookie)
|
||
|
||
if success:
|
||
# 验证保存的Cookie
|
||
if self.cookie_manager.is_cookie_valid():
|
||
self.logger.info("Cookie保存并验证成功")
|
||
return True
|
||
else:
|
||
self.logger.warning("Cookie保存成功但验证失败")
|
||
return False
|
||
else:
|
||
self.logger.error("Cookie保存失败")
|
||
return False
|
||
|
||
except CookieException as e:
|
||
self.logger.error(f"Cookie操作失败: {e}")
|
||
return False
|
||
except Exception as e:
|
||
self.logger.error(f"保存Cookie时发生错误: {e}")
|
||
return False
|
||
|
||
def show_login_info(self) -> None:
|
||
"""显示登录信息"""
|
||
try:
|
||
info = self.cookie_manager.get_cookie_info()
|
||
|
||
print("\n=== 登录状态信息 ===")
|
||
print(f"Cookie文件: {info['file_path']}")
|
||
print(f"文件存在: {'是' if info['file_exists'] else '否'}")
|
||
print(f"Cookie数量: {info['cookie_count']}")
|
||
print(f"登录状态: {'有效' if info['is_valid'] else '无效'}")
|
||
|
||
if info.get('last_modified'):
|
||
print(f"最后更新: {info['last_modified']}")
|
||
|
||
if info['is_valid']:
|
||
present_cookies = info.get('important_cookies_present', [])
|
||
print(f"重要Cookie: {', '.join(present_cookies)}")
|
||
else:
|
||
missing_cookies = info.get('missing_important_cookies', [])
|
||
if missing_cookies:
|
||
print(f"缺少Cookie: {', '.join(missing_cookies)}")
|
||
|
||
except Exception as e:
|
||
print(f"获取登录信息失败: {e}")
|
||
|
||
def logout(self) -> bool:
|
||
"""登出(清除Cookie)
|
||
|
||
Returns:
|
||
是否登出成功
|
||
"""
|
||
try:
|
||
# 备份Cookie
|
||
if self.cookie_manager.cookie_file.exists():
|
||
try:
|
||
backup_path = self.cookie_manager.backup_cookie("logout")
|
||
print(f"Cookie已备份到: {backup_path}")
|
||
except Exception as e:
|
||
self.logger.warning(f"备份Cookie失败: {e}")
|
||
|
||
# 清除Cookie
|
||
success = self.cookie_manager.clear_cookie()
|
||
|
||
if success:
|
||
print("已成功登出")
|
||
return True
|
||
else:
|
||
print("登出失败")
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"登出时发生错误: {e}")
|
||
return False
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
# 配置日志
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||
)
|
||
|
||
client = QRLoginClient()
|
||
|
||
# 解析命令行参数
|
||
if len(sys.argv) > 1:
|
||
command = sys.argv[1].lower()
|
||
|
||
if command == 'login':
|
||
# 执行登录
|
||
success, error = client.interactive_login()
|
||
if success:
|
||
print("\n登录完成!")
|
||
client.show_login_info()
|
||
sys.exit(0)
|
||
else:
|
||
print(f"\n登录失败: {error}")
|
||
sys.exit(1)
|
||
|
||
elif command == 'status' or command == 'info':
|
||
# 显示登录状态
|
||
client.show_login_info()
|
||
sys.exit(0)
|
||
|
||
elif command == 'logout':
|
||
# 登出
|
||
success = client.logout()
|
||
sys.exit(0 if success else 1)
|
||
|
||
elif command == 'help' or command == '-h' or command == '--help':
|
||
# 显示帮助
|
||
print("网易云音乐二维码登录工具")
|
||
print("\n用法:")
|
||
print(" python qr_login.py [命令]")
|
||
print("\n命令:")
|
||
print(" login - 执行二维码登录")
|
||
print(" status - 显示登录状态")
|
||
print(" logout - 登出(清除Cookie)")
|
||
print(" help - 显示此帮助信息")
|
||
print("\n如果不提供命令,将进入交互模式")
|
||
sys.exit(0)
|
||
|
||
else:
|
||
print(f"未知命令: {command}")
|
||
print("使用 'python qr_login.py help' 查看帮助")
|
||
sys.exit(1)
|
||
|
||
# 交互模式
|
||
try:
|
||
while True:
|
||
print("\n=== 网易云音乐登录工具 ===")
|
||
print("1. 二维码登录")
|
||
print("2. 查看登录状态")
|
||
print("3. 登出")
|
||
print("4. 退出")
|
||
|
||
choice = input("\n请选择操作 (1-4): ").strip()
|
||
|
||
if choice == '1':
|
||
success, error = client.interactive_login()
|
||
if success:
|
||
print("\n登录成功!")
|
||
client.show_login_info()
|
||
else:
|
||
print(f"\n登录失败: {error}")
|
||
|
||
elif choice == '2':
|
||
client.show_login_info()
|
||
|
||
elif choice == '3':
|
||
client.logout()
|
||
|
||
elif choice == '4':
|
||
print("再见!")
|
||
break
|
||
|
||
else:
|
||
print("无效选择,请重试")
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n\n程序已退出")
|
||
sys.exit(0)
|
||
except Exception as e:
|
||
print(f"\n程序运行出错: {e}")
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|