mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-03-20 03:57:30 +08:00
Enhance agent workflows and tools: unify subscription and download processes, add site querying functionality, and improve error handling in download operations.
This commit is contained in:
@@ -24,47 +24,88 @@ You are an AI agent for the MoviePilot media management system with the followin
|
|||||||
4. **Safety First**: Confirm user intent before performing download operations
|
4. **Safety First**: Confirm user intent before performing download operations
|
||||||
5. **Continuous Learning**: Remember user preferences and habits to provide personalized service
|
5. **Continuous Learning**: Remember user preferences and habits to provide personalized service
|
||||||
|
|
||||||
## Common Operation Workflows
|
## Operation Workflows
|
||||||
|
|
||||||
### Add Subscription Workflow
|
As a comprehensive AI agent, you handle various types of user requests. Different operations follow different workflows:
|
||||||
1. Understand the media content the user wants to subscribe to
|
|
||||||
2. Search for related media information
|
|
||||||
3. Create subscription rules
|
|
||||||
4. Confirm successful subscription
|
|
||||||
|
|
||||||
### Search and Download Workflow
|
### Subscription and Download Workflow
|
||||||
1. Understand user requirements (movie names, TV show names, etc.)
|
|
||||||
2. Search for related torrent resources
|
|
||||||
3. Filter suitable resources
|
|
||||||
4. Add to downloader
|
|
||||||
|
|
||||||
### Query Status Workflow
|
For **Subscription** and **Download** operations, follow this unified three-step process:
|
||||||
|
|
||||||
|
#### Step 1: Extract User Requirements
|
||||||
|
- Extract the media name/content the user wants to operate on
|
||||||
|
- Identify the operation type: **Subscription** or **Download**
|
||||||
|
- Extract and categorize user-specified parameters:
|
||||||
|
- **Media Type Parameters**: movie, tv, anime, etc. - Used when searching media information
|
||||||
|
- **Resource Parameters**: resolution, quality, site, etc. - Used when searching torrent resources
|
||||||
|
- **Download Parameters**: downloader, download path, etc. - Used when adding downloads
|
||||||
|
|
||||||
|
#### Step 2: Search Media Information
|
||||||
|
- Use the media name to search for matching media information
|
||||||
|
- **Apply Media Type Parameters**: If the user specified a media type (movie/tv/anime), use it to filter search results
|
||||||
|
- Confirm the correct media match with the user if multiple results are found
|
||||||
|
|
||||||
|
#### Step 3: Execute Operation Based on Type
|
||||||
|
|
||||||
|
**For Subscription Operations:**
|
||||||
|
1. After confirming the media information, directly create a subscription rule
|
||||||
|
2. Use the matched media information to set up the subscription
|
||||||
|
3. Confirm successful subscription creation
|
||||||
|
|
||||||
|
**For Download Operations:**
|
||||||
|
1. Use the confirmed media name to search for torrent resources
|
||||||
|
2. **Apply Resource Parameters**:
|
||||||
|
- If user specified resolution, filter resources by resolution (e.g., 1080p, 4K)
|
||||||
|
- If user specified quality, filter resources by quality (e.g., BluRay, WEB-DL)
|
||||||
|
- If user specified site, search only on the specified site(s)
|
||||||
|
3. Filter and select suitable torrent resources
|
||||||
|
4. **Apply Download Parameters**:
|
||||||
|
- If user specified downloader, use the specified downloader
|
||||||
|
- If user specified download path or other download settings, apply them
|
||||||
|
5. Add the selected torrent resources to the downloader
|
||||||
|
6. Confirm successful download addition
|
||||||
|
|
||||||
|
### Other Operation Workflows
|
||||||
|
|
||||||
|
For other types of operations (query status, search media, manage subscriptions, etc.), handle them according to their specific requirements:
|
||||||
|
|
||||||
|
#### Query Status Workflow
|
||||||
1. Understand what information the user wants to know
|
1. Understand what information the user wants to know
|
||||||
2. Query related data
|
2. Query related data using appropriate tools
|
||||||
3. Organize and present results
|
3. Organize and present results clearly
|
||||||
|
|
||||||
|
#### General Search Workflow
|
||||||
|
1. Understand the user's search intent
|
||||||
|
2. Use appropriate search tools based on the search type
|
||||||
|
3. Present search results in a structured format
|
||||||
|
|
||||||
|
#### Management Operations
|
||||||
|
1. Understand the management task (e.g., modify subscription, delete download, etc.)
|
||||||
|
2. Execute the corresponding management operation
|
||||||
|
3. Confirm the operation result
|
||||||
|
|
||||||
## Tool Usage Guidelines
|
## Tool Usage Guidelines
|
||||||
|
|
||||||
### Available Tools
|
|
||||||
You have access to the following tools for media management:
|
|
||||||
|
|
||||||
1. **search_media**: Search for movies, TV shows, anime, and other media content
|
|
||||||
2. **add_subscribe**: Create subscription rules for media content
|
|
||||||
3. **search_torrents**: Search for torrent resources on sites
|
|
||||||
4. **add_download**: Add torrent resources to downloaders
|
|
||||||
5. **query_subscribes**: Check subscription status and list
|
|
||||||
6. **query_downloads**: Check download status and progress
|
|
||||||
7. **query_downloaders**: List available downloaders and their configuration
|
|
||||||
8. **get_recommendations**: Get popular media recommendations
|
|
||||||
9. **query_media_library**: Check media library status
|
|
||||||
10. **send_message**: Send notifications to users
|
|
||||||
|
|
||||||
### Tool Usage Principles
|
### Tool Usage Principles
|
||||||
- Use tools proactively to complete user requests
|
- **Identify Operation Type First**: Determine what type of operation the user is requesting (subscription, download, query, search, management, etc.)
|
||||||
- Always explain what you're doing when using tools
|
- **Follow Appropriate Workflow**:
|
||||||
- Provide detailed results and explanations
|
- For subscription and download operations, follow the three-step workflow (Extract → Search Media → Execute Operation)
|
||||||
- Handle errors gracefully and suggest alternatives
|
- For other operations, follow their specific workflows
|
||||||
- Confirm user intent before performing download operations
|
- **Parameter Application** (for subscription/download operations): Correctly apply parameters at the appropriate stages:
|
||||||
|
- Media type parameters → when searching media information
|
||||||
|
- Resource parameters → when searching torrent resources
|
||||||
|
- Download parameters → when adding downloads
|
||||||
|
- **Proactive Execution**: Use tools proactively to complete user requests without unnecessary confirmation
|
||||||
|
- **Clear Communication**: Always explain what you're doing when using tools
|
||||||
|
- **Detailed Results**: Provide detailed results and explanations after each operation
|
||||||
|
- **Error Handling**: Handle errors gracefully and suggest alternatives
|
||||||
|
- **Safety Confirmation**: Confirm user intent before performing download operations if the intent is unclear
|
||||||
|
|
||||||
|
### Parameter Handling Best Practices
|
||||||
|
- **Extract All Parameters**: Carefully extract all user-specified parameters from their request
|
||||||
|
- **Parameter Categorization** (for subscription/download operations): Correctly categorize parameters into Media Type, Resource, or Download parameters
|
||||||
|
- **Default Behavior**: If parameters are not specified, use reasonable defaults or system defaults
|
||||||
|
- **Parameter Validation**: Validate parameters before using them (e.g., check if specified downloader exists)
|
||||||
|
|
||||||
### Response Format
|
### Response Format
|
||||||
- Always respond in Chinese
|
- Always respond in Chinese
|
||||||
@@ -72,6 +113,7 @@ You have access to the following tools for media management:
|
|||||||
- Provide structured information when appropriate
|
- Provide structured information when appropriate
|
||||||
- Include relevant details about media content (title, year, type, etc.)
|
- Include relevant details about media content (title, year, type, etc.)
|
||||||
- Explain the results of tool operations clearly
|
- Explain the results of tool operations clearly
|
||||||
|
- When applying parameters, explicitly mention which parameters are being used and where
|
||||||
|
|
||||||
### Message Format Requirements
|
### Message Format Requirements
|
||||||
When formatting your responses, please follow the format requirements for the current message channel. The system will automatically inform you of the specific format requirements based on the channel (Telegram, WeChat, Slack, etc.).
|
When formatting your responses, please follow the format requirements for the current message channel. The system will automatically inform you of the specific format requirements based on the channel (Telegram, WeChat, Slack, etc.).
|
||||||
@@ -84,9 +126,29 @@ When formatting your responses, please follow the format requirements for the cu
|
|||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
- Always confirm user intent before performing download operations
|
### Workflow Adherence
|
||||||
- If search results are not ideal, proactively adjust search strategies
|
- **Identify operation type**: First determine what type of operation the user is requesting
|
||||||
- Maintain a friendly and professional tone
|
- **For subscription/download operations**: Always follow the three-step workflow (Extract → Search Media → Execute Operation)
|
||||||
|
- **Never skip steps** (for subscription/download): Always search for media information first, even if the user only mentions downloading
|
||||||
|
- **Correct parameter application** (for subscription/download): Ensure parameters are applied at the correct stage of the workflow
|
||||||
|
- **For other operations**: Follow their specific workflows as appropriate
|
||||||
|
|
||||||
|
### Operation-Specific Notes
|
||||||
|
- **Subscription Operations**: After finding the media, directly create the subscription without additional confirmation (unless the media match is ambiguous)
|
||||||
|
- **Download Operations**:
|
||||||
|
- Always search for torrent resources using the confirmed media name
|
||||||
|
- Apply resource parameters (resolution, quality, site) when searching torrents
|
||||||
|
- Apply download parameters (downloader, path) when adding downloads
|
||||||
|
- Confirm user intent before performing download operations if the intent is unclear
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- If media search results are not ideal, proactively adjust search strategies (try different search terms, adjust media type filters)
|
||||||
|
- If torrent search results are not ideal, adjust resource parameters or try different search strategies
|
||||||
|
- Maintain a friendly and professional tone when handling errors
|
||||||
- Seek solutions proactively when encountering problems
|
- Seek solutions proactively when encountering problems
|
||||||
|
- Provide helpful suggestions when operations fail
|
||||||
|
|
||||||
|
### User Experience
|
||||||
- Remember user preferences and provide personalized recommendations
|
- Remember user preferences and provide personalized recommendations
|
||||||
- Handle errors gracefully and provide helpful suggestions
|
- Handle errors gracefully and provide helpful suggestions
|
||||||
|
- Always respond in Chinese with clear and friendly language
|
||||||
@@ -8,6 +8,7 @@ from app.agent.tools.impl.add_download import AddDownloadTool
|
|||||||
from app.agent.tools.impl.query_subscribes import QuerySubscribesTool
|
from app.agent.tools.impl.query_subscribes import QuerySubscribesTool
|
||||||
from app.agent.tools.impl.query_downloads import QueryDownloadsTool
|
from app.agent.tools.impl.query_downloads import QueryDownloadsTool
|
||||||
from app.agent.tools.impl.query_downloaders import QueryDownloadersTool
|
from app.agent.tools.impl.query_downloaders import QueryDownloadersTool
|
||||||
|
from app.agent.tools.impl.query_sites import QuerySitesTool
|
||||||
from app.agent.tools.impl.get_recommendations import GetRecommendationsTool
|
from app.agent.tools.impl.get_recommendations import GetRecommendationsTool
|
||||||
from app.agent.tools.impl.query_media_library import QueryMediaLibraryTool
|
from app.agent.tools.impl.query_media_library import QueryMediaLibraryTool
|
||||||
from app.agent.tools.impl.send_message import SendMessageTool
|
from app.agent.tools.impl.send_message import SendMessageTool
|
||||||
@@ -22,6 +23,7 @@ __all__ = [
|
|||||||
"QuerySubscribesTool",
|
"QuerySubscribesTool",
|
||||||
"QueryDownloadsTool",
|
"QueryDownloadsTool",
|
||||||
"QueryDownloadersTool",
|
"QueryDownloadersTool",
|
||||||
|
"QuerySitesTool",
|
||||||
"GetRecommendationsTool",
|
"GetRecommendationsTool",
|
||||||
"QueryMediaLibraryTool",
|
"QueryMediaLibraryTool",
|
||||||
"SendMessageTool",
|
"SendMessageTool",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from app.agent.tools.impl.get_recommendations import GetRecommendationsTool
|
|||||||
from app.agent.tools.impl.query_downloaders import QueryDownloadersTool
|
from app.agent.tools.impl.query_downloaders import QueryDownloadersTool
|
||||||
from app.agent.tools.impl.query_downloads import QueryDownloadsTool
|
from app.agent.tools.impl.query_downloads import QueryDownloadsTool
|
||||||
from app.agent.tools.impl.query_media_library import QueryMediaLibraryTool
|
from app.agent.tools.impl.query_media_library import QueryMediaLibraryTool
|
||||||
|
from app.agent.tools.impl.query_sites import QuerySitesTool
|
||||||
from app.agent.tools.impl.query_subscribes import QuerySubscribesTool
|
from app.agent.tools.impl.query_subscribes import QuerySubscribesTool
|
||||||
from app.agent.tools.impl.search_media import SearchMediaTool
|
from app.agent.tools.impl.search_media import SearchMediaTool
|
||||||
from app.agent.tools.impl.search_torrents import SearchTorrentsTool
|
from app.agent.tools.impl.search_torrents import SearchTorrentsTool
|
||||||
@@ -33,6 +34,7 @@ class MoviePilotToolFactory:
|
|||||||
QuerySubscribesTool,
|
QuerySubscribesTool,
|
||||||
QueryDownloadsTool,
|
QueryDownloadsTool,
|
||||||
QueryDownloadersTool,
|
QueryDownloadersTool,
|
||||||
|
QuerySitesTool,
|
||||||
GetRecommendationsTool,
|
GetRecommendationsTool,
|
||||||
QueryMediaLibraryTool,
|
QueryMediaLibraryTool,
|
||||||
SendMessageTool
|
SendMessageTool
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from app.agent.tools.base import MoviePilotTool, ToolChain
|
|||||||
from app.chain.download import DownloadChain
|
from app.chain.download import DownloadChain
|
||||||
from app.core.context import Context
|
from app.core.context import Context
|
||||||
from app.core.metainfo import MetaInfo
|
from app.core.metainfo import MetaInfo
|
||||||
|
from app.db.site_oper import SiteOper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.schemas import TorrentInfo
|
from app.schemas import TorrentInfo
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ from app.schemas import TorrentInfo
|
|||||||
class AddDownloadInput(BaseModel):
|
class AddDownloadInput(BaseModel):
|
||||||
"""添加下载工具的输入参数模型"""
|
"""添加下载工具的输入参数模型"""
|
||||||
explanation: str = Field(..., description="Clear explanation of why this tool is being used in the current context")
|
explanation: str = Field(..., description="Clear explanation of why this tool is being used in the current context")
|
||||||
|
site_name: str = Field(..., description="Name of the torrent site/source (e.g., 'The Pirate Bay')")
|
||||||
torrent_title: str = Field(...,
|
torrent_title: str = Field(...,
|
||||||
description="The display name/title of the torrent (e.g., 'The.Matrix.1999.1080p.BluRay.x264')")
|
description="The display name/title of the torrent (e.g., 'The.Matrix.1999.1080p.BluRay.x264')")
|
||||||
torrent_url: str = Field(..., description="Direct URL to the torrent file (.torrent) or magnet link")
|
torrent_url: str = Field(..., description="Direct URL to the torrent file (.torrent) or magnet link")
|
||||||
@@ -33,7 +35,7 @@ class AddDownloadTool(MoviePilotTool):
|
|||||||
description: str = "Add torrent download task to the configured downloader (qBittorrent, Transmission, etc.). Downloads the torrent file and starts the download process with specified settings."
|
description: str = "Add torrent download task to the configured downloader (qBittorrent, Transmission, etc.). Downloads the torrent file and starts the download process with specified settings."
|
||||||
args_schema: Type[BaseModel] = AddDownloadInput
|
args_schema: Type[BaseModel] = AddDownloadInput
|
||||||
|
|
||||||
async def run(self, torrent_title: str, torrent_url: str, torrent_description: Optional[str] = None,
|
async def run(self, site_name: str, torrent_title: str, torrent_url: str, torrent_description: Optional[str] = None,
|
||||||
downloader: Optional[str] = None, save_path: Optional[str] = None,
|
downloader: Optional[str] = None, save_path: Optional[str] = None,
|
||||||
labels: Optional[str] = None, **kwargs) -> str:
|
labels: Optional[str] = None, **kwargs) -> str:
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -46,16 +48,30 @@ class AddDownloadTool(MoviePilotTool):
|
|||||||
# 使用DownloadChain添加下载
|
# 使用DownloadChain添加下载
|
||||||
download_chain = DownloadChain()
|
download_chain = DownloadChain()
|
||||||
|
|
||||||
|
# 根据站点名称查询站点cookie
|
||||||
|
siteinfo = await SiteOper().async_get_by_name(site_name)
|
||||||
|
if not siteinfo:
|
||||||
|
return f"错误:未找到站点信息:{site_name}"
|
||||||
|
|
||||||
# 创建下载上下文
|
# 创建下载上下文
|
||||||
torrent_info = TorrentInfo(
|
torrent_info = TorrentInfo(
|
||||||
title=torrent_title,
|
title=torrent_title,
|
||||||
download_url=torrent_url
|
download_url=torrent_url,
|
||||||
|
site_name=site_name,
|
||||||
|
site_ua=siteinfo.ua,
|
||||||
|
site_cookie=siteinfo.cookie,
|
||||||
|
site_proxy=siteinfo.proxy,
|
||||||
|
site_order=siteinfo.pri,
|
||||||
|
site_downloader=siteinfo.downloader
|
||||||
)
|
)
|
||||||
meta_info = MetaInfo(title=torrent_title, subtitle=torrent_description)
|
meta_info = MetaInfo(title=torrent_title, subtitle=torrent_description)
|
||||||
|
media_info = ToolChain().recognize_media(meta=meta_info)
|
||||||
|
if not media_info:
|
||||||
|
return "错误:无法识别媒体信息,无法添加下载任务"
|
||||||
context = Context(
|
context = Context(
|
||||||
torrent_info=torrent_info,
|
torrent_info=torrent_info,
|
||||||
meta_info=meta_info,
|
meta_info=meta_info,
|
||||||
media_info=ToolChain().recognize_media(meta=meta_info)
|
media_info=media_info
|
||||||
)
|
)
|
||||||
|
|
||||||
did = download_chain.download_single(
|
did = download_chain.download_single(
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ class QueryMediaLibraryInput(BaseModel):
|
|||||||
media_type: Optional[str] = Field("all",
|
media_type: Optional[str] = Field("all",
|
||||||
description="Type of media content: '电影' for films, '电视剧' for television series or anime series, 'all' for all types")
|
description="Type of media content: '电影' for films, '电视剧' for television series or anime series, 'all' for all types")
|
||||||
title: Optional[str] = Field(None,
|
title: Optional[str] = Field(None,
|
||||||
description="Specific media title to search for (optional, if provided returns detailed info for that specific media)")
|
description="Specific media title to check if it exists in the media library (optional, if provided checks for that specific media)")
|
||||||
year: Optional[str] = Field(None,
|
year: Optional[str] = Field(None,
|
||||||
description="Release year of the media (optional, helps narrow down search results)")
|
description="Release year of the media (optional, helps narrow down search results)")
|
||||||
|
|
||||||
|
|
||||||
class QueryMediaLibraryTool(MoviePilotTool):
|
class QueryMediaLibraryTool(MoviePilotTool):
|
||||||
name: str = "query_media_library"
|
name: str = "query_media_library"
|
||||||
description: str = "Query media library status and list all media files that have been successfully processed and added to the media server (Plex, Emby, Jellyfin). Shows library statistics and file details."
|
description: str = "Check if a specific media resource already exists in the media library (Plex, Emby, Jellyfin). Use this tool to verify whether a movie or TV series has been successfully processed and added to the media server before performing operations like downloading or subscribing."
|
||||||
args_schema: Type[BaseModel] = QueryMediaLibraryInput
|
args_schema: Type[BaseModel] = QueryMediaLibraryInput
|
||||||
|
|
||||||
async def run(self, media_type: Optional[str] = "all",
|
async def run(self, media_type: Optional[str] = "all",
|
||||||
|
|||||||
66
app/agent/tools/impl/query_sites.py
Normal file
66
app/agent/tools/impl/query_sites.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
"""查询站点工具"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Optional, Type
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from app.agent.tools.base import MoviePilotTool
|
||||||
|
from app.db.site_oper import SiteOper
|
||||||
|
from app.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
class QuerySitesInput(BaseModel):
|
||||||
|
"""查询站点工具的输入参数模型"""
|
||||||
|
explanation: str = Field(..., description="Clear explanation of why this tool is being used in the current context")
|
||||||
|
status: Optional[str] = Field("all",
|
||||||
|
description="Filter sites by status: 'active' for enabled sites, 'inactive' for disabled sites, 'all' for all sites")
|
||||||
|
name: Optional[str] = Field(None,
|
||||||
|
description="Filter sites by name (partial match, optional)")
|
||||||
|
|
||||||
|
|
||||||
|
class QuerySitesTool(MoviePilotTool):
|
||||||
|
name: str = "query_sites"
|
||||||
|
description: str = "Query site status and list all configured sites. Shows site name, domain, status, priority, and basic configuration."
|
||||||
|
args_schema: Type[BaseModel] = QuerySitesInput
|
||||||
|
|
||||||
|
async def run(self, status: Optional[str] = "all", name: Optional[str] = None, **kwargs) -> str:
|
||||||
|
logger.info(f"执行工具: {self.name}, 参数: status={status}, name={name}")
|
||||||
|
try:
|
||||||
|
site_oper = SiteOper()
|
||||||
|
# 获取所有站点(按优先级排序)
|
||||||
|
sites = site_oper.list_order_by_pri()
|
||||||
|
filtered_sites = []
|
||||||
|
for site in sites:
|
||||||
|
# 按状态过滤
|
||||||
|
if status == "active" and not site.is_active:
|
||||||
|
continue
|
||||||
|
if status == "inactive" and site.is_active:
|
||||||
|
continue
|
||||||
|
# 按名称过滤(部分匹配)
|
||||||
|
if name and name.lower() not in (site.name or "").lower():
|
||||||
|
continue
|
||||||
|
filtered_sites.append(site)
|
||||||
|
if filtered_sites:
|
||||||
|
# 精简字段,只保留关键信息
|
||||||
|
simplified_sites = []
|
||||||
|
for s in filtered_sites:
|
||||||
|
simplified = {
|
||||||
|
"id": s.id,
|
||||||
|
"name": s.name,
|
||||||
|
"domain": s.domain,
|
||||||
|
"url": s.url,
|
||||||
|
"pri": s.pri,
|
||||||
|
"is_active": s.is_active,
|
||||||
|
"downloader": s.downloader,
|
||||||
|
"proxy": s.proxy,
|
||||||
|
"timeout": s.timeout
|
||||||
|
}
|
||||||
|
simplified_sites.append(simplified)
|
||||||
|
result_json = json.dumps(simplified_sites, ensure_ascii=False, indent=2)
|
||||||
|
return result_json
|
||||||
|
return "未找到相关站点。"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"查询站点失败: {e}", exc_info=True)
|
||||||
|
return f"查询站点时发生错误: {str(e)}"
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
"""搜索种子工具"""
|
"""搜索种子工具"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
from typing import List, Optional, Type
|
from typing import List, Optional, Type
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -23,6 +24,8 @@ class SearchTorrentsInput(BaseModel):
|
|||||||
season: Optional[int] = Field(None, description="Season number for TV shows (optional, only applicable for series)")
|
season: Optional[int] = Field(None, description="Season number for TV shows (optional, only applicable for series)")
|
||||||
sites: Optional[List[int]] = Field(None,
|
sites: Optional[List[int]] = Field(None,
|
||||||
description="Array of specific site IDs to search on (optional, if not provided searches all configured sites)")
|
description="Array of specific site IDs to search on (optional, if not provided searches all configured sites)")
|
||||||
|
filter_pattern: Optional[str] = Field(None,
|
||||||
|
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)")
|
||||||
|
|
||||||
|
|
||||||
class SearchTorrentsTool(MoviePilotTool):
|
class SearchTorrentsTool(MoviePilotTool):
|
||||||
@@ -32,14 +35,23 @@ class SearchTorrentsTool(MoviePilotTool):
|
|||||||
|
|
||||||
async def run(self, title: str, year: Optional[str] = None,
|
async def run(self, title: str, year: Optional[str] = None,
|
||||||
media_type: Optional[str] = None, season: Optional[int] = None,
|
media_type: Optional[str] = None, season: Optional[int] = None,
|
||||||
sites: Optional[List[int]] = None, **kwargs) -> str:
|
sites: Optional[List[int]] = None, filter_pattern: Optional[str] = None, **kwargs) -> str:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"执行工具: {self.name}, 参数: title={title}, year={year}, media_type={media_type}, season={season}, sites={sites}")
|
f"执行工具: {self.name}, 参数: title={title}, year={year}, media_type={media_type}, season={season}, sites={sites}, filter_pattern={filter_pattern}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
search_chain = SearchChain()
|
search_chain = SearchChain()
|
||||||
torrents = search_chain.search_by_title(title=title, sites=sites)
|
torrents = search_chain.search_by_title(title=title, sites=sites)
|
||||||
filtered_torrents = []
|
filtered_torrents = []
|
||||||
|
# 编译正则表达式(如果提供)
|
||||||
|
regex_pattern = None
|
||||||
|
if filter_pattern:
|
||||||
|
try:
|
||||||
|
regex_pattern = re.compile(filter_pattern, re.IGNORECASE)
|
||||||
|
except re.error as e:
|
||||||
|
logger.warning(f"正则表达式编译失败: {filter_pattern}, 错误: {e}")
|
||||||
|
return f"正则表达式格式错误: {str(e)}"
|
||||||
|
|
||||||
for torrent in torrents:
|
for torrent in torrents:
|
||||||
# torrent 是 Context 对象,需要通过 meta_info 和 media_info 访问属性
|
# torrent 是 Context 对象,需要通过 meta_info 和 media_info 访问属性
|
||||||
if year and torrent.meta_info and torrent.meta_info.year != year:
|
if year and torrent.meta_info and torrent.meta_info.year != year:
|
||||||
@@ -49,6 +61,10 @@ class SearchTorrentsTool(MoviePilotTool):
|
|||||||
continue
|
continue
|
||||||
if season and torrent.meta_info and torrent.meta_info.begin_season != season:
|
if season and torrent.meta_info and torrent.meta_info.begin_season != season:
|
||||||
continue
|
continue
|
||||||
|
# 使用正则表达式过滤标题(分辨率、质量等关键字)
|
||||||
|
if regex_pattern and torrent.torrent_info and torrent.torrent_info.title:
|
||||||
|
if not regex_pattern.search(torrent.torrent_info.title):
|
||||||
|
continue
|
||||||
filtered_torrents.append(torrent)
|
filtered_torrents.append(torrent)
|
||||||
|
|
||||||
if filtered_torrents:
|
if filtered_torrents:
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ class Site(Base):
|
|||||||
result = await db.execute(select(cls).where(cls.domain == domain))
|
result = await db.execute(select(cls).where(cls.domain == domain))
|
||||||
return result.scalar_one_or_none()
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@async_db_query
|
||||||
|
async def async_get_by_name(cls, db: AsyncSession, name: str):
|
||||||
|
result = await db.execute(select(cls).where(cls.name == name))
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@db_query
|
@db_query
|
||||||
def get_actives(cls, db: Session):
|
def get_actives(cls, db: Session):
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ class SiteOper(DbOper):
|
|||||||
"""
|
"""
|
||||||
return await Site.async_get_by_domain(self._db, domain)
|
return await Site.async_get_by_domain(self._db, domain)
|
||||||
|
|
||||||
|
async def async_get_by_name(self, name: str) -> Site:
|
||||||
|
"""
|
||||||
|
异步按名称获取站点
|
||||||
|
"""
|
||||||
|
return await Site.async_get_by_name(self._db, name)
|
||||||
|
|
||||||
def get_domains_by_ids(self, ids: List[int]) -> List[str]:
|
def get_domains_by_ids(self, ids: List[int]) -> List[str]:
|
||||||
"""
|
"""
|
||||||
按ID获取站点域名
|
按ID获取站点域名
|
||||||
|
|||||||
Reference in New Issue
Block a user