From 1ef4374899d44a7b44d0b33f28c75457e9957711 Mon Sep 17 00:00:00 2001 From: Attente <19653207+wikrin@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:56:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(telegram):=20=E5=9B=BE=E7=89=87=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=BC=93=E5=AD=98=E4=B8=8E=E5=AE=89=E5=85=A8=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C,=20=E8=8E=B7=E5=8F=96=E5=A4=B1=E8=B4=A5=E9=99=8D?= =?UTF-8?q?=E7=BA=A7=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一部分类型标注 - 修正部分文本错误 --- app/chain/subscribe.py | 2 +- app/modules/emby/emby.py | 2 +- app/modules/jellyfin/jellyfin.py | 2 +- app/modules/plex/plex.py | 2 +- app/modules/synologychat/synologychat.py | 2 +- app/modules/telegram/telegram.py | 63 ++++++++++++++++-------- app/modules/themoviedb/tmdbapi.py | 4 +- app/modules/trimemedia/trimemedia.py | 2 +- app/schemas/mediaserver.py | 4 +- 9 files changed, 53 insertions(+), 30 deletions(-) diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index a7be59aa..2f8f4ff4 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -949,7 +949,7 @@ class SubscribeChain(ChainBase): and torrent_mediainfo.douban_id != mediainfo.douban_id: continue logger.info( - f'{mediainfo.title_year} 通过媒体信ID匹配到可选资源:{torrent_info.site_name} - {torrent_info.title}') + f'{mediainfo.title_year} 通过媒体ID匹配到可选资源:{torrent_info.site_name} - {torrent_info.title}') else: continue diff --git a/app/modules/emby/emby.py b/app/modules/emby/emby.py index a9acebc4..7e93faec 100644 --- a/app/modules/emby/emby.py +++ b/app/modules/emby/emby.py @@ -640,7 +640,7 @@ class Emby: item_type=item.get("Type"), title=item.get("Name"), original_title=item.get("OriginalTitle"), - year=str(item.get("ProductionYear")), + year=item.get("ProductionYear"), tmdbid=int(tmdbid) if tmdbid else None, imdbid=item.get("ProviderIds", {}).get("Imdb"), tvdbid=item.get("ProviderIds", {}).get("Tvdb"), diff --git a/app/modules/jellyfin/jellyfin.py b/app/modules/jellyfin/jellyfin.py index 25769435..71ac3be2 100644 --- a/app/modules/jellyfin/jellyfin.py +++ b/app/modules/jellyfin/jellyfin.py @@ -732,7 +732,7 @@ class Jellyfin: item_type=item.get("Type"), title=item.get("Name"), original_title=item.get("OriginalTitle"), - year=str(item.get("ProductionYear")), + year=item.get("ProductionYear"), tmdbid=int(tmdbid) if tmdbid else None, imdbid=item.get("ProviderIds", {}).get("Imdb"), tvdbid=item.get("ProviderIds", {}).get("Tvdb"), diff --git a/app/modules/plex/plex.py b/app/modules/plex/plex.py index 3eec4446..b20ab3d0 100644 --- a/app/modules/plex/plex.py +++ b/app/modules/plex/plex.py @@ -509,7 +509,7 @@ class Plex: item_type=item.type, title=item.title, original_title=item.originalTitle, - year=str(item.year), + year=item.year, tmdbid=ids.get("tmdb_id"), imdbid=ids.get("imdb_id"), tvdbid=ids.get("tvdb_id"), diff --git a/app/modules/synologychat/synologychat.py b/app/modules/synologychat/synologychat.py index dbb56e55..5aef717e 100644 --- a/app/modules/synologychat/synologychat.py +++ b/app/modules/synologychat/synologychat.py @@ -41,7 +41,7 @@ class SynologyChat: def send_msg(self, title: str, text: Optional[str] = None, image: Optional[str] = None, userid: Optional[str] = None, link: Optional[str] = None) -> Optional[bool]: """ - 发送Telegram消息 + 发送SynologyChat消息 :param title: 消息标题 :param text: 消息内容 :param image: 消息图片地址 diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index b4ba7c6d..f6d1b79c 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -1,17 +1,18 @@ import asyncio +import io import re import threading -from threading import Event +from pathlib import Path from typing import Optional, List, Dict, Callable from urllib.parse import urljoin -import telebot +from PIL import Image +from telebot import TeleBot, apihelper +from telebot.types import BotCommand, InlineKeyboardMarkup, InlineKeyboardButton, InputMediaPhoto from telegramify_markdown import standardize, telegramify from telegramify_markdown.type import ContentTypes, SentType -from telebot import apihelper -from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton -from telebot.types import InputMediaPhoto +from app.core.cache import FileCache from app.core.config import settings from app.core.context import MediaInfo, Context from app.core.metainfo import MetaInfo @@ -19,6 +20,7 @@ from app.helper.thread import ThreadHelper from app.log import logger from app.utils.common import retry from app.utils.http import RequestUtils +from app.utils.security import SecurityUtils from app.utils.string import StringUtils @@ -28,8 +30,7 @@ class RetryException(Exception): class Telegram: _ds_url = f"http://127.0.0.1:{settings.PORT}/api/v1/message?token={settings.API_TOKEN}" - _event = Event() - _bot: telebot.TeleBot = None + _bot: TeleBot = None _callback_handlers: Dict[str, Callable] = {} # 存储回调处理器 _user_chat_mapping: Dict[str, str] = {} # userid -> chat_id mapping for reply targeting _bot_username: Optional[str] = None # Bot username for mention detection @@ -54,7 +55,7 @@ class Telegram: else: apihelper.proxy = settings.PROXY # bot - _bot = telebot.TeleBot(self._telegram_token, parse_mode="MarkdownV2") + _bot = TeleBot(self._telegram_token, parse_mode="MarkdownV2") # 记录句柄 self._bot = _bot # 获取并存储bot用户名用于@检测 @@ -539,6 +540,9 @@ class Telegram: try: # 处理图片 image = self.__process_image(image) if image else None + except RetryException as e: + logger.error(f"{str(e)}, 达到重试次数上限, 仅发送文本消息") + image = None # 图片消息的标题长度限制为1024,文本消息为4096 caption_limit = 1024 if image else 4096 @@ -554,22 +558,41 @@ class Telegram: return False @retry(RetryException, logger=logger) - def __process_image(self, image_url: str) -> bytes: + def __process_image(self, image_url: str) -> Optional[bytes]: """ 处理图片URL,获取图片内容 """ + # 缓存路径 + sanitized_path = SecurityUtils.sanitize_url_path(image_url) + cache_path = Path("images") / sanitized_path + # 没有文件类型,则添加后缀 + if not cache_path.suffix: + cache_path = cache_path.with_suffix(".jpg") + + cache_backend = FileCache(base=settings.CACHE_PATH, + ttl=settings.GLOBAL_IMAGE_CACHE_DAYS * 24 * 3600) + + content = cache_backend.get(cache_path.as_posix(), region="images") + if content: + return content + + # 请求远程图片 + referer = "https://movie.douban.com/" if "doubanio.com" in image_url else None + proxies = settings.PROXY if not referer else None + res = RequestUtils(ua=settings.NORMAL_USER_AGENT, proxies=proxies, referer=referer).get_res(url=image_url) + if not res or not res.content: + raise RetryException("获取图片失败") + try: - res = RequestUtils( - proxies=settings.PROXY, - ua=settings.NORMAL_USER_AGENT - ).get_res(image_url) - - if not res or not res.content: - raise RetryException("获取图片失败") - + # 验证内容是否为有效图片 + Image.open(io.BytesIO(res.content)).verify() + # 保存缓存 + cache_backend.set(cache_path.as_posix(), res.content, region="images") return res.content except Exception as e: - raise + logger.error(f"图片验证失败:{str(e)}, 仅发送文本消息") + return None + @retry(RetryException, logger=logger) def __send_short_message(self, image: Optional[bytes], caption: str, **kwargs): @@ -637,7 +660,7 @@ class Telegram: try: raise RetryException(f"消息 [{i + 1}/{len(boxs)}] 发送失败") from e except NameError: - raise RetryException("发送长消息失败") from e + raise def register_commands(self, commands: Dict[str, dict]): """ @@ -650,7 +673,7 @@ class Telegram: self._bot.delete_my_commands() self._bot.set_my_commands( commands=[ - telebot.types.BotCommand(cmd[1:], str(desc.get("description"))) for cmd, desc in + BotCommand(cmd[1:], str(desc.get("description"))) for cmd, desc in commands.items() ] ) diff --git a/app/modules/themoviedb/tmdbapi.py b/app/modules/themoviedb/tmdbapi.py index eb844923..f123293c 100644 --- a/app/modules/themoviedb/tmdbapi.py +++ b/app/modules/themoviedb/tmdbapi.py @@ -744,11 +744,11 @@ class TmdbApi: if validation_result is not None: return validation_result - logger.info("正在从TheDbMovie网站查询:%s ..." % name) + logger.info("正在从TheMovieDb网站查询:%s ..." % name) tmdb_url = self._build_tmdb_search_url(name) res = RequestUtils(timeout=5, ua=settings.NORMAL_USER_AGENT, proxies=settings.PROXY).get_res(url=tmdb_url) if res is None: - logger.error("无法连接TheDbMovie") + logger.error("无法连接TheMovieDb") return None # 响应验证 diff --git a/app/modules/trimemedia/trimemedia.py b/app/modules/trimemedia/trimemedia.py index fce6b666..b5d6f074 100644 --- a/app/modules/trimemedia/trimemedia.py +++ b/app/modules/trimemedia/trimemedia.py @@ -449,7 +449,7 @@ class TrimeMedia: item_type=item_type, title=item.title, original_title=item.original_title, - year=str(year), + year=year, tmdbid=item.tmdb_id, imdbid=item.imdb_id, user_state=user_state, diff --git a/app/schemas/mediaserver.py b/app/schemas/mediaserver.py index 10d15710..daf88112 100644 --- a/app/schemas/mediaserver.py +++ b/app/schemas/mediaserver.py @@ -43,7 +43,7 @@ class RefreshMediaItem(BaseModel): # 标题 title: Optional[str] = None # 年份 - year: Optional[str] = None + year: Optional[Union[str, int]] = None # 类型 type: Optional[MediaType] = None # 类别 @@ -110,7 +110,7 @@ class MediaServerItem(BaseModel): # 原标题 original_title: Optional[str] = None # 年份 - year: Optional[str] = None + year: Optional[Union[str, int]] = None # TMDBID tmdbid: Optional[int] = None # IMDBID