Compare commits
10 Commits
0efd122242
...
3db7e3c222
| Author | SHA1 | Date | |
|---|---|---|---|
| 3db7e3c222 | |||
| a1a106a955 | |||
| c563846017 | |||
| 58a8d6052d | |||
|
|
8b3e6a4b5c | ||
|
|
ddee295fa3 | ||
| c6e7658133 | |||
|
|
9ca80684b6 | ||
| 507f01d8d0 | |||
| 7694511601 |
182
README.md
182
README.md
@ -2,151 +2,81 @@
|
|||||||
|
|
||||||
## 📝 插件简介
|
## 📝 插件简介
|
||||||
|
|
||||||
Nullbr资源搜索插件是为MoviePilot-v2设计的资源搜索增强插件,通过集成Nullbr API,为用户提供优先级资源搜索功能。
|
Nullbr资源搜索插件是为MoviePilot设计的智能资源搜索插件,通过集成Nullbr API,支持115网盘、磁力、ed2k、m3u8等多种资源类型的搜索和自动转存。
|
||||||
|
|
||||||
**主要特点:**
|
## 🛠️ 插件安装方法
|
||||||
- 🚀 **优先搜索**: 在MoviePilot搜索其他资源站之前,优先使用Nullbr API查找资源
|
|
||||||
- 🔍 **智能跳过**: 如果Nullbr找到资源,自动跳过后续搜索,提高效率
|
|
||||||
- 🎯 **多种资源**: 支持115网盘、磁力链接、ed2k、m3u8等多种资源类型
|
|
||||||
- 📊 **统计监控**: 提供详细的使用统计和状态监控
|
|
||||||
- ⚙️ **灵活配置**: 支持自定义资源类型和搜索参数
|
|
||||||
|
|
||||||
## 🛠️ 安装方法
|
复制本仓库地址到插件源配置即可在市场搜到本插件:
|
||||||
|
|
||||||
### 方法1: 直接复制文件
|
1. 在MoviePilot设置中找到「插件」→「插件源」
|
||||||
1. 将`nullbrsearch`目录复制到MoviePilot的`app/plugins/`目录下
|
2. 添加插件源:`https://github.com/Hqyel/MoviePilot-Plugins-Third`
|
||||||
2. 重启MoviePilot服务
|
3. 保存后在插件市场搜索「Nullbr资源搜索」
|
||||||
|
4. 点击安装并配置相关参数
|
||||||
|
|
||||||
### 方法2: Git克隆
|
## 📖 使用方法
|
||||||
```bash
|
|
||||||
cd /path/to/moviepilot/app/plugins/
|
**重要:与本插件交互必须以问号结尾!**
|
||||||
git clone <repository-url> nullbrsearch
|
|
||||||
```
|
### 基本搜索
|
||||||
|
在MoviePilot的任何消息界面发送搜索请求时,必须以问号结尾:
|
||||||
|
|
||||||
|
- ✅ `武林外传?`
|
||||||
|
- ✅ `权力的游戏?`
|
||||||
|
- ✅ `复仇者联盟?`
|
||||||
|
- ❌ `武林外传` (不会触发搜索)
|
||||||
|
- ❌ `权力的游戏` (不会触发搜索)
|
||||||
|
|
||||||
|
### 资源获取流程
|
||||||
|
1. **搜索影片**:发送 `影片名?`
|
||||||
|
2. **选择影片**:回复数字(如 `1?`)查看资源类型
|
||||||
|
3. **获取资源**:
|
||||||
|
- 自动获取(按优先级):直接发送数字 `2?`
|
||||||
|
- 指定类型:发送 `2.115?` 或 `3.magnet?`
|
||||||
|
|
||||||
|
### CloudMediaSync转存
|
||||||
|
如果配置了CMS系统,获取115网盘资源后:
|
||||||
|
- 发送资源编号即可转存:`1?`、`2?`、`3?`...
|
||||||
|
|
||||||
|
## ⚠️ 重要注意事项
|
||||||
|
|
||||||
|
**本插件与ChatGPT插件冲突!**
|
||||||
|
|
||||||
|
如果你同时安装了ChatGPT插件,需要:
|
||||||
|
1. 禁用ChatGPT插件,或
|
||||||
|
2. 调整插件优先级,确保Nullbr插件优先级更高
|
||||||
|
|
||||||
|
这是因为两个插件都会监听用户消息,可能产生冲突。
|
||||||
|
|
||||||
## 🔧 配置说明
|
## 🔧 配置说明
|
||||||
|
|
||||||
### 必需配置
|
### 必需配置
|
||||||
- **APP_ID**: Nullbr API的应用ID,用于基本搜索功能(必填)
|
- **APP_ID**: Nullbr API的应用ID(必填)
|
||||||
|
- **API_KEY**: Nullbr API的密钥(可选,用于获取下载链接)
|
||||||
|
|
||||||
### 可选配置
|
### 可选配置
|
||||||
- **API_KEY**: Nullbr API的密钥,用于获取具体下载链接(可选)
|
- **资源类型**: 启用/禁用不同资源类型(115网盘、磁力、M3U8、ED2K)
|
||||||
- **资源类型**: 可以选择启用/禁用不同的资源类型
|
- **资源优先级**: 设置资源获取的优先顺序
|
||||||
- 115网盘分享
|
- **CloudMediaSync**: 配置115网盘资源的自动转存功能
|
||||||
- 磁力链接
|
|
||||||
- M3U8在线视频
|
|
||||||
- ED2K链接
|
|
||||||
|
|
||||||
### 高级设置
|
|
||||||
- **搜索超时**: 设置API请求的超时时间(10-120秒)
|
|
||||||
|
|
||||||
## 📖 使用方法
|
|
||||||
|
|
||||||
1. **获取API密钥**: 从Nullbr官方获取APP_ID和API_KEY
|
|
||||||
2. **配置插件**: 在MoviePilot插件设置中填入相关信息
|
|
||||||
3. **启用插件**: 打开插件开关,插件开始工作
|
|
||||||
4. **正常使用**: 通过MoviePilot的任何搜索功能,插件会自动优先搜索Nullbr资源
|
|
||||||
|
|
||||||
## 🌐 支持的API接口
|
|
||||||
|
|
||||||
插件提供了以下REST API接口:
|
|
||||||
|
|
||||||
### `/nullbr/search`
|
|
||||||
- **方法**: GET
|
|
||||||
- **参数**: `keyword`, `page`
|
|
||||||
- **功能**: 搜索影视资源
|
|
||||||
|
|
||||||
### `/nullbr/resources`
|
|
||||||
- **方法**: POST
|
|
||||||
- **参数**: `media_type`, `tmdbid`, `resource_type`
|
|
||||||
- **功能**: 获取具体资源链接
|
|
||||||
|
|
||||||
### `/nullbr/test`
|
|
||||||
- **方法**: GET
|
|
||||||
- **功能**: 测试API连接状态
|
|
||||||
|
|
||||||
## 📊 工作原理
|
|
||||||
|
|
||||||
```
|
|
||||||
用户搜索请求
|
|
||||||
↓
|
|
||||||
MoviePilot接收
|
|
||||||
↓
|
|
||||||
Nullbr插件拦截 ← 优先级最高
|
|
||||||
↓
|
|
||||||
调用Nullbr API
|
|
||||||
↓
|
|
||||||
找到资源? → 是 → 返回结果给用户 → 结束搜索
|
|
||||||
↓
|
|
||||||
否
|
|
||||||
↓
|
|
||||||
继续搜索其他资源站
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 支持的媒体类型
|
|
||||||
|
|
||||||
- **电影** (movie): 支持获取电影资源
|
|
||||||
- **剧集** (tv): 支持获取完整剧集资源
|
|
||||||
- **合集** (collection): 支持搜索系列合集
|
|
||||||
- **人物** (person): 支持人物相关搜索
|
|
||||||
|
|
||||||
## 📈 状态监控
|
|
||||||
|
|
||||||
插件提供详细的使用统计:
|
|
||||||
- 总搜索次数
|
|
||||||
- 成功搜索次数
|
|
||||||
- 失败搜索次数
|
|
||||||
- 最后搜索时间
|
|
||||||
- API连接状态
|
|
||||||
- 资源类型启用状态
|
|
||||||
|
|
||||||
## ⚠️ 注意事项
|
|
||||||
|
|
||||||
1. **API限制**: 请遵守Nullbr API的使用限制和频率限制
|
|
||||||
2. **网络连接**: 确保MoviePilot服务器能够访问`api.nullbr.eu.org`
|
|
||||||
3. **权限要求**: API_KEY的权限级别决定了能获取的资源类型
|
|
||||||
4. **日志监控**: 如遇问题请检查MoviePilot日志中的相关错误信息
|
|
||||||
|
|
||||||
## 🐛 故障排除
|
|
||||||
|
|
||||||
### 常见问题
|
|
||||||
|
|
||||||
**Q: 插件显示已启用但不工作**
|
|
||||||
A: 检查APP_ID是否正确配置,查看日志中的错误信息
|
|
||||||
|
|
||||||
**Q: 能搜索但无法获取下载链接**
|
|
||||||
A: 需要配置有效的API_KEY才能获取具体资源链接
|
|
||||||
|
|
||||||
**Q: API请求超时**
|
|
||||||
A: 可能是网络问题,尝试增加超时时间或检查网络连接
|
|
||||||
|
|
||||||
**Q: 搜索结果为空**
|
|
||||||
A: 检查搜索关键词,或者查看Nullbr API是否有该资源
|
|
||||||
|
|
||||||
### 日志排查
|
|
||||||
在MoviePilot日志中搜索以下关键词:
|
|
||||||
- `Nullbr`
|
|
||||||
- `nullbr`
|
|
||||||
- `NullbrSearch`
|
|
||||||
|
|
||||||
## 📝 更新日志
|
## 📝 更新日志
|
||||||
|
|
||||||
### v1.0.0 (2024-08-06)
|
### v2.0.4 (2024-08-08)
|
||||||
- 🎉 首次发布
|
- 🎉 移除了数据统计页面,简化插件结构
|
||||||
- ✅ 支持基本搜索功能
|
- ✅ 优化了用户交互流程
|
||||||
- ✅ 支持多种资源类型
|
- ✅ 修复了与其他插件的兼容性问题
|
||||||
- ✅ 提供完整的配置界面
|
- ✅ 改进了微信消息格式兼容性
|
||||||
- ✅ 集成API接口
|
- ✅ 完善了CloudMediaSync转存功能
|
||||||
- ✅ 添加使用统计功能
|
|
||||||
|
|
||||||
## 🤝 贡献
|
## 🤝 贡献
|
||||||
|
|
||||||
欢迎提交Issue和Pull Request来改进这个插件!
|
欢迎提交Issue和Pull Request来改进这个插件!
|
||||||
|
|
||||||
## 📄 许可证
|
|
||||||
|
|
||||||
本插件基于GPL-3.0许可证开源。
|
|
||||||
|
|
||||||
## 🙏 致谢
|
## 🙏 致谢
|
||||||
|
|
||||||
- 感谢MoviePilot项目提供的优秀插件框架
|
- 感谢MoviePilot项目提供的优秀插件框架
|
||||||
- 感谢Nullbr提供的资源API服务
|
- 感谢Nullbr提供的资源API服务
|
||||||
|
- 感谢iLay1678大佬,CMS接口部分参考了他项目:[nullbr_cms_bot](https://github.com/iLay1678/nullbr_cms_bot)
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
本插件基于GPL-3.0许可证开源。
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
{
|
{
|
||||||
"nullbrsearch": {
|
"nullbr_search": {
|
||||||
"name": "Nullbr资源搜索",
|
"name": "Nullbr资源搜索",
|
||||||
"description": "优先使用Nullbr API搜索影视资源,支持115网盘、磁力、ed2k、m3u8等多种资源类型。在MoviePilot搜索其他资源站之前优先查找Nullbr资源,提高搜索效率。",
|
"description": "支持nullbr api接口直接搜索影视资源。支持115网盘、磁力、ed2k、m3u8等多种资源类型。",
|
||||||
"labels": "资源",
|
"labels": "资源",
|
||||||
"version": "1.0.5",
|
"version": "2.0.0",
|
||||||
"icon": "https://raw.githubusercontent.com/Hqyel/MoviePilot-Plugins/main/icons/nullbr.png",
|
"icon": "https://raw.githubusercontent.com/Hqyel/MoviePilot-Plugins/main/icons/nullbr.png",
|
||||||
"author": "Hqyel",
|
"author": "Hqyel",
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"history": {
|
"history": {
|
||||||
"v1.0.4": "初始上架版本",
|
"v2.0.0": "重构整理代码。"
|
||||||
"v1.0.5": "增加资源优先度设置"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
110
plugins.v2/nullbr_search/cms_client.py
Normal file
110
plugins.v2/nullbr_search/cms_client.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import requests
|
||||||
|
import time
|
||||||
|
from app.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
class CloudSyncMediaClient:
|
||||||
|
"""CloudSyncMedia客户端"""
|
||||||
|
|
||||||
|
def __init__(self, base_url: str, username: str, password: str):
|
||||||
|
self.base_url = base_url.rstrip('/')
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.token = None
|
||||||
|
self.token_expiry = 0
|
||||||
|
|
||||||
|
# 配置请求会话
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.headers.update({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
})
|
||||||
|
|
||||||
|
# CMS一般为内网服务,禁用代理访问
|
||||||
|
self.session.proxies = {
|
||||||
|
'http': None,
|
||||||
|
'https': None
|
||||||
|
}
|
||||||
|
|
||||||
|
# 初始化时获取token
|
||||||
|
self._ensure_valid_token()
|
||||||
|
|
||||||
|
def _login(self) -> dict:
|
||||||
|
"""登录CMS系统获取token"""
|
||||||
|
try:
|
||||||
|
response = self.session.post(
|
||||||
|
f'{self.base_url}/api/auth/login',
|
||||||
|
json={
|
||||||
|
'username': self.username,
|
||||||
|
'password': self.password
|
||||||
|
},
|
||||||
|
timeout=(10, 30)
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get('code') != 200 or 'data' not in data:
|
||||||
|
raise ValueError(f'CMS登录失败: {data}')
|
||||||
|
|
||||||
|
return data['data']
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f'CMS登录失败: {str(e)}')
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _ensure_valid_token(self):
|
||||||
|
"""确保有效的token"""
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# 如果token不存在或距离过期时间不到1小时,重新获取token
|
||||||
|
if not self.token or current_time >= (self.token_expiry - 3600):
|
||||||
|
login_data = self._login()
|
||||||
|
self.token = login_data['token']
|
||||||
|
|
||||||
|
# 设置token过期时间为24小时后
|
||||||
|
self.token_expiry = current_time + 86400
|
||||||
|
|
||||||
|
# 更新session的Authorization header
|
||||||
|
self.session.headers.update({
|
||||||
|
'Authorization': f'Bearer {self.token}'
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info("CMS token已更新")
|
||||||
|
|
||||||
|
def add_share_down(self, url: str) -> dict:
|
||||||
|
"""添加分享链接到CMS系统进行转存"""
|
||||||
|
if not url:
|
||||||
|
raise ValueError('转存链接不能为空')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._ensure_valid_token()
|
||||||
|
|
||||||
|
response = self.session.post(
|
||||||
|
f'{self.base_url}/api/cloud/add_share_down',
|
||||||
|
json={'url': url},
|
||||||
|
timeout=(10, 30)
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
logger.info(f"CMS转存请求已发送: {url}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code == 401:
|
||||||
|
# token可能过期,强制重新获取
|
||||||
|
self.token = None
|
||||||
|
self._ensure_valid_token()
|
||||||
|
|
||||||
|
# 重试请求
|
||||||
|
response = self.session.post(
|
||||||
|
f'{self.base_url}/api/cloud/add_share_down',
|
||||||
|
json={'url': url},
|
||||||
|
timeout=(10, 30)
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'CMS转存请求失败: {str(e)}')
|
||||||
|
raise
|
||||||
202
plugins.v2/nullbr_search/nullbr_client.py
Normal file
202
plugins.v2/nullbr_search/nullbr_client.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import requests
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from urllib3.util.retry import Retry
|
||||||
|
from typing import Dict, Optional
|
||||||
|
from app.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
class NullbrApiClient:
|
||||||
|
"""Nullbr API客户端"""
|
||||||
|
|
||||||
|
def __init__(self, app_id: str, api_key: str = None):
|
||||||
|
self._app_id = app_id
|
||||||
|
self._api_key = api_key
|
||||||
|
self._base_url = "https://api.nullbr.eu.org"
|
||||||
|
|
||||||
|
# 配置请求会话
|
||||||
|
self._session = requests.Session()
|
||||||
|
self._session.headers.update({
|
||||||
|
'User-Agent': 'MoviePilot-NullbrSearch/1.0.4',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
|
||||||
|
# 配置重试策略
|
||||||
|
try:
|
||||||
|
retry_strategy = Retry(
|
||||||
|
total=3,
|
||||||
|
status_forcelist=[429, 500, 502, 503, 504, 408],
|
||||||
|
backoff_factor=1,
|
||||||
|
allowed_methods=["HEAD", "GET", "OPTIONS"]
|
||||||
|
)
|
||||||
|
|
||||||
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
||||||
|
self._session.mount("http://", adapter)
|
||||||
|
self._session.mount("https://", adapter)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"重试策略配置失败: {str(e)}")
|
||||||
|
|
||||||
|
def _make_request(self, url: str, params: dict, headers: dict, use_proxy: bool = True) -> requests.Response:
|
||||||
|
"""发起HTTP请求,支持代理重试机制"""
|
||||||
|
session = self._session
|
||||||
|
|
||||||
|
# 如果不使用代理,创建临时session
|
||||||
|
if not use_proxy:
|
||||||
|
session = requests.Session()
|
||||||
|
session.headers.update(self._session.headers)
|
||||||
|
session.proxies = {'http': None, 'https': None}
|
||||||
|
|
||||||
|
timeout = 5 if use_proxy else (10, 30)
|
||||||
|
|
||||||
|
return session.get(url, params=params, headers=headers, timeout=timeout)
|
||||||
|
|
||||||
|
def search(self, query: str, page: int = 1) -> Optional[Dict]:
|
||||||
|
"""搜索媒体资源"""
|
||||||
|
try:
|
||||||
|
headers = {'X-APP-ID': self._app_id}
|
||||||
|
|
||||||
|
if self._api_key:
|
||||||
|
headers['X-API-KEY'] = self._api_key
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'query': query,
|
||||||
|
'page': page
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"搜索请求: {query}")
|
||||||
|
logger.debug(f"请求参数: {params}")
|
||||||
|
logger.debug(f"请求头: X-APP-ID={self._app_id}, X-API-KEY={'已设置' if self._api_key else '未设置'}")
|
||||||
|
|
||||||
|
url = f"{self._base_url}/search"
|
||||||
|
|
||||||
|
# 首先尝试使用系统代理
|
||||||
|
try:
|
||||||
|
logger.debug("尝试使用系统代理访问Nullbr API")
|
||||||
|
response = self._make_request(url, params, headers, use_proxy=True)
|
||||||
|
logger.info(f"使用系统代理请求成功,状态码: {response.status_code}")
|
||||||
|
|
||||||
|
except (requests.exceptions.Timeout, requests.exceptions.ConnectTimeout,
|
||||||
|
requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError) as e:
|
||||||
|
logger.warning(f"系统代理访问失败: {str(e)},尝试直连")
|
||||||
|
try:
|
||||||
|
response = self._make_request(url, params, headers, use_proxy=False)
|
||||||
|
logger.info(f"直连请求成功,状态码: {response.status_code}")
|
||||||
|
|
||||||
|
except Exception as direct_error:
|
||||||
|
logger.error(f"直连也失败: {str(direct_error)}")
|
||||||
|
raise direct_error
|
||||||
|
|
||||||
|
# 检查响应状态
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# 解析JSON响应
|
||||||
|
result = response.json()
|
||||||
|
logger.info(f"搜索完成,找到 {len(result.get('items', []))} 个结果")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if response.status_code == 401:
|
||||||
|
logger.error("API认证失败,请检查APP_ID和API_KEY")
|
||||||
|
elif response.status_code == 403:
|
||||||
|
logger.error("API访问被禁止,请检查权限")
|
||||||
|
elif response.status_code == 429:
|
||||||
|
logger.error("API请求频率超限,请稍后再试")
|
||||||
|
else:
|
||||||
|
logger.error(f"HTTP错误: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"网络请求失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"搜索异常: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_movie_resources(self, tmdbid: int, resource_type: str = "115") -> Optional[Dict]:
|
||||||
|
"""获取电影资源链接"""
|
||||||
|
if not self._api_key:
|
||||||
|
logger.warning("获取资源链接需要API_KEY")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
headers = {'X-APP-ID': self._app_id, 'X-API-KEY': self._api_key}
|
||||||
|
url = f"{self._base_url}/movie/{tmdbid}/{resource_type}"
|
||||||
|
|
||||||
|
# 首先尝试使用系统代理
|
||||||
|
try:
|
||||||
|
logger.debug("尝试使用系统代理获取电影资源")
|
||||||
|
response = self._make_request(url, {}, headers, use_proxy=True)
|
||||||
|
logger.info(f"使用系统代理请求成功,状态码: {response.status_code}")
|
||||||
|
|
||||||
|
except (requests.exceptions.Timeout, requests.exceptions.ConnectTimeout,
|
||||||
|
requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError) as e:
|
||||||
|
logger.warning(f"系统代理访问失败: {str(e)},尝试直连")
|
||||||
|
try:
|
||||||
|
response = self._make_request(url, {}, headers, use_proxy=False)
|
||||||
|
logger.info(f"直连请求成功,状态码: {response.status_code}")
|
||||||
|
|
||||||
|
except Exception as direct_error:
|
||||||
|
logger.error(f"直连也失败: {str(direct_error)}")
|
||||||
|
raise direct_error
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
logger.info(f"获取电影资源成功: TMDB={tmdbid}, 类型={resource_type}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if response.status_code == 404:
|
||||||
|
logger.warning(f"未找到电影资源: TMDB={tmdbid}, 类型={resource_type}")
|
||||||
|
else:
|
||||||
|
logger.error(f"获取电影资源失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取电影资源异常: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_tv_resources(self, tmdbid: int, resource_type: str = "115") -> Optional[Dict]:
|
||||||
|
"""获取剧集资源链接"""
|
||||||
|
if not self._api_key:
|
||||||
|
logger.warning("获取资源链接需要API_KEY")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
headers = {'X-APP-ID': self._app_id, 'X-API-KEY': self._api_key}
|
||||||
|
url = f"{self._base_url}/tv/{tmdbid}/{resource_type}"
|
||||||
|
|
||||||
|
# 首先尝试使用系统代理
|
||||||
|
try:
|
||||||
|
logger.debug("尝试使用系统代理获取剧集资源")
|
||||||
|
response = self._make_request(url, {}, headers, use_proxy=True)
|
||||||
|
logger.info(f"使用系统代理请求成功,状态码: {response.status_code}")
|
||||||
|
|
||||||
|
except (requests.exceptions.Timeout, requests.exceptions.ConnectTimeout,
|
||||||
|
requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError) as e:
|
||||||
|
logger.warning(f"系统代理访问失败: {str(e)},尝试直连")
|
||||||
|
try:
|
||||||
|
response = self._make_request(url, {}, headers, use_proxy=False)
|
||||||
|
logger.info(f"直连请求成功,状态码: {response.status_code}")
|
||||||
|
|
||||||
|
except Exception as direct_error:
|
||||||
|
logger.error(f"直连也失败: {str(direct_error)}")
|
||||||
|
raise direct_error
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
logger.info(f"获取剧集资源成功: TMDB={tmdbid}, 类型={resource_type}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if response.status_code == 404:
|
||||||
|
logger.warning(f"未找到剧集资源: TMDB={tmdbid}, 类型={resource_type}")
|
||||||
|
else:
|
||||||
|
logger.error(f"获取剧集资源失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取剧集资源异常: {str(e)}")
|
||||||
|
return None
|
||||||
Loading…
x
Reference in New Issue
Block a user