Files
MoviePilot/app/modules/indexer/spider/torrentleech.py
T

174 lines
6.3 KiB
Python

from typing import List, Tuple, Optional
from urllib.parse import quote
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.types import MediaType
from app.adapters.network.http import RequestUtils, AsyncRequestUtils
from app.foundation import temporal as time_tools
from app.foundation import text as text_tools
class TorrentLeech:
"""TorrentLeech JSON 搜索接口索引器。"""
_indexer = None
_proxy = None
_size = 100
_searchurl = "%storrents/browse/list/query/%s"
_browseurl = "%storrents/browse/list/page/%s"
_downloadurl = "%sdownload/%s/%s"
_pageurl = "%storrent/%s"
_timeout = 15
@classmethod
def get_search_page_size(cls, keyword: Optional[str] = None) -> Optional[int]:
"""
获取搜索接口单页容量;关键词搜索 URL 当前没有可靠页码入口。
"""
return None if keyword else cls._size
def __init__(self, indexer: dict):
"""初始化站点认证信息和媒体分类配置。"""
self._indexer = indexer
if indexer.get('proxy'):
self._proxy = settings.PROXY
self._timeout = indexer.get('timeout') or 15
def __category_ids(self, mtype: MediaType = None) -> List[str]:
"""读取资源包中与请求媒体类型对应的 TorrentLeech 分类 ID。"""
category_key = {
MediaType.MOVIE: "movie",
MediaType.TV: "tv",
MediaType.MUSIC: "music",
}.get(mtype)
if not category_key:
return []
return [
str(item.get("id"))
for item in ((self._indexer.get("category") or {}).get(category_key) or [])
if isinstance(item, dict) and item.get("id") is not None
]
def __get_search_url(self, keyword: str, mtype: MediaType = None) -> str:
"""按媒体类型构造 TorrentLeech 搜索接口地址。"""
domain = self._indexer.get('domain')
encoded_keyword = quote(keyword)
category_ids = self.__category_ids(mtype)
if category_ids:
return (
f"{domain}torrents/browse/list/categories/{','.join(category_ids)}"
f"/exact/1/query/{encoded_keyword}"
)
return self._searchurl % (domain, encoded_keyword)
def __parse_result(self, results: List[dict], mtype: MediaType = None) -> List[dict]:
"""
解析搜索结果
"""
torrents = []
if not results:
return torrents
requested_category_ids = set(self.__category_ids(mtype))
for result in results:
torrent = {
'title': result.get('name'),
'enclosure': self._downloadurl % (self._indexer.get('domain'),
result.get('fid'),
result.get('filename')),
'pubdate': time_tools.format_timestamp(result.get('addedTimestamp')),
'size': result.get('size'),
'seeders': result.get('seeders'),
'peers': result.get('leechers'),
'grabs': result.get('completed'),
'downloadvolumefactor': result.get('download_multiplier'),
'uploadvolumefactor': 1,
'page_url': self._pageurl % (self._indexer.get('domain'), result.get('fid')),
'imdbid': result.get('imdbID')
}
if requested_category_ids:
torrent['category'] = (
mtype.value
if str(result.get('categoryID')) in requested_category_ids
else MediaType.UNKNOWN.value
)
torrents.append(torrent)
return torrents
def search(
self,
keyword: str,
mtype: MediaType = None,
page: Optional[int] = 0,
) -> Tuple[bool, List[dict]]:
"""
搜索种子
"""
if text_tools.contains_chinese(keyword):
# 不支持中文
return True, []
if keyword:
url = self.__get_search_url(keyword, mtype)
else:
url = self._browseurl % (self._indexer.get('domain'), int(page) + 1)
res = RequestUtils(
headers={
"Content-Type": "application/json; charset=utf-8",
"User-Agent": f"{self._indexer.get('ua')}",
},
cookies=self._indexer.get('cookie'),
proxies=self._proxy,
timeout=self._timeout
).get_res(url)
if res and res.status_code == 200:
results = res.json().get('torrentList') or []
return False, self.__parse_result(results, mtype)
elif res is not None:
logger.warn(f"{self._indexer.get('name')} 搜索失败,错误码:{res.status_code}")
return True, []
else:
logger.warn(f"{self._indexer.get('name')} 搜索失败,无法连接 {self._indexer.get('domain')}")
return True, []
async def async_search(
self,
keyword: str,
mtype: MediaType = None,
page: Optional[int] = 0,
) -> Tuple[bool, List[dict]]:
"""
异步搜索种子
"""
if text_tools.contains_chinese(keyword):
# 不支持中文
return True, []
if keyword:
url = self.__get_search_url(keyword, mtype)
else:
url = self._browseurl % (self._indexer.get('domain'), int(page) + 1)
res = await AsyncRequestUtils(
headers={
"Content-Type": "application/json; charset=utf-8",
"User-Agent": f"{self._indexer.get('ua')}",
},
cookies=self._indexer.get('cookie'),
proxies=self._proxy,
timeout=self._timeout
).get_res(url)
if res and res.status_code == 200:
results = res.json().get('torrentList') or []
return False, self.__parse_result(results, mtype)
elif res is not None:
logger.warn(f"{self._indexer.get('name')} 搜索失败,错误码:{res.status_code}")
return True, []
else:
logger.warn(f"{self._indexer.get('name')} 搜索失败,无法连接 {self._indexer.get('domain')}")
return True, []