1+ import asyncio
12import re
23import threading
3- import uuid
4- from pathlib import Path
54from threading import Event
65from typing import Optional , List , Dict , Callable
76from urllib .parse import urljoin
87
98import telebot
9+ from telegramify_markdown import standardize , telegramify
10+ from telegramify_markdown .type import ContentTypes , SentType
1011from telebot import apihelper
11- from telebot .types import InputFile , InlineKeyboardMarkup , InlineKeyboardButton
12+ from telebot .types import InlineKeyboardMarkup , InlineKeyboardButton
1213from telebot .types import InputMediaPhoto
1314
1415from app .core .config import settings
1516from app .core .context import MediaInfo , Context
1617from app .core .metainfo import MetaInfo
18+ from app .helper .thread import ThreadHelper
1719from app .log import logger
1820from app .utils .common import retry
1921from app .utils .http import RequestUtils
@@ -52,7 +54,7 @@ def __init__(self, TELEGRAM_TOKEN: Optional[str] = None, TELEGRAM_CHAT_ID: Optio
5254 else :
5355 apihelper .proxy = settings .PROXY
5456 # bot
55- _bot = telebot .TeleBot (self ._telegram_token , parse_mode = "Markdown " )
57+ _bot = telebot .TeleBot (self ._telegram_token , parse_mode = "MarkdownV2 " )
5658 # 记录句柄
5759 self ._bot = _bot
5860 # 获取并存储bot用户名用于@检测
@@ -236,12 +238,14 @@ def send_msg(self, title: str, text: Optional[str] = None, image: Optional[str]
236238 return False
237239
238240 try :
239- if text :
240- # 对text进行Markdown特殊字符转义
241- text = re .sub (r"([_`])" , r"\\\1" , text )
242- caption = f"*{ title } *\n { text } "
241+ if title and text :
242+ caption = f"**{ title } **\n { text } "
243+ elif title :
244+ caption = f"**{ title } **"
245+ elif text :
246+ caption = text
243247 else :
244- caption = f"* { title } * "
248+ caption = " "
245249
246250 if link :
247251 caption = f"{ caption } \n [查看详情]({ link } )"
@@ -499,7 +503,7 @@ def __edit_message(self, chat_id: str, message_id: int, text: str,
499503
500504 if image :
501505 # 如果有图片,使用edit_message_media
502- media = InputMediaPhoto (media = image , caption = text , parse_mode = "Markdown " )
506+ media = InputMediaPhoto (media = image , caption = standardize ( text ) , parse_mode = "MarkdownV2 " )
503507 self ._bot .edit_message_media (
504508 chat_id = chat_id ,
505509 message_id = message_id ,
@@ -511,58 +515,129 @@ def __edit_message(self, chat_id: str, message_id: int, text: str,
511515 self ._bot .edit_message_text (
512516 chat_id = chat_id ,
513517 message_id = message_id ,
514- text = text ,
515- parse_mode = "Markdown " ,
518+ text = standardize ( text ) ,
519+ parse_mode = "MarkdownV2 " ,
516520 reply_markup = reply_markup
517521 )
518522 return True
519523 except Exception as e :
520524 logger .error (f"编辑消息失败:{ str (e )} " )
521525 return False
522526
523- @retry (RetryException , logger = logger )
524527 def __send_request (self , userid : Optional [str ] = None , image = "" , caption = "" ,
525528 reply_markup : Optional [InlineKeyboardMarkup ] = None ) -> bool :
526529 """
527530 向Telegram发送报文
528531 :param reply_markup: 内联键盘
529532 """
530- if image :
531- res = RequestUtils (proxies = settings .PROXY , ua = settings .NORMAL_USER_AGENT ).get_res (image )
532- if res is None :
533- raise Exception ("获取图片失败" )
534- if res .content :
535- # 使用随机标识构建图片文件的完整路径,并写入图片内容到文件
536- image_file = Path (settings .TEMP_PATH ) / "telegram" / str (uuid .uuid4 ())
537- if not image_file .parent .exists ():
538- image_file .parent .mkdir (parents = True , exist_ok = True )
539- image_file .write_bytes (res .content )
540- photo = InputFile (image_file )
541- # 发送图片到Telegram
542- ret = self ._bot .send_photo (chat_id = userid or self ._telegram_chat_id ,
543- photo = photo ,
544- caption = caption ,
545- parse_mode = "Markdown" ,
546- reply_markup = reply_markup )
547- if ret is None :
548- raise RetryException ("发送图片消息失败" )
549- return True
550- # 按4096分段循环发送消息
551- ret = None
552- if len (caption ) > 4095 :
553- for i in range (0 , len (caption ), 4095 ):
554- ret = self ._bot .send_message (chat_id = userid or self ._telegram_chat_id ,
555- text = caption [i :i + 4095 ],
556- parse_mode = "Markdown" ,
557- reply_markup = reply_markup if i == 0 else None )
558- else :
559- ret = self ._bot .send_message (chat_id = userid or self ._telegram_chat_id ,
560- text = caption ,
561- parse_mode = "Markdown" ,
562- reply_markup = reply_markup )
563- if ret is None :
564- raise RetryException ("发送文本消息失败" )
565- return True if ret else False
533+ kwargs = {
534+ 'chat_id' : userid or self ._telegram_chat_id ,
535+ 'parse_mode' : "MarkdownV2" ,
536+ 'reply_markup' : reply_markup
537+ }
538+
539+ try :
540+ # 处理图片
541+ image = self .__process_image (image ) if image else None
542+
543+ # 图片消息的标题长度限制为1024,文本消息为4096
544+ caption_limit = 1024 if image else 4096
545+ if len (caption ) < caption_limit :
546+ ret = self .__send_short_message (image , caption , ** kwargs )
547+ else :
548+ sent_idx = set ()
549+ ret = self .__send_long_message (image , caption , sent_idx , ** kwargs )
550+
551+ return ret is not None
552+ except Exception as e :
553+ logger .error (f"发送Telegram消息���败: { e } " )
554+ return False
555+
556+ @retry (RetryException , logger = logger )
557+ def __process_image (self , image_url : str ) -> bytes :
558+ """
559+ 处理图片URL,获取图片内容
560+ """
561+ try :
562+ res = RequestUtils (
563+ proxies = settings .PROXY ,
564+ ua = settings .NORMAL_USER_AGENT
565+ ).get_res (image_url )
566+
567+ if not res or not res .content :
568+ raise RetryException ("获取图片失败" )
569+
570+ return res .content
571+ except Exception as e :
572+ raise
573+
574+ @retry (RetryException , logger = logger )
575+ def __send_short_message (self , image : Optional [bytes ], caption : str , ** kwargs ):
576+ """
577+ 发送短消息
578+ """
579+ try :
580+ if image :
581+ return self ._bot .send_photo (
582+ photo = image ,
583+ caption = standardize (caption ),
584+ ** kwargs
585+ )
586+ else :
587+ return self ._bot .send_message (
588+ text = standardize (caption ),
589+ ** kwargs
590+ )
591+ except Exception as e :
592+ raise RetryException (f"发送{ '图片' if image else '文本' } 消息失败" )
593+
594+ @retry (RetryException , logger = logger )
595+ def __send_long_message (self , image : Optional [bytes ], caption : str , sent_idx : set , ** kwargs ):
596+ """
597+ 发送长消息
598+ """
599+ try :
600+ reply_markup = kwargs .pop ("reply_markup" , None )
601+
602+ boxs : SentType = ThreadHelper ().submit (lambda x : asyncio .run (telegramify (x )), caption ).result ()
603+
604+ ret = None
605+ for i , item in enumerate (boxs ):
606+ if i in sent_idx :
607+ # 跳过已发送消息
608+ continue
609+
610+ current_reply_markup = reply_markup if i == 0 else None
611+
612+ if item .content_type == ContentTypes .TEXT and (i != 0 or not image ):
613+ ret = self ._bot .send_message (** kwargs ,
614+ text = item .content ,
615+ reply_markup = current_reply_markup
616+ )
617+
618+ elif item .content_type == ContentTypes .PHOTO or (image and i == 0 ):
619+ ret = self ._bot .send_photo (** kwargs ,
620+ photo = (getattr (item , "file_name" , "" ),
621+ getattr (item , "file_data" , image )),
622+ caption = getattr (item , "caption" , item .content ),
623+ reply_markup = current_reply_markup
624+ )
625+
626+ elif item .content_type == ContentTypes .FILE :
627+ ret = self ._bot .send_document (** kwargs ,
628+ document = (item .file_name , item .file_data ),
629+ caption = item .caption ,
630+ reply_markup = current_reply_markup
631+ )
632+
633+ sent_idx .add (i )
634+
635+ return ret
636+ except Exception as e :
637+ try :
638+ raise RetryException (f"消息 [{ i + 1 } /{ len (boxs )} ] 发送失败" ) from e
639+ except NameError :
640+ raise RetryException ("发送长消息失败" ) from e
566641
567642 def register_commands (self , commands : Dict [str , dict ]):
568643 """
0 commit comments