mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-09 07:42:40 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e077cd64d | ||
|
|
e3f957a59b | ||
|
|
55c62a3ab5 | ||
|
|
22e7eef1bd | ||
|
|
d6524907f3 | ||
|
|
357db334cd | ||
|
|
f8bed3909b | ||
|
|
182bbdde91 | ||
|
|
2c70f990c2 | ||
|
|
0b01a6aa91 | ||
|
|
e557dffbc6 | ||
|
|
7f33b0b1b8 | ||
|
|
41ddf77a5b | ||
|
|
8c657ce41d | ||
|
|
3ff3b9ed4a | ||
|
|
ef43419ecd | ||
|
|
2ca375c214 | ||
|
|
cbd45c1d0f | ||
|
|
2592ea3464 | ||
|
|
73ac97cd96 | ||
|
|
e014663e97 |
@@ -82,7 +82,7 @@ def exists(media_in: schemas.MediaInfo,
|
||||
mediainfo.from_dict(media_in.model_dump())
|
||||
existsinfo: schemas.ExistMediaInfo = MediaServerChain().media_exists(mediainfo=mediainfo)
|
||||
if not existsinfo:
|
||||
return []
|
||||
return {}
|
||||
if media_in.season:
|
||||
return {
|
||||
media_in.season: existsinfo.seasons.get(media_in.season) or []
|
||||
|
||||
@@ -149,7 +149,9 @@ def get_global_setting(token: str):
|
||||
info.update({
|
||||
"USER_UNIQUE_ID": SubscribeHelper().get_user_uuid(),
|
||||
"SUBSCRIBE_SHARE_MANAGE": share_admin,
|
||||
"WORKFLOW_SHARE_MANAGE": share_admin
|
||||
"WORKFLOW_SHARE_MANAGE": share_admin,
|
||||
"FRONTEND_VERSION": SystemChain.get_frontend_version(),
|
||||
"BACKEND_VERSION": APP_VERSION
|
||||
})
|
||||
return schemas.Response(success=True,
|
||||
data=info)
|
||||
|
||||
@@ -454,7 +454,6 @@ class Base:
|
||||
|
||||
@db_update
|
||||
def update(self, db: Session, payload: dict):
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
for key, value in payload.items():
|
||||
setattr(self, key, value)
|
||||
if inspect(self).detached:
|
||||
@@ -462,7 +461,6 @@ class Base:
|
||||
|
||||
@async_db_update
|
||||
async def async_update(self, db: AsyncSession, payload: dict):
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
for key, value in payload.items():
|
||||
setattr(self, key, value)
|
||||
if inspect(self).detached:
|
||||
|
||||
@@ -23,6 +23,7 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
|
||||
if not Discord:
|
||||
logger.error("Discord 依赖未就绪(需要安装 discord.py==2.6.4),模块未启动")
|
||||
return
|
||||
self.stop()
|
||||
super().init_service(service_name=Discord.__name__.lower(),
|
||||
service_type=Discord)
|
||||
self._channel = MessageChannel.Discord
|
||||
@@ -154,7 +155,8 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
|
||||
image=message.image, userid=userid, link=message.link,
|
||||
buttons=message.buttons,
|
||||
original_message_id=message.original_message_id,
|
||||
original_chat_id=message.original_chat_id)
|
||||
original_chat_id=message.original_chat_id,
|
||||
mtype=message.mtype)
|
||||
|
||||
def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> None:
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import re
|
||||
import threading
|
||||
from typing import Optional, List, Dict, Any, Union
|
||||
from typing import Optional, List, Dict, Any, Tuple, Union
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
@@ -11,8 +11,18 @@ from app.core.config import settings
|
||||
from app.core.context import MediaInfo, Context
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.log import logger
|
||||
from app.schemas.types import NotificationType
|
||||
from app.utils.string import StringUtils
|
||||
|
||||
# Discord embed 字段解析白名单
|
||||
# 只有这些消息类型会使用复杂的字段解析逻辑
|
||||
PARSE_FIELD_TYPES = {
|
||||
NotificationType.Download, # 资源下载
|
||||
NotificationType.Organize, # 整理入库
|
||||
NotificationType.Subscribe, # 订阅
|
||||
NotificationType.Manual, # 手动处理
|
||||
}
|
||||
|
||||
|
||||
class Discord:
|
||||
"""
|
||||
@@ -40,7 +50,10 @@ class Discord:
|
||||
intents.messages = True
|
||||
intents.guilds = True
|
||||
|
||||
self._client: Optional[discord.Client] = discord.Client(intents=intents)
|
||||
self._client: Optional[discord.Client] = discord.Client(
|
||||
intents=intents,
|
||||
proxy=settings.PROXY_HOST
|
||||
)
|
||||
self._tree: Optional[app_commands.CommandTree] = None
|
||||
self._loop: asyncio.AbstractEventLoop = asyncio.new_event_loop()
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
@@ -153,7 +166,8 @@ class Discord:
|
||||
userid: Optional[str] = None, link: Optional[str] = None,
|
||||
buttons: Optional[List[List[dict]]] = None,
|
||||
original_message_id: Optional[Union[int, str]] = None,
|
||||
original_chat_id: Optional[str] = None) -> Optional[bool]:
|
||||
original_chat_id: Optional[str] = None,
|
||||
mtype: Optional['NotificationType'] = None) -> Optional[bool]:
|
||||
if not self.get_state():
|
||||
return False
|
||||
if not title and not text:
|
||||
@@ -165,7 +179,8 @@ class Discord:
|
||||
self._send_message(title=title, text=text, image=image, userid=userid,
|
||||
link=link, buttons=buttons,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id),
|
||||
original_chat_id=original_chat_id,
|
||||
mtype=mtype),
|
||||
self._loop)
|
||||
return future.result(timeout=30)
|
||||
except Exception as err:
|
||||
@@ -237,13 +252,14 @@ class Discord:
|
||||
userid: Optional[str], link: Optional[str],
|
||||
buttons: Optional[List[List[dict]]],
|
||||
original_message_id: Optional[Union[int, str]],
|
||||
original_chat_id: Optional[str]) -> bool:
|
||||
original_chat_id: Optional[str],
|
||||
mtype: Optional['NotificationType'] = None) -> bool:
|
||||
channel = await self._resolve_channel(userid=userid, chat_id=original_chat_id)
|
||||
if not channel:
|
||||
logger.error("未找到可用的 Discord 频道或私聊")
|
||||
return False
|
||||
|
||||
embed = self._build_embed(title=title, text=text, image=image, link=link)
|
||||
embed = self._build_embed(title=title, text=text, image=image, link=link, mtype=mtype)
|
||||
view = self._build_view(buttons=buttons, link=link)
|
||||
content = None
|
||||
|
||||
@@ -315,21 +331,92 @@ class Discord:
|
||||
|
||||
@staticmethod
|
||||
def _build_embed(title: str, text: Optional[str], image: Optional[str],
|
||||
link: Optional[str]) -> discord.Embed:
|
||||
description = ""
|
||||
link: Optional[str], mtype: Optional['NotificationType'] = None) -> discord.Embed:
|
||||
fields: List[Dict[str, str]] = []
|
||||
desc_lines: List[str] = []
|
||||
should_parse_fields = mtype in PARSE_FIELD_TYPES if mtype else False
|
||||
def _collect_spans(s: str, left: str, right: str) -> List[Tuple[int, int]]:
|
||||
spans: List[Tuple[int, int]] = []
|
||||
start = 0
|
||||
while True:
|
||||
l_idx = s.find(left, start)
|
||||
if l_idx == -1:
|
||||
break
|
||||
r_idx = s.find(right, l_idx + 1)
|
||||
if r_idx == -1:
|
||||
break
|
||||
spans.append((l_idx, r_idx))
|
||||
start = r_idx + 1
|
||||
return spans
|
||||
|
||||
def _find_colon_index(s: str, m: re.Match) -> Optional[int]:
|
||||
segment = s[m.start():m.end()]
|
||||
for i, ch in enumerate(segment):
|
||||
if ch in (":", ":"):
|
||||
return m.start() + i
|
||||
return None
|
||||
|
||||
if text:
|
||||
for line in text.splitlines():
|
||||
if ":" in line:
|
||||
name, value = line.split(":", 1)
|
||||
fields.append({"name": name.strip(), "value": value.strip() or "-", "inline": False})
|
||||
else:
|
||||
description += f"{line}\n"
|
||||
description = description.strip()
|
||||
# 处理上游未反序列化的 "\n" 等转义换行,避免被当成普通字符
|
||||
if "\\n" in text or "\\r" in text:
|
||||
text = text.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\\r", "\n")
|
||||
if not should_parse_fields:
|
||||
desc_lines.append(text.strip())
|
||||
else:
|
||||
# 匹配形如 "字段:值" 的片段,字段名不允许包含常见分隔符;
|
||||
# 下一个字段需以顿号/逗号/分号等分隔开,且不能是 URL 协议开头,避免值里出现 URL 的":" 被误拆
|
||||
name_re = r"[A-Za-z0-9\u4e00-\u9fa5_\-&]+"
|
||||
pair_pattern = re.compile(
|
||||
rf"({name_re})[::](.*?)(?=(?:[,,。;;、]+\s*(?!https?://|ftp://|ftps://|magnet:){name_re}[::])|$)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
matches = list(pair_pattern.finditer(line))
|
||||
if matches:
|
||||
book_spans = _collect_spans(line, "《", "》") + _collect_spans(line, "【", "】")
|
||||
if book_spans:
|
||||
has_book_colon = False
|
||||
for m in matches:
|
||||
colon_idx = _find_colon_index(line, m)
|
||||
if colon_idx is not None and any(l < colon_idx < r for l, r in book_spans):
|
||||
has_book_colon = True
|
||||
break
|
||||
if has_book_colon:
|
||||
desc_lines.append(line)
|
||||
continue
|
||||
# 若整行只是 URL/时间等自然包含":"的内容,则不当作字段
|
||||
url_like_names = {"http", "https", "ftp", "ftps", "magnet"}
|
||||
if all(m.group(1).lower() in url_like_names or m.group(1).isdigit() for m in matches):
|
||||
desc_lines.append(line)
|
||||
continue
|
||||
last_end = 0
|
||||
for m in matches:
|
||||
# 追加匹配前的非空文本到描述
|
||||
prefix = line[last_end:m.start()].strip(" ,,;;。、")
|
||||
# 仅当前缀不全是分隔符/空白时才记录
|
||||
if prefix and prefix.strip(" ,,;;。、"):
|
||||
desc_lines.append(prefix)
|
||||
name = m.group(1).strip()
|
||||
value = m.group(2).strip(" ,,;;。、\t") or "-"
|
||||
if name:
|
||||
fields.append({"name": name, "value": value, "inline": False})
|
||||
last_end = m.end()
|
||||
# 匹配末尾后的文本
|
||||
suffix = line[last_end:].strip(" ,,;;。、")
|
||||
if suffix and suffix.strip(" ,,;;。、"):
|
||||
desc_lines.append(suffix)
|
||||
else:
|
||||
desc_lines.append(line)
|
||||
description = "\n".join(desc_lines).strip()
|
||||
if not description and not fields and text:
|
||||
description = text.strip()
|
||||
embed = discord.Embed(
|
||||
title=title,
|
||||
url=link or "https://github.com/jxxghp/MoviePilot",
|
||||
description=description if description else (text or None),
|
||||
description=description if description else None,
|
||||
color=0xE67E22
|
||||
)
|
||||
for field in fields:
|
||||
|
||||
@@ -2,6 +2,7 @@ import base64
|
||||
import json
|
||||
import re
|
||||
from typing import Tuple, List, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from app.core.config import settings
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
@@ -25,6 +26,9 @@ class MTorrentSpider:
|
||||
_size = 100
|
||||
_searchurl = "https://api.%s/api/torrent/search"
|
||||
_downloadurl = "https://api.%s/api/torrent/genDlToken"
|
||||
_subtitle_list_url = "https://api.%s/api/subtitle/list"
|
||||
_subtitle_genlink_url = "https://api.%s/api/subtitle/genlink"
|
||||
_subtitle_download_url ="https://api.%s/api/subtitle/dlV2?credential=%s"
|
||||
_pageurl = "%sdetail/%s"
|
||||
_timeout = 15
|
||||
|
||||
@@ -262,3 +266,110 @@ class MTorrentSpider:
|
||||
# base64编码
|
||||
base64_str = base64.b64encode(json.dumps(params).encode('utf-8')).decode('utf-8')
|
||||
return f"[{base64_str}]{url}"
|
||||
|
||||
def get_subtitle_links(self, page_url: str) -> List[str]:
|
||||
"""
|
||||
获取指定页面的字幕下载链接
|
||||
|
||||
:param page_url: 种子详情页网址
|
||||
:type page_url: str
|
||||
:return: 字幕下载链接
|
||||
:rtype: List[str]
|
||||
"""
|
||||
if not page_url:
|
||||
return []
|
||||
# 从馒头的详情页网址中提取种子id
|
||||
torrent_id = urlparse(page_url).path.rsplit("/", 1)[-1].strip()
|
||||
if not torrent_id:
|
||||
return []
|
||||
return self.get_subtitle_links_by_id(torrent_id)
|
||||
|
||||
def get_subtitle_links_by_id(self, torrent_id: str) -> List[str]:
|
||||
"""
|
||||
获取指定种子的字幕下载链接
|
||||
|
||||
:param torrent_id: 种子ID
|
||||
:type torrent_id: str
|
||||
:return: 字幕下载链接
|
||||
:rtype: List[str]
|
||||
"""
|
||||
results = []
|
||||
try:
|
||||
for subtitle_id in self.__subtitle_ids(torrent_id) or []:
|
||||
if link := self.__subtitle_genlink(subtitle_id):
|
||||
results.append(link)
|
||||
except Exception as e:
|
||||
logger.error(f"{self._name} 获取字幕失败:{e}")
|
||||
return results
|
||||
|
||||
def __subtitle_ids(self, torrent_id: str) -> Optional[List[str]]:
|
||||
"""
|
||||
获取指定种子的字幕列表
|
||||
|
||||
:param torrent_id: 种子ID
|
||||
:type torrent_id: str
|
||||
:return: 字幕ID
|
||||
:rtype: List[str] | None
|
||||
"""
|
||||
url = self._subtitle_list_url % self._domain
|
||||
# 发送请求
|
||||
res = RequestUtils(
|
||||
headers={
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"User-Agent": f"{self._ua}",
|
||||
"x-api-key": self._apikey,
|
||||
},
|
||||
proxies=self._proxy,
|
||||
timeout=self._timeout,
|
||||
).post_res(url, data={"id": torrent_id})
|
||||
if res and res.status_code == 200:
|
||||
result = res.json()
|
||||
if int(result.get("code", -1)) == 0:
|
||||
return [item["id"] for item in result.get("data", []) if "id" in item]
|
||||
else:
|
||||
logger.warn(
|
||||
f"{self._name} 获取字幕列表失败,返回:{result.get("message", "未知")}"
|
||||
)
|
||||
return None
|
||||
elif res is not None:
|
||||
logger.warn(f"{self._name} 获取字幕列表失败,错误码:{res.status_code}")
|
||||
return None
|
||||
else:
|
||||
logger.warn(f"{self._name} 获取字幕列表失败,无法连接 {self._domain}")
|
||||
return None
|
||||
|
||||
def __subtitle_genlink(self, subtitle_id: str) -> Optional[str]:
|
||||
"""
|
||||
获取字幕下载链接
|
||||
|
||||
:param subtitle_id: 字幕ID
|
||||
:type subtitle_id: str
|
||||
:return: 下载链接
|
||||
:rtype: str | None
|
||||
"""
|
||||
url = self._subtitle_genlink_url % self._domain
|
||||
# 发送请求
|
||||
res = RequestUtils(
|
||||
headers={
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"User-Agent": f"{self._ua}",
|
||||
"x-api-key": self._apikey,
|
||||
},
|
||||
proxies=self._proxy,
|
||||
timeout=self._timeout,
|
||||
).post_res(url, data={"id": subtitle_id})
|
||||
if res and res.status_code == 200:
|
||||
result = res.json()
|
||||
if int(result.get("code", -1)) == 0 and isinstance(result.get("data"), str):
|
||||
return self._subtitle_download_url % (self._domain, result["data"])
|
||||
else:
|
||||
logger.warn(
|
||||
f"{self._name} 获取字幕下载链接失败,返回:{result.get("message", "未知")}"
|
||||
)
|
||||
return None
|
||||
elif res is not None:
|
||||
logger.warn(f"{self._name} 获取字幕下载链接失败,错误码:{res.status_code}")
|
||||
return None
|
||||
else:
|
||||
logger.warn(f"{self._name} 获取字幕下载链接失败,无法连接 {self._domain}")
|
||||
return None
|
||||
|
||||
@@ -8,9 +8,13 @@ from lxml import etree
|
||||
from app.chain.storage import StorageChain
|
||||
from app.core.config import settings
|
||||
from app.core.context import Context
|
||||
from app.db.site_oper import SiteOper
|
||||
from app.helper.sites import SitesHelper # noqa
|
||||
from app.helper.torrent import TorrentHelper
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.modules.indexer.spider.mtorrent import MTorrentSpider
|
||||
from app.schemas import TorrentInfo
|
||||
from app.schemas.file import FileURI
|
||||
from app.schemas.types import ModuleType, OtherModulesType
|
||||
from app.utils.http import RequestUtils
|
||||
@@ -25,7 +29,9 @@ class SubtitleModule(_ModuleBase):
|
||||
|
||||
# 站点详情页字幕下载链接识别XPATH
|
||||
_SITE_SUBTITLE_XPATH = [
|
||||
'//td[@class="rowhead"][text()="字幕"]/following-sibling::td//a[not(@class)]/@href',
|
||||
'//td[@class="rowhead"][text()="字幕"]/following-sibling::td//a/@href',
|
||||
'//div[contains(@class, "font-bold")][text()="字幕"]/following-sibling::div[1]//a[not(@class)]/@href', # 憨憨
|
||||
]
|
||||
|
||||
def init_module(self) -> None:
|
||||
@@ -65,6 +71,54 @@ class SubtitleModule(_ModuleBase):
|
||||
def test(self):
|
||||
pass
|
||||
|
||||
def _get_subtitle_links(self, torrent: TorrentInfo):
|
||||
"""
|
||||
获取字幕链接
|
||||
"""
|
||||
# API请求方式的站点需要特殊处理
|
||||
if torrent.site is not None:
|
||||
site = SiteOper().get(torrent.site)
|
||||
if indexer := SitesHelper().get_indexer(site.domain):
|
||||
if indexer.get("parser") == "mTorrent":
|
||||
return MTorrentSpider(indexer).get_subtitle_links(
|
||||
torrent.page_url
|
||||
)
|
||||
# TODO 其它采用API访问的站点
|
||||
# 普通站点通过解析网站代码的方式获取
|
||||
request = RequestUtils(cookies=torrent.site_cookie, ua=torrent.site_ua)
|
||||
res = request.get_res(torrent.page_url)
|
||||
if res and res.status_code == 200:
|
||||
if not res.text:
|
||||
logger.warn(f"读取页面代码失败:{torrent.page_url}")
|
||||
return []
|
||||
html = etree.HTML(res.text)
|
||||
try:
|
||||
sublink_list = []
|
||||
for xpath in self._SITE_SUBTITLE_XPATH:
|
||||
sublinks = html.xpath(xpath)
|
||||
if sublinks:
|
||||
for sublink in sublinks:
|
||||
if not sublink:
|
||||
continue
|
||||
if not sublink.startswith("http"):
|
||||
base_url = StringUtils.get_base_url(torrent.page_url)
|
||||
if sublink.startswith("/"):
|
||||
sublink = "%s%s" % (base_url, sublink)
|
||||
else:
|
||||
sublink = "%s/%s" % (base_url, sublink)
|
||||
sublink_list.append(sublink)
|
||||
# 已成功获取了链接,后续xpath可以忽略
|
||||
break
|
||||
return sublink_list
|
||||
finally:
|
||||
if html is not None:
|
||||
del html
|
||||
elif res is not None:
|
||||
logger.warn(f"连接 {torrent.page_url} 失败,状态码:{res.status_code}")
|
||||
else:
|
||||
logger.warn(f"无法打开链接:{torrent.page_url}")
|
||||
return None
|
||||
|
||||
def download_added(self, context: Context, download_dir: Path, torrent_content: Union[str, bytes] = None):
|
||||
"""
|
||||
添加下载任务成功后,从站点下载字幕,保存到下载目录
|
||||
@@ -117,83 +171,56 @@ class SubtitleModule(_ModuleBase):
|
||||
logger.error(f"下载目录不存在,无法保存字幕:{download_dir / folder_name}")
|
||||
return
|
||||
# 读取网站代码
|
||||
sublink_list = self._get_subtitle_links(torrent)
|
||||
if not sublink_list:
|
||||
logger.warn(f"{torrent.page_url} 页面未找到字幕下载链接")
|
||||
return
|
||||
# 下载所有字幕文件
|
||||
request = RequestUtils(cookies=torrent.site_cookie, ua=torrent.site_ua)
|
||||
res = request.get_res(torrent.page_url)
|
||||
if res and res.status_code == 200:
|
||||
if not res.text:
|
||||
logger.warn(f"读取页面代码失败:{torrent.page_url}")
|
||||
return
|
||||
html = etree.HTML(res.text)
|
||||
try:
|
||||
sublink_list = []
|
||||
for xpath in self._SITE_SUBTITLE_XPATH:
|
||||
sublinks = html.xpath(xpath)
|
||||
if sublinks:
|
||||
for sublink in sublinks:
|
||||
if not sublink:
|
||||
continue
|
||||
if not sublink.startswith("http"):
|
||||
base_url = StringUtils.get_base_url(torrent.page_url)
|
||||
if sublink.startswith("/"):
|
||||
sublink = "%s%s" % (base_url, sublink)
|
||||
else:
|
||||
sublink = "%s/%s" % (base_url, sublink)
|
||||
sublink_list.append(sublink)
|
||||
finally:
|
||||
if html is not None:
|
||||
del html
|
||||
# 下载所有字幕文件
|
||||
for sublink in sublink_list:
|
||||
logger.info(f"找到字幕下载链接:{sublink},开始下载...")
|
||||
# 下载
|
||||
ret = request.get_res(sublink)
|
||||
if ret and ret.status_code == 200:
|
||||
# 保存ZIP
|
||||
file_name = TorrentHelper.get_url_filename(ret, sublink)
|
||||
if not file_name:
|
||||
logger.warn(f"链接不是字幕文件:{sublink}")
|
||||
continue
|
||||
if file_name.lower().endswith(".zip"):
|
||||
# ZIP包
|
||||
zip_file = settings.TEMP_PATH / file_name
|
||||
# 保存
|
||||
zip_file.write_bytes(ret.content)
|
||||
# 解压路径
|
||||
zip_path = zip_file.with_name(zip_file.stem)
|
||||
# 解压文件
|
||||
shutil.unpack_archive(zip_file, zip_path, format='zip')
|
||||
# 遍历转移文件
|
||||
for sub_file in SystemUtils.list_files(zip_path, settings.RMT_SUBEXT):
|
||||
target_sub_file = Path(working_dir_item.path) / Path(sub_file.name)
|
||||
if storageChain.get_file_item(storage, target_sub_file):
|
||||
logger.info(f"字幕文件已存在:{target_sub_file}")
|
||||
continue
|
||||
logger.info(f"转移字幕 {sub_file} 到 {target_sub_file} ...")
|
||||
storageChain.upload_file(working_dir_item, sub_file)
|
||||
# 删除临时文件
|
||||
try:
|
||||
shutil.rmtree(zip_path)
|
||||
zip_file.unlink()
|
||||
except Exception as err:
|
||||
logger.error(f"删除临时文件失败:{str(err)}")
|
||||
else:
|
||||
sub_file = settings.TEMP_PATH / file_name
|
||||
# 保存
|
||||
sub_file.write_bytes(ret.content)
|
||||
for sublink in sublink_list:
|
||||
logger.info(f"找到字幕下载链接:{sublink},开始下载...")
|
||||
# 下载
|
||||
ret = request.get_res(sublink)
|
||||
if ret and ret.status_code == 200:
|
||||
# 保存ZIP
|
||||
file_name = TorrentHelper.get_url_filename(ret, sublink)
|
||||
if not file_name:
|
||||
logger.warn(f"链接不是字幕文件:{sublink}")
|
||||
continue
|
||||
if file_name.lower().endswith(".zip"):
|
||||
# ZIP包
|
||||
zip_file = settings.TEMP_PATH / file_name
|
||||
# 保存
|
||||
zip_file.write_bytes(ret.content)
|
||||
# 解压路径
|
||||
zip_path = zip_file.with_name(zip_file.stem)
|
||||
# 解压文件
|
||||
shutil.unpack_archive(zip_file, zip_path, format='zip')
|
||||
# 遍历转移文件
|
||||
for sub_file in SystemUtils.list_files(zip_path, settings.RMT_SUBEXT):
|
||||
target_sub_file = Path(working_dir_item.path) / Path(sub_file.name)
|
||||
if storageChain.get_file_item(storage, target_sub_file):
|
||||
logger.info(f"字幕文件已存在:{target_sub_file}")
|
||||
continue
|
||||
logger.info(f"转移字幕 {sub_file} 到 {target_sub_file} ...")
|
||||
storageChain.upload_file(working_dir_item, sub_file)
|
||||
# 删除临时文件
|
||||
try:
|
||||
shutil.rmtree(zip_path)
|
||||
zip_file.unlink()
|
||||
except Exception as err:
|
||||
logger.error(f"删除临时文件失败:{str(err)}")
|
||||
else:
|
||||
logger.error(f"下载字幕文件失败:{sublink}")
|
||||
continue
|
||||
if sublink_list:
|
||||
logger.info(f"{torrent.page_url} 页面字幕下载完成")
|
||||
sub_file = settings.TEMP_PATH / file_name
|
||||
# 保存
|
||||
sub_file.write_bytes(ret.content)
|
||||
target_sub_file = Path(working_dir_item.path) / Path(sub_file.name)
|
||||
if storageChain.get_file_item(storage, target_sub_file):
|
||||
logger.info(f"字幕文件已存在:{target_sub_file}")
|
||||
continue
|
||||
logger.info(f"转移字幕 {sub_file} 到 {target_sub_file} ...")
|
||||
storageChain.upload_file(working_dir_item, sub_file)
|
||||
else:
|
||||
logger.warn(f"{torrent.page_url} 页面未找到字幕下载链接")
|
||||
elif res is not None:
|
||||
logger.warn(f"连接 {torrent.page_url} 失败,状态码:{res.status_code}")
|
||||
else:
|
||||
logger.warn(f"无法打开链接:{torrent.page_url}")
|
||||
logger.error(f"下载字幕文件失败:{sublink}")
|
||||
continue
|
||||
logger.info(f"{torrent.page_url} 页面字幕下载完成")
|
||||
|
||||
@@ -78,7 +78,7 @@ class TheMovieDbModule(_ModuleBase):
|
||||
"""
|
||||
测试模块连接性
|
||||
"""
|
||||
ret = RequestUtils(proxies=settings.PROXY).get_res(
|
||||
ret = RequestUtils(ua=settings.NORMAL_USER_AGENT, proxies=settings.PROXY).get_res(
|
||||
f"https://{settings.TMDB_API_DOMAIN}/3/movie/550?api_key={settings.TMDB_API_KEY}")
|
||||
if ret and ret.status_code == 200:
|
||||
return True, ""
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
APP_VERSION = 'v2.8.9'
|
||||
FRONTEND_VERSION = 'v2.8.9'
|
||||
APP_VERSION = 'v2.9.1'
|
||||
FRONTEND_VERSION = 'v2.9.1'
|
||||
|
||||
Reference in New Issue
Block a user