- LookingWebMain: 强制 IPv4 避免 IPv6 不可达挂起;requests 加超时防卡死 - get_seed_from_element_detail: 修复限流页崩溃与 favicon 误作来源 URL,改用 canonical/传入 URL - QbittorrentDownloader: 修复 main() 中模块/类误用导致的 download_by_seed AttributeError;请求加超时 - MySqlService: 修复数据库不可达时 query/execute/close 的 None 崩溃,close 后置空 connection - 新增 cf_session: Cloudflare 人机认证 cookie 管理 + 限流退避重试 + 自动弹浏览器刷新验证 - 新增 refresh_cf_cookies.py: 弹出浏览器手动验证并保存 cf_clearance - tests/: 真实网络/数据库测试(get_page、seed_is_exist、cf_session)+ run_tests.py runner - 清理无用脚本(final_test2/verify_replace/REPLACEMENT_INFO),.idea 与 cookie 文件入 .gitignore 注: test_qbittorrent_downloader.py 11 个用例为既有失败(mock 目标 session 为实例属性,与实现错配),未在本次范围
157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
import requests
|
|
from loguru import logger
|
|
from dataService.dto.SeedInfo import SeedInfo
|
|
|
|
# 请求超时: (连接超时, 读取超时),防止 qBittorrent 不可达时程序卡死
|
|
REQUEST_TIMEOUT = (5, 15)
|
|
|
|
class QbittorrentDownloader:
|
|
"""使用 qBittorrent Web API 进行下载"""
|
|
|
|
def __init__(self, host="192.168.123.199", api_port=8085, username="admin", password="GUOxy.5157"):
|
|
"""
|
|
初始化 qBittorrent 下载器
|
|
|
|
Args:
|
|
host: qBittorrent 主机地址
|
|
api_port: qBittorrent Web API 端口
|
|
username: 用户名(如果启用用户认证)
|
|
password: 密码(如果启用用户认证)
|
|
"""
|
|
self.host = host
|
|
self.api_port = api_port
|
|
self.username = username
|
|
self.password = password
|
|
self.api_base_url = f"http://{host}:{api_port}/api/v2"
|
|
|
|
# 创建 session 并进行登录
|
|
self.session = requests.Session()
|
|
if username and password:
|
|
self._login()
|
|
|
|
def _login(self):
|
|
"""登录 qBittorrent 并获取 session cookie"""
|
|
login_url = f"{self.api_base_url}/auth/login"
|
|
try:
|
|
response = self.session.post(login_url, data={"username": self.username, "password": self.password}, timeout=REQUEST_TIMEOUT)
|
|
if response.status_code == 200:
|
|
logger.debug("qBittorrent 登录成功")
|
|
else:
|
|
logger.warning(f"qBittorrent 登录失败,状态码: {response.status_code}")
|
|
except Exception as e:
|
|
logger.error(f"qBittorrent 登录异常: {str(e)}")
|
|
|
|
def download_by_magnet(self, magnet_url, save_path="", category="", tags=""):
|
|
"""
|
|
通过 magnet 链接添加下载任务
|
|
|
|
Args:
|
|
magnet_url: magnet 链接
|
|
save_path: 保存路径
|
|
category: 下载分类
|
|
tags: 下载标签
|
|
|
|
Returns:
|
|
str: "1"表示成功,"0"表示失败,"-1"表示请求异常
|
|
"""
|
|
add_url = f"{self.api_base_url}/torrents/add"
|
|
|
|
# 构造添加下载的参数
|
|
form_data = {
|
|
'urls': magnet_url
|
|
}
|
|
|
|
# 如果有保存路径、分类或标签,添加到表单数据
|
|
if save_path:
|
|
form_data['savepath'] = save_path
|
|
|
|
if category:
|
|
form_data['category'] = category
|
|
|
|
if tags:
|
|
form_data['tags'] = tags
|
|
|
|
logger.debug(f"开始向 qBittorrent 添加下载任务,magnet 链接: {magnet_url}")
|
|
|
|
try:
|
|
response = self.session.post(add_url, data=form_data, timeout=REQUEST_TIMEOUT)
|
|
|
|
if response.status_code == 200:
|
|
logger.info(f"请求成功,magnet 链接 {magnet_url} 已添加至下载队列")
|
|
return "1"
|
|
else:
|
|
logger.warning(
|
|
f"请求返回非200状态码,magnet 链接 {magnet_url} 添加失败。状态码: {response.status_code}, 响应: {response.text}"
|
|
)
|
|
return "-1" # 请求成功,但失败了
|
|
except Exception as e:
|
|
logger.error(
|
|
f"请求过程中出现异常,magnet 链接 {magnet_url} 添加失败。错误信息: {str(e)}"
|
|
)
|
|
return "0" # 请求过程中发生异常
|
|
|
|
def download_by_seed(self, seed_info: SeedInfo, save_path="", category="", tags=""):
|
|
"""
|
|
通过种子信息添加下载任务(主要处理 magnet 链接)
|
|
|
|
Args:
|
|
seed_info: 种子信息对象
|
|
save_path: 保存路径
|
|
category: 下载分类
|
|
tags: 下载标签
|
|
|
|
Returns:
|
|
str: "1"表示成功,"0"表示失败,"-1"表示请求异常
|
|
"""
|
|
# seedInfo 中的 seedUrl 应该是 magnet 链接
|
|
if not seed_info.seedUrl:
|
|
logger.error("种子信息中缺少种子链接 (seedUrl)")
|
|
return "0"
|
|
|
|
return self.download_by_magnet(
|
|
seed_info.seedUrl,
|
|
save_path=save_path,
|
|
category=category,
|
|
tags=tags
|
|
)
|
|
|
|
def test_connection(self):
|
|
"""
|
|
测试与 qBittorrent 的连接
|
|
|
|
Returns:
|
|
bool: 连接成功返回 True,否则返回 False
|
|
"""
|
|
try:
|
|
version_url = f"{self.api_base_url}/app/version"
|
|
response = self.session.get(version_url, timeout=REQUEST_TIMEOUT)
|
|
|
|
if response.status_code == 200:
|
|
logger.info(f"qBittorrent 连接成功,版本: {response.text}")
|
|
return True
|
|
else:
|
|
logger.error(f"qBittorrent 连接失败,状态码: {response.status_code}")
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"qBittorrent 连接测试异常: {str(e)}")
|
|
return False
|
|
|
|
def get_downloads(self):
|
|
"""
|
|
获取当前下载列表
|
|
|
|
Returns:
|
|
list: 下载任务列表
|
|
"""
|
|
try:
|
|
torrents_url = f"{self.api_base_url}/torrents/info"
|
|
response = self.session.get(torrents_url, timeout=REQUEST_TIMEOUT)
|
|
|
|
if response.status_code == 200:
|
|
return response.json()
|
|
else:
|
|
logger.error(f"获取下载列表失败,状态码: {response.status_code}")
|
|
return []
|
|
except Exception as e:
|
|
logger.error(f"获取下载列表异常: {str(e)}")
|
|
return [] |