Skip to content

Commit 7c872c4

Browse files
committed
fix: 保持相册广播合并发送
1 parent 68106ad commit 7c872c4

1 file changed

Lines changed: 84 additions & 13 deletions

File tree

handlers/broadcast.py

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import time
22
import re
33
import logging
4-
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
4+
import html
5+
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto, InputMediaVideo
56
from telegram.ext import ContextTypes
67
from telegram.constants import ParseMode
78
from telegram.error import BadRequest, Forbidden
@@ -17,12 +18,26 @@
1718
logger = logging.getLogger("Broadcast")
1819
# 内存缓存:互动按钮冷却
1920
click_cooldown = {}
21+
# 内存缓存:媒体组暂存,用于相册广播
22+
media_group_cache = {} # media_group_id -> {"items": [..], "ts": float}
23+
msg_to_media_group = {} # admin msg_id -> media_group_id
24+
MEDIA_CACHE_TTL = 300 # 秒
2025

2126
async def handle_broadcast_input(update: Update, context: ContextTypes.DEFAULT_TYPE):
2227
"""终极管理员消息入口:处理广播选择、回复追踪、自动路由"""
2328
if update.effective_chat.id != ADMIN_ID: return
2429

2530
msg_id = update.message.message_id
31+
mgid = update.message.media_group_id
32+
33+
# 0. 如为相册,先缓存媒体,保持多图合并
34+
if mgid:
35+
media_group_cache.setdefault(mgid, {"items": [], "ts": time.time()})
36+
entry = media_group_cache[mgid]
37+
entry["ts"] = time.time()
38+
entry["items"].append(_extract_media(update.message))
39+
msg_to_media_group[msg_id] = mgid
40+
_cleanup_media_cache()
2641

2742
# 1. 检查是否是回复消息 (精准追踪)
2843
reply_to = update.message.reply_to_message
@@ -59,6 +74,28 @@ async def handle_broadcast_input(update: Update, context: ContextTypes.DEFAULT_T
5974

6075
await update.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode=ParseMode.MARKDOWN)
6176

77+
def _extract_media(message):
78+
"""提取相册媒体所需信息"""
79+
base = {
80+
"caption": message.caption or "",
81+
}
82+
if message.photo:
83+
fid = message.photo[-1].file_id
84+
return {**base, "type": "photo", "file_id": fid}
85+
if message.video:
86+
return {**base, "type": "video", "file_id": message.video.file_id}
87+
return None
88+
89+
def _cleanup_media_cache():
90+
now = time.time()
91+
expired = [k for k, v in media_group_cache.items() if now - v["ts"] > MEDIA_CACHE_TTL]
92+
for k in expired:
93+
media_group_cache.pop(k, None)
94+
# 同步清理 msg_to_media_group
95+
for mid, mgid in list(msg_to_media_group.items()):
96+
if mgid not in media_group_cache:
97+
msg_to_media_group.pop(mid, None)
98+
6299
async def execute_reply_broadcast(update: Update, context: ContextTypes.DEFAULT_TYPE, targets: dict):
63100
"""精准回复:根据历史映射,去目标群 Reply 对应的那条消息"""
64101
success = 0
@@ -106,7 +143,7 @@ async def callback_broadcast_exec(update: Update, context: ContextTypes.DEFAULT_
106143
"signal": ("sub_signal", "topic_signal", "信号", True),
107144
"general": (None, None, "公共频道", False)
108145
}
109-
146+
110147
conf = routes.get(action)
111148
if not conf:
112149
await query.answer("未知类型")
@@ -135,19 +172,53 @@ async def callback_broadcast_exec(update: Update, context: ContextTypes.DEFAULT_
135172

136173
success = 0
137174
sent_map = {}
175+
media_group_id = msg_to_media_group.get(msg_id)
176+
media_items = media_group_cache.get(media_group_id, {}).get("items") if media_group_id else None
177+
is_album = media_items and len(media_items) > 0
138178

139179
for t in targets:
140180
try:
141-
sent = await context.bot.copy_message(
142-
chat_id=t["id"],
143-
from_chat_id=ADMIN_ID,
144-
message_id=msg_id,
145-
message_thread_id=t["topic"],
146-
reply_markup=kb,
147-
protect_content=True
148-
)
149-
success += 1
150-
sent_map[str(t["id"])] = sent.message_id
181+
# 相册路径:保持多媒体合并
182+
if is_album:
183+
media_payload = []
184+
for idx, item in enumerate(media_items):
185+
if not item:
186+
continue
187+
caption = html.escape(item["caption"]) if idx == 0 and item.get("caption") else None
188+
if item["type"] == "photo":
189+
media_payload.append(InputMediaPhoto(media=item["file_id"], caption=caption, parse_mode=ParseMode.HTML))
190+
elif item["type"] == "video":
191+
media_payload.append(InputMediaVideo(media=item["file_id"], caption=caption, parse_mode=ParseMode.HTML))
192+
sent_album = await context.bot.send_media_group(
193+
chat_id=t["id"],
194+
media=media_payload,
195+
message_thread_id=t["topic"],
196+
protect_content=True
197+
)
198+
success += 1
199+
# 记录第一条映射,便于回复追踪
200+
if sent_album:
201+
sent_map[str(t["id"])] = sent_album[0].message_id
202+
# 信号类型补发互动按钮
203+
if kb:
204+
await context.bot.send_message(
205+
chat_id=t["id"],
206+
text="📈 信号互动",
207+
reply_markup=kb,
208+
message_thread_id=t["topic"],
209+
protect_content=True
210+
)
211+
else:
212+
sent = await context.bot.copy_message(
213+
chat_id=t["id"],
214+
from_chat_id=ADMIN_ID,
215+
message_id=msg_id,
216+
message_thread_id=t["topic"],
217+
reply_markup=kb,
218+
protect_content=True
219+
)
220+
success += 1
221+
sent_map[str(t["id"])] = sent.message_id
151222

152223
# [修复 2] 捕获具体的发送错误,清理死群
153224
except (BadRequest, Forbidden) as e:
@@ -221,4 +292,4 @@ async def callback_signal_btn(update: Update, context: ContextTypes.DEFAULT_TYPE
221292
else:
222293
await query.answer("✅ 已记录")
223294
except Exception:
224-
await query.answer()
295+
await query.answer()

0 commit comments

Comments
 (0)