v0.2.7: 修复Redis连接 + 启动管理后台
- 修复Redis认证 (配置密码) - 启动Python管理后台 (端口9531, 15个功能开关) - 统一版本号 0.2.7 - 更新docker-compose.yml (镜像版本/Redis URL/Admin服务)
This commit is contained in:
198
cloudsearch_transfer/adapter/xunlei/cleanup.py
Normal file
198
cloudsearch_transfer/adapter/xunlei/cleanup.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
CloudSearch Transfer — 迅雷网盘清理模块 v1.0.0
|
||||
|
||||
提供文件删除和广告过滤功能。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import requests
|
||||
|
||||
from .credential import XunleiCredentialManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ─── 迅雷 API ─────────────────────────────────────────────────────────
|
||||
XUNLEI_PAN_API = "https://api-pan.xunlei.com"
|
||||
|
||||
|
||||
class XunleiCleanup:
|
||||
"""迅雷网盘文件清理器。
|
||||
|
||||
提供批量删除文件和广告文件过滤功能。
|
||||
|
||||
Attributes:
|
||||
credential: 迅雷凭证管理器。
|
||||
session: 复用的 requests.Session。
|
||||
timeout: HTTP 请求超时秒数。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
credential: XunleiCredentialManager,
|
||||
timeout: int = 30,
|
||||
) -> None:
|
||||
"""初始化清理器。
|
||||
|
||||
Args:
|
||||
credential: 有效的迅雷凭证管理器。
|
||||
timeout: HTTP 请求超时秒数。
|
||||
"""
|
||||
self.credential: XunleiCredentialManager = credential
|
||||
self.timeout: int = timeout
|
||||
self.session: requests.Session = requests.Session()
|
||||
|
||||
def delete_files(self, file_ids: List[str]) -> bool:
|
||||
"""批量删除文件。
|
||||
|
||||
POST /drive/v1/files:batchDelete
|
||||
Body: {
|
||||
"ids": ["<fid1>", "<fid2>", ...],
|
||||
"space": ""
|
||||
}
|
||||
|
||||
Args:
|
||||
file_ids: 要删除的文件 ID 列表。
|
||||
|
||||
Returns:
|
||||
True 表示删除请求已提交成功,False 表示失败。
|
||||
|
||||
Raises:
|
||||
RuntimeError: HTTP 请求错误。
|
||||
"""
|
||||
if not file_ids:
|
||||
logger.warning("[XunleiCleanup] delete_files called with empty list")
|
||||
return True
|
||||
|
||||
url: str = f"{XUNLEI_PAN_API}/drive/v1/files:batchDelete"
|
||||
body: Dict[str, Any] = {
|
||||
"ids": file_ids,
|
||||
"space": "",
|
||||
}
|
||||
headers = self.credential.get_headers()
|
||||
headers.setdefault("Content-Type", "application/json")
|
||||
|
||||
logger.info("[XunleiCleanup] Deleting %d files: %s", len(file_ids), file_ids)
|
||||
|
||||
try:
|
||||
resp = self.session.post(
|
||||
url, json=body, headers=headers, timeout=self.timeout
|
||||
)
|
||||
resp.raise_for_status()
|
||||
except requests.RequestException as exc:
|
||||
raise RuntimeError(f"删除文件失败: {exc}") from exc
|
||||
|
||||
data: Dict[str, Any] = resp.json()
|
||||
errcode = data.get("errcode", data.get("error_code", 0))
|
||||
if errcode != 0:
|
||||
logger.error(
|
||||
"[XunleiCleanup] Delete returned error: errcode=%s, message=%s",
|
||||
errcode,
|
||||
data.get("message", data.get("error", "")),
|
||||
)
|
||||
return False
|
||||
|
||||
logger.info("[XunleiCleanup] Delete succeeded for %d files", len(file_ids))
|
||||
return True
|
||||
|
||||
def delete_files_permanent(self, file_ids: List[str]) -> bool:
|
||||
"""彻底删除文件。
|
||||
|
||||
迅雷的 batchDelete 默认为彻底删除(与回收站不同),
|
||||
此方法与 delete_files 行为一致。
|
||||
|
||||
Args:
|
||||
file_ids: 要彻底删除的文件 ID 列表。
|
||||
|
||||
Returns:
|
||||
True 表示删除请求已提交成功。
|
||||
"""
|
||||
return self.delete_files(file_ids)
|
||||
|
||||
@staticmethod
|
||||
def filter_ads(
|
||||
files: List[Dict[str, Any]],
|
||||
banned_keywords: List[str],
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""按关键词过滤文件列表中的广告文件。
|
||||
|
||||
遍历文件列表,剔除文件名中包含任一 banned_keywords 的文件。
|
||||
匹配方式:不区分大小写的子串匹配。
|
||||
|
||||
Args:
|
||||
files: 文件信息字典列表,每个字典需包含 "name" 或 "file_name" 字段。
|
||||
banned_keywords: 被禁关键词列表(匹配不区分大小写)。
|
||||
|
||||
Returns:
|
||||
过滤后的文件信息列表。
|
||||
"""
|
||||
if not banned_keywords:
|
||||
return files
|
||||
|
||||
filtered: List[Dict[str, Any]] = []
|
||||
removed_count: int = 0
|
||||
|
||||
for f in files:
|
||||
name: str = f.get("name", f.get("file_name", ""))
|
||||
name_lower: str = str(name).lower()
|
||||
|
||||
if any(keyword.lower() in name_lower for keyword in banned_keywords):
|
||||
logger.info("[XunleiCleanup] Filtered ad file: '%s'", name)
|
||||
removed_count += 1
|
||||
continue
|
||||
|
||||
filtered.append(f)
|
||||
|
||||
if removed_count > 0:
|
||||
logger.info(
|
||||
"[XunleiCleanup] Ad filter removed %d/%d files",
|
||||
removed_count,
|
||||
len(files),
|
||||
)
|
||||
return filtered
|
||||
|
||||
@staticmethod
|
||||
def filter_ad_ids(
|
||||
file_ids: List[str],
|
||||
file_names: List[str],
|
||||
banned_keywords: List[str],
|
||||
) -> List[str]:
|
||||
"""按关键词过滤文件 ID 列表。
|
||||
|
||||
根据 file_names 判断是否为广告,返回对应的 file_ids。
|
||||
|
||||
Args:
|
||||
file_ids: 文件 ID 列表。
|
||||
file_names: 与 file_ids 一一对应的文件名列表。
|
||||
banned_keywords: 被禁关键词列表。
|
||||
|
||||
Returns:
|
||||
过滤后的 file_ids 列表。
|
||||
"""
|
||||
if not banned_keywords or len(file_ids) != len(file_names):
|
||||
return file_ids
|
||||
|
||||
filtered_ids: List[str] = []
|
||||
for fid, name in zip(file_ids, file_names):
|
||||
name_lower: str = str(name).lower()
|
||||
if any(kw.lower() in name_lower for kw in banned_keywords):
|
||||
logger.info(
|
||||
"[XunleiCleanup] Filtered ad file: '%s' (id=%s)", name, fid
|
||||
)
|
||||
continue
|
||||
filtered_ids.append(fid)
|
||||
|
||||
return filtered_ids
|
||||
|
||||
def close(self) -> None:
|
||||
"""关闭 HTTP 会话。"""
|
||||
self.session.close()
|
||||
|
||||
def __enter__(self) -> "XunleiCleanup":
|
||||
return self
|
||||
|
||||
def __exit__(self, *args: Any) -> None:
|
||||
self.close()
|
||||
Reference in New Issue
Block a user