feat:Telegram、Slack 支持按钮

This commit is contained in:
jxxghp
2025-06-15 15:34:06 +08:00
parent c534e3dcb8
commit 95a827e8a2
6 changed files with 918 additions and 134 deletions

View File

@@ -3,12 +3,12 @@ import threading
import uuid
from pathlib import Path
from threading import Event
from typing import Optional, List, Dict
from typing import Optional, List, Dict, Callable
from urllib.parse import urljoin
import telebot
from telebot import apihelper
from telebot.types import InputFile
from telebot.types import InputFile, InlineKeyboardMarkup, InlineKeyboardButton
from app.core.config import settings
from app.core.context import MediaInfo, Context
@@ -23,6 +23,7 @@ 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
_callback_handlers: Dict[str, Callable] = {} # 存储回调处理器
def __init__(self, TELEGRAM_TOKEN: Optional[str] = None, TELEGRAM_CHAT_ID: Optional[str] = None, **kwargs):
"""
@@ -57,7 +58,39 @@ class Telegram:
@_bot.message_handler(func=lambda message: True)
def echo_all(message):
RequestUtils(timeout=5).post_res(self._ds_url, json=message.json)
RequestUtils(timeout=15).post_res(self._ds_url, json=message.json)
@_bot.callback_query_handler(func=lambda call: True)
def callback_query(call):
"""
处理按钮点击回调
"""
try:
# 解析回调数据
callback_data = call.data
user_id = str(call.from_user.id)
logger.info(f"收到按钮回调:{callback_data},用户:{user_id}")
# 发送回调数据给主程序处理
callback_json = {
"callback_query": {
"id": call.id,
"from": call.from_user.to_dict(),
"message": call.message.to_dict(),
"data": callback_data
}
}
# 先确认回调避免用户看到loading状态
_bot.answer_callback_query(call.id)
# 发送给主程序处理
RequestUtils(timeout=15).post_res(self._ds_url, json=callback_json)
except Exception as e:
logger.error(f"处理按钮回调失败:{str(e)}")
_bot.answer_callback_query(call.id, "处理失败,请重试")
def run_polling():
"""
@@ -80,7 +113,8 @@ class Telegram:
return self._bot is not None
def send_msg(self, title: str, text: Optional[str] = None, image: Optional[str] = None,
userid: Optional[str] = None, link: Optional[str] = None) -> Optional[bool]:
userid: Optional[str] = None, link: Optional[str] = None,
buttons: Optional[List[List[dict]]] = None) -> Optional[bool]:
"""
发送Telegram消息
:param title: 消息标题
@@ -88,6 +122,7 @@ class Telegram:
:param image: 消息图片地址
:param userid: 用户ID如有则只发消息给该用户
:param link: 跳转链接
:param buttons: 按钮列表,格式:[[{"text": "按钮文本", "callback_data": "回调数据"}]]
:userid: 发送消息的目标用户ID为空则发给管理员
"""
if not self._telegram_token or not self._telegram_chat_id:
@@ -113,16 +148,27 @@ class Telegram:
else:
chat_id = self._telegram_chat_id
return self.__send_request(userid=chat_id, image=image, caption=caption)
# 创建按钮键盘
reply_markup = None
if buttons:
reply_markup = self._create_inline_keyboard(buttons)
return self.__send_request(userid=chat_id, image=image, caption=caption, reply_markup=reply_markup)
except Exception as msg_e:
logger.error(f"发送消息失败:{msg_e}")
return False
def send_medias_msg(self, medias: List[MediaInfo], userid: Optional[str] = None,
title: Optional[str] = None, link: Optional[str] = None) -> Optional[bool]:
title: Optional[str] = None, link: Optional[str] = None,
buttons: Optional[List[List[Dict]]] = None) -> Optional[bool]:
"""
发送媒体列表消息
:param medias: 媒体信息列表
:param userid: 用户ID如有则只发消息给该用户
:param title: 消息标题
:param link: 跳转链接
:param buttons: 按钮列表,格式:[[{"text": "按钮文本", "callback_data": "回调数据"}]]
"""
if not self._telegram_token or not self._telegram_chat_id:
return None
@@ -155,7 +201,12 @@ class Telegram:
else:
chat_id = self._telegram_chat_id
return self.__send_request(userid=chat_id, image=image, caption=caption)
# 创建按钮键盘
reply_markup = None
if buttons:
reply_markup = self._create_inline_keyboard(buttons)
return self.__send_request(userid=chat_id, image=image, caption=caption, reply_markup=reply_markup)
except Exception as msg_e:
logger.error(f"发送消息失败:{msg_e}")
@@ -163,9 +214,14 @@ class Telegram:
def send_torrents_msg(self, torrents: List[Context],
userid: Optional[str] = None, title: Optional[str] = None,
link: Optional[str] = None) -> Optional[bool]:
link: Optional[str] = None, buttons: Optional[List[List[Dict]]] = None) -> Optional[bool]:
"""
发送列表消息
:param torrents: Torrent信息列表
:param userid: 用户ID如有则只发消息给该用户
:param title: 消息标题
:param link: 跳转链接
:param buttons: 按钮列表,格式:[[{"text": "按钮文本", "callback_data": "回调数据"}]]
"""
if not self._telegram_token or not self._telegram_chat_id:
return None
@@ -200,17 +256,61 @@ class Telegram:
else:
chat_id = self._telegram_chat_id
# 创建按钮键盘
reply_markup = None
if buttons:
reply_markup = self._create_inline_keyboard(buttons)
return self.__send_request(userid=chat_id, caption=caption,
image=mediainfo.get_message_image())
image=mediainfo.get_message_image(), reply_markup=reply_markup)
except Exception as msg_e:
logger.error(f"发送消息失败:{msg_e}")
return False
@staticmethod
def _create_inline_keyboard(buttons: List[List[Dict]]) -> InlineKeyboardMarkup:
"""
创建内联键盘
:param buttons: 按钮配置,格式:[[{"text": "按钮文本", "callback_data": "回调数据", "url": "链接"}]]
:return: InlineKeyboardMarkup对象
"""
keyboard = []
for row in buttons:
button_row = []
for button in row:
if "url" in button:
# URL按钮
btn = InlineKeyboardButton(text=button["text"], url=button["url"])
else:
# 回调按钮
btn = InlineKeyboardButton(text=button["text"], callback_data=button["callback_data"])
button_row.append(btn)
keyboard.append(button_row)
return InlineKeyboardMarkup(keyboard)
def answer_callback_query(self, callback_query_id: int, text: Optional[str] = None,
show_alert: bool = False) -> Optional[bool]:
"""
回应回调查询
:param callback_query_id: 回调查询ID
:param text: 提示文本
:param show_alert: 是否显示弹窗提示
:return: 回应结果
"""
try:
self._bot.answer_callback_query(callback_query_id, text, show_alert)
return True
except Exception as e:
logger.error(f"回应回调查询失败:{str(e)}")
return False
@retry(Exception, logger=logger)
def __send_request(self, userid: Optional[str] = None, image="", caption="") -> bool:
def __send_request(self, userid: Optional[str] = None, image="", caption="",
reply_markup: Optional[InlineKeyboardMarkup] = None) -> bool:
"""
向Telegram发送报文
:param reply_markup: 内联键盘
"""
if image:
res = RequestUtils(proxies=settings.PROXY).get_res(image)
@@ -227,7 +327,8 @@ class Telegram:
ret = self._bot.send_photo(chat_id=userid or self._telegram_chat_id,
photo=photo,
caption=caption,
parse_mode="Markdown")
parse_mode="Markdown",
reply_markup=reply_markup)
if ret is None:
raise Exception("发送图片消息失败")
return True
@@ -237,11 +338,13 @@ class Telegram:
for i in range(0, len(caption), 4095):
ret = self._bot.send_message(chat_id=userid or self._telegram_chat_id,
text=caption[i:i + 4095],
parse_mode="Markdown")
parse_mode="Markdown",
reply_markup=reply_markup if i == 0 else None)
else:
ret = self._bot.send_message(chat_id=userid or self._telegram_chat_id,
text=caption,
parse_mode="Markdown")
parse_mode="Markdown",
reply_markup=reply_markup)
if ret is None:
raise Exception("发送文本消息失败")
return True if ret else False