mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-03-20 03:57:30 +08:00
177 lines
6.6 KiB
Python
177 lines
6.6 KiB
Python
"""种子搜索工具辅助函数"""
|
|
|
|
import re
|
|
from typing import List, Optional
|
|
|
|
from app.core.context import Context
|
|
from app.utils.crypto import HashUtils
|
|
from app.utils.string import StringUtils
|
|
|
|
SEARCH_RESULT_CACHE_FILE = "__search_result__"
|
|
TORRENT_RESULT_LIMIT = 50
|
|
|
|
|
|
def build_torrent_ref(context: Optional[Context]) -> str:
|
|
"""生成用于下载校验的短引用"""
|
|
if not context or not context.torrent_info:
|
|
return ""
|
|
return HashUtils.sha1(context.torrent_info.enclosure or "")[:7]
|
|
|
|
|
|
def sort_season_options(options: List[str]) -> List[str]:
|
|
"""按前端逻辑排序季集选项"""
|
|
if len(options) <= 1:
|
|
return options
|
|
|
|
parsed_options = []
|
|
for index, option in enumerate(options):
|
|
match = re.match(r"^S(\d+)(?:-S(\d+))?\s*(?:E(\d+)(?:-E(\d+))?)?$", option or "")
|
|
if not match:
|
|
parsed_options.append({
|
|
"original": option,
|
|
"season_num": 0,
|
|
"episode_num": 0,
|
|
"max_episode_num": 0,
|
|
"is_whole_season": False,
|
|
"index": index,
|
|
})
|
|
continue
|
|
|
|
episode_num = int(match.group(3)) if match.group(3) else 0
|
|
max_episode_num = int(match.group(4)) if match.group(4) else episode_num
|
|
parsed_options.append({
|
|
"original": option,
|
|
"season_num": int(match.group(1)),
|
|
"episode_num": episode_num,
|
|
"max_episode_num": max_episode_num,
|
|
"is_whole_season": not match.group(3),
|
|
"index": index,
|
|
})
|
|
|
|
whole_seasons = [item for item in parsed_options if item["is_whole_season"]]
|
|
episodes = [item for item in parsed_options if not item["is_whole_season"]]
|
|
|
|
whole_seasons.sort(key=lambda item: (-item["season_num"], item["index"]))
|
|
episodes.sort(
|
|
key=lambda item: (
|
|
-item["season_num"],
|
|
-(item["max_episode_num"] or item["episode_num"]),
|
|
-item["episode_num"],
|
|
item["index"],
|
|
)
|
|
)
|
|
return [item["original"] for item in whole_seasons + episodes]
|
|
|
|
|
|
def append_option(options: List[str], value: Optional[str]) -> None:
|
|
"""按前端逻辑收集去重后的筛选项"""
|
|
if value and value not in options:
|
|
options.append(value)
|
|
|
|
|
|
def build_filter_options(items: List[Context]) -> dict:
|
|
"""从搜索结果中构建筛选项汇总"""
|
|
filter_options = {
|
|
"site": [],
|
|
"season": [],
|
|
"freeState": [],
|
|
"edition": [],
|
|
"resolution": [],
|
|
"videoCode": [],
|
|
"releaseGroup": [],
|
|
}
|
|
|
|
for item in items:
|
|
torrent_info = item.torrent_info
|
|
meta_info = item.meta_info
|
|
append_option(filter_options["site"], getattr(torrent_info, "site_name", None))
|
|
append_option(filter_options["season"], getattr(meta_info, "season_episode", None))
|
|
append_option(filter_options["freeState"], getattr(torrent_info, "volume_factor", None))
|
|
append_option(filter_options["edition"], getattr(meta_info, "edition", None))
|
|
append_option(filter_options["resolution"], getattr(meta_info, "resource_pix", None))
|
|
append_option(filter_options["videoCode"], getattr(meta_info, "video_encode", None))
|
|
append_option(filter_options["releaseGroup"], getattr(meta_info, "resource_team", None))
|
|
|
|
filter_options["season"] = sort_season_options(filter_options["season"])
|
|
return filter_options
|
|
|
|
|
|
def match_filter(filter_values: Optional[List[str]], value: Optional[str]) -> bool:
|
|
"""匹配前端同款多选筛选规则"""
|
|
return not filter_values or bool(value and value in filter_values)
|
|
|
|
|
|
def filter_contexts(items: List[Context],
|
|
site: Optional[List[str]] = None,
|
|
season: Optional[List[str]] = None,
|
|
free_state: Optional[List[str]] = None,
|
|
video_code: Optional[List[str]] = None,
|
|
edition: Optional[List[str]] = None,
|
|
resolution: Optional[List[str]] = None,
|
|
release_group: Optional[List[str]] = None) -> List[Context]:
|
|
"""按前端同款维度筛选结果"""
|
|
filtered_items = []
|
|
for item in items:
|
|
torrent_info = item.torrent_info
|
|
meta_info = item.meta_info
|
|
if (
|
|
match_filter(site, getattr(torrent_info, "site_name", None))
|
|
and match_filter(free_state, getattr(torrent_info, "volume_factor", None))
|
|
and match_filter(season, getattr(meta_info, "season_episode", None))
|
|
and match_filter(release_group, getattr(meta_info, "resource_team", None))
|
|
and match_filter(video_code, getattr(meta_info, "video_encode", None))
|
|
and match_filter(resolution, getattr(meta_info, "resource_pix", None))
|
|
and match_filter(edition, getattr(meta_info, "edition", None))
|
|
):
|
|
filtered_items.append(item)
|
|
return filtered_items
|
|
|
|
|
|
def simplify_search_result(context: Context, index: int) -> dict:
|
|
"""精简单条搜索结果"""
|
|
simplified = {}
|
|
torrent_info = context.torrent_info
|
|
meta_info = context.meta_info
|
|
media_info = context.media_info
|
|
|
|
if torrent_info:
|
|
simplified["torrent_info"] = {
|
|
"title": torrent_info.title,
|
|
"size": StringUtils.format_size(torrent_info.size),
|
|
"seeders": torrent_info.seeders,
|
|
"peers": torrent_info.peers,
|
|
"site_name": torrent_info.site_name,
|
|
"torrent_url": f"{build_torrent_ref(context)}:{index}",
|
|
"page_url": torrent_info.page_url,
|
|
"volume_factor": torrent_info.volume_factor,
|
|
"freedate_diff": torrent_info.freedate_diff,
|
|
"pubdate": torrent_info.pubdate,
|
|
}
|
|
|
|
if media_info:
|
|
simplified["media_info"] = {
|
|
"title": media_info.title,
|
|
"en_title": media_info.en_title,
|
|
"year": media_info.year,
|
|
"type": media_info.type.value if media_info.type else None,
|
|
"season": media_info.season,
|
|
"tmdb_id": media_info.tmdb_id,
|
|
}
|
|
|
|
if meta_info:
|
|
simplified["meta_info"] = {
|
|
"name": meta_info.name,
|
|
"cn_name": meta_info.cn_name,
|
|
"en_name": meta_info.en_name,
|
|
"year": meta_info.year,
|
|
"type": meta_info.type.value if meta_info.type else None,
|
|
"begin_season": meta_info.begin_season,
|
|
"season_episode": meta_info.season_episode,
|
|
"resource_team": meta_info.resource_team,
|
|
"video_encode": meta_info.video_encode,
|
|
"edition": meta_info.edition,
|
|
"resource_pix": meta_info.resource_pix,
|
|
}
|
|
|
|
return simplified
|