feat 支持新版本mteam
This commit is contained in:
parent
fd9eef2089
commit
e298a1a8a0
@ -1,12 +1,10 @@
|
|||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.core.plugin import PluginManager
|
from app.core.plugin import PluginManager
|
||||||
from app.core.security import verify_token
|
from app.core.security import verify_token
|
||||||
from app.db import get_db
|
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
from app.schemas.types import SystemConfigKey
|
from app.schemas.types import SystemConfigKey
|
||||||
|
|
||||||
|
@ -684,7 +684,7 @@ def arr_serie(apikey: str, tid: int, db: Session = Depends(get_db)) -> Any:
|
|||||||
"monitored": True,
|
"monitored": True,
|
||||||
}],
|
}],
|
||||||
year=subscribe.year,
|
year=subscribe.year,
|
||||||
remotePoster=subscribe.image,
|
remotePoster=subscribe.poster,
|
||||||
tmdbId=subscribe.tmdbid,
|
tmdbId=subscribe.tmdbid,
|
||||||
tvdbId=subscribe.tvdbid,
|
tvdbId=subscribe.tvdbid,
|
||||||
imdbId=subscribe.imdbid,
|
imdbId=subscribe.imdbid,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import base64
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional, Tuple, Set, Dict, Union
|
from typing import List, Optional, Tuple, Set, Dict, Union
|
||||||
@ -15,6 +17,7 @@ from app.helper.torrent import TorrentHelper
|
|||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.schemas import ExistMediaInfo, NotExistMediaInfo, DownloadingTorrent, Notification
|
from app.schemas import ExistMediaInfo, NotExistMediaInfo, DownloadingTorrent, Notification
|
||||||
from app.schemas.types import MediaType, TorrentStatus, EventType, MessageChannel, NotificationType
|
from app.schemas.types import MediaType, TorrentStatus, EventType, MessageChannel, NotificationType
|
||||||
|
from app.utils.http import RequestUtils
|
||||||
from app.utils.string import StringUtils
|
from app.utils.string import StringUtils
|
||||||
|
|
||||||
|
|
||||||
@ -79,8 +82,68 @@ class DownloadChain(ChainBase):
|
|||||||
下载种子文件,如果是磁力链,会返回磁力链接本身
|
下载种子文件,如果是磁力链,会返回磁力链接本身
|
||||||
:return: 种子路径,种子目录名,种子文件清单
|
:return: 种子路径,种子目录名,种子文件清单
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __get_redict_url(url: str, ua: str = None, cookie: str = None) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
获取下载链接, url格式:[base64]url
|
||||||
|
"""
|
||||||
|
# 获取[]中的内容
|
||||||
|
m = re.search(r"\[(.*)](.*)", url)
|
||||||
|
if m:
|
||||||
|
# 参数
|
||||||
|
base64_str = m.group(1)
|
||||||
|
# URL
|
||||||
|
url = m.group(2)
|
||||||
|
if not base64_str:
|
||||||
|
return url
|
||||||
|
# 解码参数
|
||||||
|
req_str = base64.b64decode(base64_str.encode('utf-8')).decode('utf-8')
|
||||||
|
req_params: Dict[str, dict] = json.loads(req_str)
|
||||||
|
if req_params.get('method') == 'get':
|
||||||
|
# GET请求
|
||||||
|
res = RequestUtils(
|
||||||
|
ua=ua,
|
||||||
|
cookies=cookie
|
||||||
|
).get_res(url, params=req_params.get('params'))
|
||||||
|
else:
|
||||||
|
# POST请求
|
||||||
|
res = RequestUtils(
|
||||||
|
ua=ua,
|
||||||
|
cookies=cookie
|
||||||
|
).post_res(url, params=req_params.get('params'))
|
||||||
|
if not res:
|
||||||
|
return None
|
||||||
|
if not req_params.get('result'):
|
||||||
|
return res.text
|
||||||
|
else:
|
||||||
|
data = res.json()
|
||||||
|
for key in str(req_params.get('result')).split("."):
|
||||||
|
data = data.get(key)
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
logger.info(f"获取到下载地址:{data}")
|
||||||
|
return data
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 获取下载链接
|
||||||
|
if not torrent.enclosure:
|
||||||
|
return None, "", []
|
||||||
|
if torrent.enclosure.startswith("magnet:"):
|
||||||
|
return torrent.enclosure, "", []
|
||||||
|
|
||||||
|
if torrent.enclosure.startswith("["):
|
||||||
|
# 需要解码获取下载地址
|
||||||
|
torrent_url = __get_redict_url(url=torrent.enclosure,
|
||||||
|
ua=torrent.site_ua,
|
||||||
|
cookie=torrent.site_cookie)
|
||||||
|
else:
|
||||||
|
torrent_url = torrent.enclosure
|
||||||
|
if not torrent_url:
|
||||||
|
logger.error(f"{torrent.title} 无法获取下载地址:{torrent.enclosure}!")
|
||||||
|
return None, "", []
|
||||||
|
# 下载种子文件
|
||||||
torrent_file, content, download_folder, files, error_msg = self.torrent.download_torrent(
|
torrent_file, content, download_folder, files, error_msg = self.torrent.download_torrent(
|
||||||
url=torrent.enclosure,
|
url=torrent_url,
|
||||||
cookie=torrent.site_cookie,
|
cookie=torrent.site_cookie,
|
||||||
ua=torrent.site_ua,
|
ua=torrent.site_ua,
|
||||||
proxy=torrent.site_proxy)
|
proxy=torrent.site_proxy)
|
||||||
@ -90,7 +153,7 @@ class DownloadChain(ChainBase):
|
|||||||
return content, "", []
|
return content, "", []
|
||||||
|
|
||||||
if not torrent_file:
|
if not torrent_file:
|
||||||
logger.error(f"下载种子文件失败:{torrent.title} - {torrent.enclosure}")
|
logger.error(f"下载种子文件失败:{torrent.title} - {torrent_url}")
|
||||||
self.post_message(Notification(
|
self.post_message(Notification(
|
||||||
channel=channel,
|
channel=channel,
|
||||||
mtype=NotificationType.Manual,
|
mtype=NotificationType.Manual,
|
||||||
@ -122,7 +185,9 @@ class DownloadChain(ChainBase):
|
|||||||
_folder_name = ""
|
_folder_name = ""
|
||||||
if not torrent_file:
|
if not torrent_file:
|
||||||
# 下载种子文件,得到的可能是文件也可能是磁力链
|
# 下载种子文件,得到的可能是文件也可能是磁力链
|
||||||
content, _folder_name, _file_list = self.download_torrent(_torrent, userid=userid)
|
content, _folder_name, _file_list = self.download_torrent(_torrent,
|
||||||
|
channel=channel,
|
||||||
|
userid=userid)
|
||||||
if not content:
|
if not content:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -253,12 +318,14 @@ class DownloadChain(ChainBase):
|
|||||||
contexts: List[Context],
|
contexts: List[Context],
|
||||||
no_exists: Dict[int, Dict[int, NotExistMediaInfo]] = None,
|
no_exists: Dict[int, Dict[int, NotExistMediaInfo]] = None,
|
||||||
save_path: str = None,
|
save_path: str = None,
|
||||||
|
channel: str = None,
|
||||||
userid: str = None) -> Tuple[List[Context], Dict[int, Dict[int, NotExistMediaInfo]]]:
|
userid: str = None) -> Tuple[List[Context], Dict[int, Dict[int, NotExistMediaInfo]]]:
|
||||||
"""
|
"""
|
||||||
根据缺失数据,自动种子列表中组合择优下载
|
根据缺失数据,自动种子列表中组合择优下载
|
||||||
:param contexts: 资源上下文列表
|
:param contexts: 资源上下文列表
|
||||||
:param no_exists: 缺失的剧集信息
|
:param no_exists: 缺失的剧集信息
|
||||||
:param save_path: 保存路径
|
:param save_path: 保存路径
|
||||||
|
:param channel: 通知渠道
|
||||||
:param userid: 用户ID
|
:param userid: 用户ID
|
||||||
:return: 已经下载的资源列表、剩余未下载到的剧集 no_exists[tmdb_id] = {season: NotExistMediaInfo}
|
:return: 已经下载的资源列表、剩余未下载到的剧集 no_exists[tmdb_id] = {season: NotExistMediaInfo}
|
||||||
"""
|
"""
|
||||||
@ -323,7 +390,8 @@ class DownloadChain(ChainBase):
|
|||||||
# 如果是电影,直接下载
|
# 如果是电影,直接下载
|
||||||
for context in contexts:
|
for context in contexts:
|
||||||
if context.media_info.type == MediaType.MOVIE:
|
if context.media_info.type == MediaType.MOVIE:
|
||||||
if self.download_single(context, save_path=save_path, userid=userid):
|
if self.download_single(context, save_path=save_path,
|
||||||
|
channel=channel, userid=userid):
|
||||||
# 下载成功
|
# 下载成功
|
||||||
downloaded_list.append(context)
|
downloaded_list.append(context)
|
||||||
|
|
||||||
@ -390,11 +458,13 @@ class DownloadChain(ChainBase):
|
|||||||
context=context,
|
context=context,
|
||||||
torrent_file=content if isinstance(content, Path) else None,
|
torrent_file=content if isinstance(content, Path) else None,
|
||||||
save_path=save_path,
|
save_path=save_path,
|
||||||
|
channel=channel,
|
||||||
userid=userid
|
userid=userid
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 下载
|
# 下载
|
||||||
download_id = self.download_single(context, save_path=save_path, userid=userid)
|
download_id = self.download_single(context, save_path=save_path,
|
||||||
|
channel=channel, userid=userid)
|
||||||
|
|
||||||
if download_id:
|
if download_id:
|
||||||
# 下载成功
|
# 下载成功
|
||||||
@ -452,7 +522,8 @@ class DownloadChain(ChainBase):
|
|||||||
# 为需要集的子集则下载
|
# 为需要集的子集则下载
|
||||||
if torrent_episodes.issubset(set(need_episodes)):
|
if torrent_episodes.issubset(set(need_episodes)):
|
||||||
# 下载
|
# 下载
|
||||||
download_id = self.download_single(context, save_path=save_path, userid=userid)
|
download_id = self.download_single(context, save_path=save_path,
|
||||||
|
channel=channel, userid=userid)
|
||||||
if download_id:
|
if download_id:
|
||||||
# 下载成功
|
# 下载成功
|
||||||
downloaded_list.append(context)
|
downloaded_list.append(context)
|
||||||
@ -508,7 +579,7 @@ class DownloadChain(ChainBase):
|
|||||||
and len(meta.season_list) == 1 \
|
and len(meta.season_list) == 1 \
|
||||||
and meta.season_list[0] == need_season:
|
and meta.season_list[0] == need_season:
|
||||||
# 检查种子看是否有需要的集
|
# 检查种子看是否有需要的集
|
||||||
content, _, torrent_files = self.download_torrent(torrent, userid=userid)
|
content, _, torrent_files = self.download_torrent(torrent)
|
||||||
if not content:
|
if not content:
|
||||||
continue
|
continue
|
||||||
if isinstance(content, str):
|
if isinstance(content, str):
|
||||||
@ -529,6 +600,7 @@ class DownloadChain(ChainBase):
|
|||||||
torrent_file=content if isinstance(content, Path) else None,
|
torrent_file=content if isinstance(content, Path) else None,
|
||||||
episodes=selected_episodes,
|
episodes=selected_episodes,
|
||||||
save_path=save_path,
|
save_path=save_path,
|
||||||
|
channel=channel,
|
||||||
userid=userid
|
userid=userid
|
||||||
)
|
)
|
||||||
if not download_id:
|
if not download_id:
|
||||||
|
@ -348,6 +348,7 @@ class MessageChain(ChainBase):
|
|||||||
# 批量下载
|
# 批量下载
|
||||||
downloads, lefts = self.downloadchain.batch_download(contexts=cache_list,
|
downloads, lefts = self.downloadchain.batch_download(contexts=cache_list,
|
||||||
no_exists=no_exists,
|
no_exists=no_exists,
|
||||||
|
channel=channel,
|
||||||
userid=userid)
|
userid=userid)
|
||||||
if downloads and not lefts:
|
if downloads and not lefts:
|
||||||
# 全部下载完成
|
# 全部下载完成
|
||||||
|
@ -23,14 +23,17 @@ class CookieHelper:
|
|||||||
"password": [
|
"password": [
|
||||||
'//input[@name="password"]',
|
'//input[@name="password"]',
|
||||||
'//input[@id="form_item_password"]',
|
'//input[@id="form_item_password"]',
|
||||||
'//input[@id="password"]'
|
'//input[@id="password"]',
|
||||||
|
'//input[@type="password"]'
|
||||||
],
|
],
|
||||||
"captcha": [
|
"captcha": [
|
||||||
'//input[@name="imagestring"]',
|
'//input[@name="imagestring"]',
|
||||||
'//input[@name="captcha"]',
|
'//input[@name="captcha"]',
|
||||||
'//input[@id="form_item_captcha"]'
|
'//input[@id="form_item_captcha"]',
|
||||||
|
'//input[@placeholder="驗證碼"]'
|
||||||
],
|
],
|
||||||
"captcha_img": [
|
"captcha_img": [
|
||||||
|
'//img[@alt="captcha"]/@src',
|
||||||
'//img[@alt="CAPTCHA"]/@src',
|
'//img[@alt="CAPTCHA"]/@src',
|
||||||
'//img[@alt="SECURITY CODE"]/@src',
|
'//img[@alt="SECURITY CODE"]/@src',
|
||||||
'//img[@id="LAY-user-get-vercode"]/@src',
|
'//img[@id="LAY-user-get-vercode"]/@src',
|
||||||
|
@ -6,6 +6,7 @@ from ruamel.yaml import CommentedMap
|
|||||||
from app.core.context import MediaInfo, TorrentInfo
|
from app.core.context import MediaInfo, TorrentInfo
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules import _ModuleBase
|
from app.modules import _ModuleBase
|
||||||
|
from app.modules.indexer.mtorrent import MTorrentSpider
|
||||||
from app.modules.indexer.spider import TorrentSpider
|
from app.modules.indexer.spider import TorrentSpider
|
||||||
from app.modules.indexer.tnode import TNodeSpider
|
from app.modules.indexer.tnode import TNodeSpider
|
||||||
from app.modules.indexer.torrentleech import TorrentLeech
|
from app.modules.indexer.torrentleech import TorrentLeech
|
||||||
@ -74,6 +75,12 @@ class IndexerModule(_ModuleBase):
|
|||||||
keyword=search_word,
|
keyword=search_word,
|
||||||
page=page
|
page=page
|
||||||
)
|
)
|
||||||
|
elif site.get('parser') == "mTorrent":
|
||||||
|
error_flag, result_array = MTorrentSpider(site).search(
|
||||||
|
keyword=search_word,
|
||||||
|
mtype=mediainfo.type if mediainfo else None,
|
||||||
|
page=page
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
error_flag, result_array = self.__spider_search(
|
error_flag, result_array = self.__spider_search(
|
||||||
keyword=search_word,
|
keyword=search_word,
|
||||||
|
144
app/modules/indexer/mtorrent.py
Normal file
144
app/modules/indexer/mtorrent.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from typing import Tuple, List
|
||||||
|
|
||||||
|
from ruamel.yaml import CommentedMap
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
|
from app.log import logger
|
||||||
|
from app.schemas import MediaType
|
||||||
|
from app.utils.http import RequestUtils
|
||||||
|
from app.utils.string import StringUtils
|
||||||
|
|
||||||
|
|
||||||
|
class MTorrentSpider:
|
||||||
|
_indexerid = None
|
||||||
|
_domain = None
|
||||||
|
_name = ""
|
||||||
|
_proxy = None
|
||||||
|
_cookie = None
|
||||||
|
_ua = None
|
||||||
|
_size = 100
|
||||||
|
_searchurl = "%sapi/torrent/search"
|
||||||
|
_downloadurl = "%sapi/torrent/genDlToken"
|
||||||
|
_pageurl = "%sdetail/%s"
|
||||||
|
|
||||||
|
# 电影分类
|
||||||
|
_movie_category = ['401', '419', '420', '421', '439', '405', '404']
|
||||||
|
_tv_category = ['403', '402', '435', '438', '404', '405']
|
||||||
|
|
||||||
|
# 标签
|
||||||
|
_labels = {
|
||||||
|
0: "",
|
||||||
|
4: "中字",
|
||||||
|
6: "国配",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, indexer: CommentedMap):
|
||||||
|
if indexer:
|
||||||
|
self._indexerid = indexer.get('id')
|
||||||
|
self._domain = indexer.get('domain')
|
||||||
|
self._searchurl = self._searchurl % self._domain
|
||||||
|
self._name = indexer.get('name')
|
||||||
|
if indexer.get('proxy'):
|
||||||
|
self._proxy = settings.PROXY
|
||||||
|
self._cookie = indexer.get('cookie')
|
||||||
|
self._ua = indexer.get('ua')
|
||||||
|
|
||||||
|
def search(self, keyword: str, mtype: MediaType = None, page: int = 0) -> Tuple[bool, List[dict]]:
|
||||||
|
if not mtype:
|
||||||
|
categories = []
|
||||||
|
elif mtype == MediaType.TV:
|
||||||
|
categories = self._tv_category
|
||||||
|
else:
|
||||||
|
categories = self._movie_category
|
||||||
|
params = {
|
||||||
|
"keyword": keyword,
|
||||||
|
"categories": categories,
|
||||||
|
"pageNumber": int(page) + 1,
|
||||||
|
"pageSize": self._size,
|
||||||
|
"visible": 1
|
||||||
|
}
|
||||||
|
res = RequestUtils(
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": f"{self._ua}"
|
||||||
|
},
|
||||||
|
cookies=self._cookie,
|
||||||
|
proxies=self._proxy,
|
||||||
|
referer=f"{self._domain}browse",
|
||||||
|
timeout=30
|
||||||
|
).post_res(url=self._searchurl, json=params)
|
||||||
|
torrents = []
|
||||||
|
if res and res.status_code == 200:
|
||||||
|
results = res.json().get('data', {}).get("data") or []
|
||||||
|
for result in results:
|
||||||
|
torrent = {
|
||||||
|
'title': result.get('name'),
|
||||||
|
'description': result.get('smallDescr'),
|
||||||
|
'enclosure': self.__get_download_url(result.get('id')),
|
||||||
|
'pubdate': StringUtils.format_timestamp(result.get('createdDate')),
|
||||||
|
'size': result.get('size'),
|
||||||
|
'seeders': result.get('status', {}).get("seeders"),
|
||||||
|
'peers': result.get('status', {}).get("leechers"),
|
||||||
|
'grabs': result.get('status', {}).get("timesCompleted"),
|
||||||
|
'downloadvolumefactor': self.__get_downloadvolumefactor(result.get('status', {}).get("discount")),
|
||||||
|
'uploadvolumefactor': self.__get_uploadvolumefactor(result.get('status', {}).get("discount")),
|
||||||
|
'page_url': self._pageurl % (self._domain, result.get('id')),
|
||||||
|
'imdbid': self.__find_imdbid(result.get('imdb')),
|
||||||
|
'labels': [self._labels.get(result.get('labels') or 0)] if result.get('labels') else []
|
||||||
|
}
|
||||||
|
torrents.append(torrent)
|
||||||
|
elif res is not None:
|
||||||
|
logger.warn(f"{self._name} 搜索失败,错误码:{res.status_code}")
|
||||||
|
return True, []
|
||||||
|
else:
|
||||||
|
logger.warn(f"{self._name} 搜索失败,无法连接 {self._domain}")
|
||||||
|
return True, []
|
||||||
|
return False, torrents
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __find_imdbid(imdb: str) -> str:
|
||||||
|
if imdb:
|
||||||
|
m = re.search(r"tt\d+", imdb)
|
||||||
|
if m:
|
||||||
|
return m.group(0)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __get_downloadvolumefactor(discount: str) -> float:
|
||||||
|
discount_dict = {
|
||||||
|
"FREE": 0,
|
||||||
|
"PERCENT_50": 0.5,
|
||||||
|
"PERCENT_70": 0.3,
|
||||||
|
"_2X_FREE": 0,
|
||||||
|
"_2X_PERCENT_50": 0.5
|
||||||
|
}
|
||||||
|
if discount:
|
||||||
|
return discount_dict.get(discount, 1)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __get_uploadvolumefactor(discount: str) -> float:
|
||||||
|
uploadvolumefactor_dict = {
|
||||||
|
"_2X": 2.0,
|
||||||
|
"_2X_FREE": 2.0,
|
||||||
|
"_2X_PERCENT_50": 2.0
|
||||||
|
}
|
||||||
|
if discount:
|
||||||
|
return uploadvolumefactor_dict.get(discount, 1)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def __get_download_url(self, torrent_id: str) -> str:
|
||||||
|
url = self._downloadurl % self._domain
|
||||||
|
params = {
|
||||||
|
'method': 'post',
|
||||||
|
'params': {
|
||||||
|
'id': torrent_id
|
||||||
|
},
|
||||||
|
'result': 'data'
|
||||||
|
}
|
||||||
|
# base64编码
|
||||||
|
base64_str = base64.b64encode(json.dumps(params).encode('utf-8')).decode('utf-8')
|
||||||
|
return f"[{base64_str}]{url}"
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user