Compare commits

...
21 Commits
Author SHA1 Message Date
jxxghp 5e077cd64d 更新 version.py 2025-12-31 07:49:12 +08:00
jxxghp e3f957a59b 更新 __init__.py 2025-12-31 07:20:52 +08:00
jxxghp 55c62a3ab5 Merge pull request #5303 from HankunYu/v2 2025-12-31 07:00:04 +08:00
jxxghp 22e7eef1bd Merge pull request #5302 from cddjr/fix_tmdb_healthcheck 2025-12-31 06:59:03 +08:00
HankunYu d6524907f3 修复重载模块会产生新的DC实例;建立embed解析白名单,不解析插件等消息以免破坏原有格式 2025-12-30 16:51:30 +00:00
景大侠 357db334cd 修复 自建TMDB服无法通过健康检测
携带UA以避免被反爬虫脚本过滤
2025-12-30 22:13:43 +08:00
jxxghp f8bed3909b Merge pull request #5299 from cddjr/fix_5297 2025-12-30 15:52:29 +08:00
景大侠 182bbdde91 fix #5297 2025-12-30 15:21:27 +08:00
jxxghp 2c70f990c2 Merge pull request #5294 from cddjr/mteam_subtitle 2025-12-30 06:57:15 +08:00
景大侠 0b01a6aa91 避免获取到字幕上传者的详情链接 2025-12-29 22:52:26 +08:00
景大侠 e557dffbc6 支持憨憨站点的字幕下载 2025-12-29 22:43:47 +08:00
景大侠 7f33b0b1b8 支持馒头站点的字幕下载 2025-12-29 22:43:07 +08:00
景大侠 41ddf77a5b 添加馒头字幕API 2025-12-29 20:01:54 +08:00
jxxghp 8c657ce41d 更新 version.py 2025-12-28 17:58:39 +08:00
jxxghp 3ff3b9ed4a Merge pull request #5290 from PKC278/v2 2025-12-28 17:58:05 +08:00
PKC278andgemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> ef43419ecd Update app/api/endpoints/system.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-28 16:38:30 +08:00
PKC278 2ca375c214 feat(system): 添加前端和后端版本信息 2025-12-28 16:08:14 +08:00
jxxghp cbd45c1d0f Merge pull request #5289 from HankunYu/v2 2025-12-28 12:50:40 +08:00
HankunYu 2592ea3464 清理 prefix/suffix 与字段值的分隔符;字段名允许 &;当冒号落在 《》/【】 内时整行作为描述,避免书名号误拆 2025-12-27 17:00:07 +00:00
HankunYu 73ac97cd96 更新解析embed逻辑; 添加使用代理 2025-12-27 13:05:57 +00:00
jxxghp e014663e97 更新 version.py 2025-12-27 14:22:35 +08:00
9 changed files with 322 additions and 95 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ def exists(media_in: schemas.MediaInfo,
mediainfo.from_dict(media_in.model_dump()) mediainfo.from_dict(media_in.model_dump())
existsinfo: schemas.ExistMediaInfo = MediaServerChain().media_exists(mediainfo=mediainfo) existsinfo: schemas.ExistMediaInfo = MediaServerChain().media_exists(mediainfo=mediainfo)
if not existsinfo: if not existsinfo:
return [] return {}
if media_in.season: if media_in.season:
return { return {
media_in.season: existsinfo.seasons.get(media_in.season) or [] media_in.season: existsinfo.seasons.get(media_in.season) or []
+3 -1
View File
@@ -149,7 +149,9 @@ def get_global_setting(token: str):
info.update({ info.update({
"USER_UNIQUE_ID": SubscribeHelper().get_user_uuid(), "USER_UNIQUE_ID": SubscribeHelper().get_user_uuid(),
"SUBSCRIBE_SHARE_MANAGE": share_admin, "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, return schemas.Response(success=True,
data=info) data=info)
-2
View File
@@ -454,7 +454,6 @@ class Base:
@db_update @db_update
def update(self, db: Session, payload: dict): 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(): for key, value in payload.items():
setattr(self, key, value) setattr(self, key, value)
if inspect(self).detached: if inspect(self).detached:
@@ -462,7 +461,6 @@ class Base:
@async_db_update @async_db_update
async def async_update(self, db: AsyncSession, payload: dict): 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(): for key, value in payload.items():
setattr(self, key, value) setattr(self, key, value)
if inspect(self).detached: if inspect(self).detached:
+3 -1
View File
@@ -23,6 +23,7 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
if not Discord: if not Discord:
logger.error("Discord 依赖未就绪(需要安装 discord.py==2.6.4),模块未启动") logger.error("Discord 依赖未就绪(需要安装 discord.py==2.6.4),模块未启动")
return return
self.stop()
super().init_service(service_name=Discord.__name__.lower(), super().init_service(service_name=Discord.__name__.lower(),
service_type=Discord) service_type=Discord)
self._channel = MessageChannel.Discord self._channel = MessageChannel.Discord
@@ -154,7 +155,8 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
image=message.image, userid=userid, link=message.link, image=message.image, userid=userid, link=message.link,
buttons=message.buttons, buttons=message.buttons,
original_message_id=message.original_message_id, 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: def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> None:
""" """
+103 -16
View File
@@ -1,7 +1,7 @@
import asyncio import asyncio
import re import re
import threading import threading
from typing import Optional, List, Dict, Any, Union from typing import Optional, List, Dict, Any, Tuple, Union
import discord import discord
from discord import app_commands 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.context import MediaInfo, Context
from app.core.metainfo import MetaInfo from app.core.metainfo import MetaInfo
from app.log import logger from app.log import logger
from app.schemas.types import NotificationType
from app.utils.string import StringUtils from app.utils.string import StringUtils
# Discord embed 字段解析白名单
# 只有这些消息类型会使用复杂的字段解析逻辑
PARSE_FIELD_TYPES = {
NotificationType.Download, # 资源下载
NotificationType.Organize, # 整理入库
NotificationType.Subscribe, # 订阅
NotificationType.Manual, # 手动处理
}
class Discord: class Discord:
""" """
@@ -40,7 +50,10 @@ class Discord:
intents.messages = True intents.messages = True
intents.guilds = 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._tree: Optional[app_commands.CommandTree] = None
self._loop: asyncio.AbstractEventLoop = asyncio.new_event_loop() self._loop: asyncio.AbstractEventLoop = asyncio.new_event_loop()
self._thread: Optional[threading.Thread] = None self._thread: Optional[threading.Thread] = None
@@ -153,7 +166,8 @@ class Discord:
userid: Optional[str] = None, link: Optional[str] = None, userid: Optional[str] = None, link: Optional[str] = None,
buttons: Optional[List[List[dict]]] = None, buttons: Optional[List[List[dict]]] = None,
original_message_id: Optional[Union[int, str]] = 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(): if not self.get_state():
return False return False
if not title and not text: if not title and not text:
@@ -165,7 +179,8 @@ class Discord:
self._send_message(title=title, text=text, image=image, userid=userid, self._send_message(title=title, text=text, image=image, userid=userid,
link=link, buttons=buttons, link=link, buttons=buttons,
original_message_id=original_message_id, original_message_id=original_message_id,
original_chat_id=original_chat_id), original_chat_id=original_chat_id,
mtype=mtype),
self._loop) self._loop)
return future.result(timeout=30) return future.result(timeout=30)
except Exception as err: except Exception as err:
@@ -237,13 +252,14 @@ class Discord:
userid: Optional[str], link: Optional[str], userid: Optional[str], link: Optional[str],
buttons: Optional[List[List[dict]]], buttons: Optional[List[List[dict]]],
original_message_id: Optional[Union[int, str]], 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) channel = await self._resolve_channel(userid=userid, chat_id=original_chat_id)
if not channel: if not channel:
logger.error("未找到可用的 Discord 频道或私聊") logger.error("未找到可用的 Discord 频道或私聊")
return False 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) view = self._build_view(buttons=buttons, link=link)
content = None content = None
@@ -315,21 +331,92 @@ class Discord:
@staticmethod @staticmethod
def _build_embed(title: str, text: Optional[str], image: Optional[str], def _build_embed(title: str, text: Optional[str], image: Optional[str],
link: Optional[str]) -> discord.Embed: link: Optional[str], mtype: Optional['NotificationType'] = None) -> discord.Embed:
description = ""
fields: List[Dict[str, str]] = [] 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: if text:
for line in text.splitlines(): # 处理上游未反序列化的 "\n" 等转义换行,避免被当成普通字符
if "" in line: if "\\n" in text or "\\r" in text:
name, value = line.split("", 1) text = text.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\\r", "\n")
fields.append({"name": name.strip(), "value": value.strip() or "-", "inline": False}) if not should_parse_fields:
else: desc_lines.append(text.strip())
description += f"{line}\n" else:
description = description.strip() # 匹配形如 "字段:值" 的片段,字段名不允许包含常见分隔符;
# 下一个字段需以顿号/逗号/分号等分隔开,且不能是 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( embed = discord.Embed(
title=title, title=title,
url=link or "https://github.com/jxxghp/MoviePilot", url=link or "https://github.com/jxxghp/MoviePilot",
description=description if description else (text or None), description=description if description else None,
color=0xE67E22 color=0xE67E22
) )
for field in fields: for field in fields:
+111
View File
@@ -2,6 +2,7 @@ import base64
import json import json
import re import re
from typing import Tuple, List, Optional from typing import Tuple, List, Optional
from urllib.parse import urlparse
from app.core.config import settings from app.core.config import settings
from app.db.systemconfig_oper import SystemConfigOper from app.db.systemconfig_oper import SystemConfigOper
@@ -25,6 +26,9 @@ class MTorrentSpider:
_size = 100 _size = 100
_searchurl = "https://api.%s/api/torrent/search" _searchurl = "https://api.%s/api/torrent/search"
_downloadurl = "https://api.%s/api/torrent/genDlToken" _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" _pageurl = "%sdetail/%s"
_timeout = 15 _timeout = 15
@@ -262,3 +266,110 @@ class MTorrentSpider:
# base64编码 # base64编码
base64_str = base64.b64encode(json.dumps(params).encode('utf-8')).decode('utf-8') base64_str = base64.b64encode(json.dumps(params).encode('utf-8')).decode('utf-8')
return f"[{base64_str}]{url}" 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
+98 -71
View File
@@ -8,9 +8,13 @@ from lxml import etree
from app.chain.storage import StorageChain from app.chain.storage import StorageChain
from app.core.config import settings from app.core.config import settings
from app.core.context import Context 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.helper.torrent import TorrentHelper
from app.log import logger from app.log import logger
from app.modules import _ModuleBase 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.file import FileURI
from app.schemas.types import ModuleType, OtherModulesType from app.schemas.types import ModuleType, OtherModulesType
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
@@ -25,7 +29,9 @@ class SubtitleModule(_ModuleBase):
# 站点详情页字幕下载链接识别XPATH # 站点详情页字幕下载链接识别XPATH
_SITE_SUBTITLE_XPATH = [ _SITE_SUBTITLE_XPATH = [
'//td[@class="rowhead"][text()="字幕"]/following-sibling::td//a[not(@class)]/@href',
'//td[@class="rowhead"][text()="字幕"]/following-sibling::td//a/@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: def init_module(self) -> None:
@@ -65,6 +71,54 @@ class SubtitleModule(_ModuleBase):
def test(self): def test(self):
pass 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): 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}") logger.error(f"下载目录不存在,无法保存字幕:{download_dir / folder_name}")
return 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) request = RequestUtils(cookies=torrent.site_cookie, ua=torrent.site_ua)
res = request.get_res(torrent.page_url) for sublink in sublink_list:
if res and res.status_code == 200: logger.info(f"找到字幕下载链接:{sublink},开始下载...")
if not res.text: # 下载
logger.warn(f"读取页面代码失败:{torrent.page_url}") ret = request.get_res(sublink)
return if ret and ret.status_code == 200:
html = etree.HTML(res.text) # 保存ZIP
try: file_name = TorrentHelper.get_url_filename(ret, sublink)
sublink_list = [] if not file_name:
for xpath in self._SITE_SUBTITLE_XPATH: logger.warn(f"链接不是字幕文件:{sublink}")
sublinks = html.xpath(xpath) continue
if sublinks: if file_name.lower().endswith(".zip"):
for sublink in sublinks: # ZIP包
if not sublink: zip_file = settings.TEMP_PATH / file_name
continue # 保存
if not sublink.startswith("http"): zip_file.write_bytes(ret.content)
base_url = StringUtils.get_base_url(torrent.page_url) # 解压路径
if sublink.startswith("/"): zip_path = zip_file.with_name(zip_file.stem)
sublink = "%s%s" % (base_url, sublink) # 解压文件
else: shutil.unpack_archive(zip_file, zip_path, format='zip')
sublink = "%s/%s" % (base_url, sublink) # 遍历转移文件
sublink_list.append(sublink) for sub_file in SystemUtils.list_files(zip_path, settings.RMT_SUBEXT):
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)
target_sub_file = Path(working_dir_item.path) / Path(sub_file.name) target_sub_file = Path(working_dir_item.path) / Path(sub_file.name)
if storageChain.get_file_item(storage, target_sub_file): if storageChain.get_file_item(storage, target_sub_file):
logger.info(f"字幕文件已存在:{target_sub_file}") logger.info(f"字幕文件已存在:{target_sub_file}")
continue continue
logger.info(f"转移字幕 {sub_file}{target_sub_file} ...") logger.info(f"转移字幕 {sub_file}{target_sub_file} ...")
storageChain.upload_file(working_dir_item, 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: else:
logger.error(f"下载字幕文件失败:{sublink}") sub_file = settings.TEMP_PATH / file_name
continue # 保存
if sublink_list: sub_file.write_bytes(ret.content)
logger.info(f"{torrent.page_url} 页面字幕下载完成") 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: else:
logger.warn(f"{torrent.page_url} 页面未找到字幕下载链接") logger.error(f"下载字幕文件失败:{sublink}")
elif res is not None: continue
logger.warn(f"连接 {torrent.page_url} 失败,状态码:{res.status_code}") logger.info(f"{torrent.page_url} 页面字幕下载完成")
else:
logger.warn(f"无法打开链接:{torrent.page_url}")
+1 -1
View File
@@ -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}") f"https://{settings.TMDB_API_DOMAIN}/3/movie/550?api_key={settings.TMDB_API_KEY}")
if ret and ret.status_code == 200: if ret and ret.status_code == 200:
return True, "" return True, ""
+2 -2
View File
@@ -1,2 +1,2 @@
APP_VERSION = 'v2.8.9' APP_VERSION = 'v2.9.1'
FRONTEND_VERSION = 'v2.8.9' FRONTEND_VERSION = 'v2.9.1'