Skip to content

Commit 92e262f

Browse files
authored
Merge branch 'jxxghp:v2' into v2
2 parents c46880b + cdb178c commit 92e262f

File tree

23 files changed

+3115
-34
lines changed

23 files changed

+3115
-34
lines changed

app/agent/tools/impl/search_torrents.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import re
55
from typing import List, Optional, Type
66

7-
from pydantic import BaseModel, Field
7+
from pydantic import BaseModel, Field, field_validator
88

99
from app.agent.tools.base import MoviePilotTool
1010
from app.chain.search import SearchChain
@@ -28,6 +28,28 @@ class SearchTorrentsInput(BaseModel):
2828
filter_pattern: Optional[str] = Field(None,
2929
description="Regular expression pattern to filter torrent titles by resolution, quality, or other keywords (e.g., '4K|2160p|UHD' for 4K content, '1080p|BluRay' for 1080p BluRay)")
3030

31+
@field_validator("sites", mode="before")
32+
@classmethod
33+
def normalize_sites(cls, value):
34+
"""兼容字符串格式的站点列表(如 "[28]"、"28,30")"""
35+
if value is None:
36+
return value
37+
if isinstance(value, str):
38+
value = value.strip()
39+
if not value:
40+
return None
41+
try:
42+
parsed = json.loads(value)
43+
if isinstance(parsed, list):
44+
return parsed
45+
except Exception:
46+
pass
47+
if "," in value:
48+
return [v.strip() for v in value.split(",") if v.strip()]
49+
if value.isdigit():
50+
return [value]
51+
return value
52+
3153

3254
class SearchTorrentsTool(MoviePilotTool):
3355
name: str = "search_torrents"

app/api/endpoints/dashboard.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,17 @@ def statistic(name: Optional[str] = None, _: schemas.TokenPayload = Depends(veri
2626
if media_statistics:
2727
# 汇总各媒体库统计信息
2828
ret_statistic = schemas.Statistic()
29+
has_episode_count = False
2930
for media_statistic in media_statistics:
30-
ret_statistic.movie_count += media_statistic.movie_count
31-
ret_statistic.tv_count += media_statistic.tv_count
32-
ret_statistic.episode_count += media_statistic.episode_count
33-
ret_statistic.user_count += media_statistic.user_count
31+
ret_statistic.movie_count += media_statistic.movie_count or 0
32+
ret_statistic.tv_count += media_statistic.tv_count or 0
33+
ret_statistic.user_count += media_statistic.user_count or 0
34+
if media_statistic.episode_count is not None:
35+
ret_statistic.episode_count += media_statistic.episode_count or 0
36+
has_episode_count = True
37+
if not has_episode_count:
38+
# 所有媒体服务都未提供剧集统计时,返回 None 供前端展示“未获取”。
39+
ret_statistic.episode_count = None
3440
return ret_statistic
3541
else:
3642
return schemas.Statistic()

app/api/endpoints/transfer.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,14 @@ def manual_transfer(transer_item: ManualTransferItem,
138138
else:
139139
return schemas.Response(success=False, message=f"缺少参数")
140140

141-
# 类型
142-
mtype = MediaType(transer_item.type_name) if transer_item.type_name else None
141+
# 类型(“自动/auto/none”按未指定处理)
142+
mtype = None
143+
type_name = str(transer_item.type_name).strip() if transer_item.type_name else ""
144+
if type_name and type_name.lower() not in {"自动", "auto", "none"}:
145+
try:
146+
mtype = MediaType(type_name)
147+
except ValueError:
148+
return schemas.Response(success=False, message=f"不支持的媒体类型:{type_name}")
143149
# 自定义格式
144150
epformat = None
145151
if transer_item.episode_offset or transer_item.episode_part \

app/chain/message.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ def process(self, body: Any, form: Any, args: Any) -> None:
112112
channel = info.channel
113113
# 用户ID
114114
userid = info.userid
115-
# 用户名
116-
username = info.username or userid
115+
# 用户名(当渠道未提供公开用户名时,回退为 userid 的字符串,避免后续类型校验异常)
116+
username = str(info.username) if info.username not in (None, "") else str(userid)
117117
if userid is None or userid == '':
118118
logger.debug(f'未识别到用户ID:{body}{form}{args}')
119119
return

app/core/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ class ConfigModel(BaseModel):
414414
RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME: bool = True
415415
# 对OpenList进行快照对比时,是否检查文件夹的修改时间
416416
OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME: bool = True
417+
# 对阿里云盘进行快照对比时,是否检查文件夹的修改时间(默认关闭,因为阿里云盘目录时间不随子文件变更而更新)
418+
ALIPAN_SNAPSHOT_CHECK_FOLDER_MODTIME: bool = False
417419

418420
# ==================== Docker配置 ====================
419421
# Docker Client API地址

app/core/meta/metavideo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def __init__(self, title: str, subtitle: str = None, isfile: bool = False):
7676
self.type = MediaType.TV
7777
return
7878
# 全名为Season xx 及 Sxx 直接返回
79-
season_full_res = re.search(r"^Season\s+(\d{1,3})$|^S(\d{1,3})$", title)
79+
season_full_res = re.search(r"^(?:Season\s+|S)(\d{1,3})$", title, re.IGNORECASE)
8080
if season_full_res:
8181
self.type = MediaType.TV
8282
season = season_full_res.group(1)

app/helper/progress.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33

44
from app.core.cache import TTLCache
55
from app.schemas.types import ProgressKey
6-
from app.utils.singleton import WeakSingleton
76

87

9-
class ProgressHelper(metaclass=WeakSingleton):
8+
class ProgressHelper:
109
"""
1110
处理进度辅助类
1211
"""

app/modules/filemanager/__init__.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,19 @@ def test(self) -> Tuple[bool, str]:
8181
return False, f"{d.name} 的下载目录未设置"
8282
if d.storage == "local" and not Path(download_path).exists():
8383
return False, f"{d.name} 的下载目录 {download_path} 不存在"
84-
# 媒体库目录
84+
# 仅在启用整理时检查媒体库目录
8585
library_path = d.library_path
86-
if not library_path:
87-
return False, f"{d.name} 的媒体库目录未设置"
88-
if d.library_storage == "local" and not Path(library_path).exists():
89-
return False, f"{d.name} 的媒体库目录 {library_path} 不存在"
90-
# 硬链接
91-
if d.transfer_type == "link" \
92-
and d.storage == "local" \
93-
and d.library_storage == "local" \
94-
and not SystemUtils.is_same_disk(Path(download_path), Path(library_path)):
95-
return False, f"{d.name} 的下载目录 {download_path} 与媒体库目录 {library_path} 不在同一磁盘,无法硬链接"
86+
if d.transfer_type:
87+
if not library_path:
88+
return False, f"{d.name} 的媒体库目录未设置"
89+
if d.library_storage == "local" and not Path(library_path).exists():
90+
return False, f"{d.name} 的媒体库目录 {library_path} 不存在"
91+
# 硬链接
92+
if d.transfer_type == "link" \
93+
and d.storage == "local" \
94+
and d.library_storage == "local" \
95+
and not SystemUtils.is_same_disk(Path(download_path), Path(library_path)):
96+
return False, f"{d.name} 的下载目录 {download_path} 与媒体库目录 {library_path} 不在同一磁盘,无法硬链接"
9697
# 存储
9798
storage_oper = self.__get_storage_oper(d.storage)
9899
if storage_oper:

app/modules/filemanager/storages/__init__.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,12 @@ def __snapshot_file(_fileitm: schemas.FileItem, current_depth: int = 0):
261261
for sub_file in sub_files:
262262
__snapshot_file(sub_file, current_depth + 1)
263263
else:
264-
# 记录文件的完整信息用于比对
265-
if getattr(_fileitm, 'modify_time', 0) > last_snapshot_time:
266-
files_info[_fileitm.path] = {
267-
'size': _fileitm.size or 0,
268-
'modify_time': getattr(_fileitm, 'modify_time', 0),
269-
'type': _fileitm.type
270-
}
264+
# 记录文件的完整信息用于比对(始终包含所有文件,由 compare_snapshots 负责检测变化)
265+
files_info[_fileitm.path] = {
266+
'size': _fileitm.size or 0,
267+
'modify_time': getattr(_fileitm, 'modify_time', 0),
268+
'type': _fileitm.type
269+
}
271270

272271
except Exception as e:
273272
logger.debug(f"Snapshot error for {_fileitm.path}: {e}")

app/modules/filemanager/storages/alipan.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
4343
# 基础url
4444
base_url = "https://openapi.alipan.com"
4545

46+
# 阿里云盘目录时间不随子文件变更而更新,默认关闭目录修改时间检查
47+
snapshot_check_folder_modtime = settings.ALIPAN_SNAPSHOT_CHECK_FOLDER_MODTIME
48+
4649
# 文件块大小,默认10MB
4750
chunk_size = 10 * 1024 * 1024
4851

0 commit comments

Comments
 (0)