fix: keep user commands and render answers as html

This commit is contained in:
ngfchl
2026-07-06 21:23:37 +08:00
parent 7c7f39e15b
commit a2c6c4c790
4 changed files with 26 additions and 30 deletions
-3
View File
@@ -24,7 +24,6 @@ async def cmd_bind(update: Update, context: ContextTypes.DEFAULT_TYPE):
f"📅 绑定时间: {existing['bound_at']}\n\n"
f"如需重新绑定,先 /unbind"
)
asyncio.create_task(auto_delete(update.message))
asyncio.create_task(auto_delete(reply))
return
@@ -35,7 +34,6 @@ async def cmd_bind(update: Update, context: ContextTypes.DEFAULT_TYPE):
f"请点击以下链接完成绑定:\n{url}\n\n"
"⏳ 链接 5 分钟内有效"
)
asyncio.create_task(auto_delete(update.message))
asyncio.create_task(auto_delete(reply, seconds=120))
@@ -48,7 +46,6 @@ async def cmd_unbind(update: Update, context: ContextTypes.DEFAULT_TYPE):
await Action.create(action="UNBIND", user_id=user.id, username=user.username)
else:
reply = await update.message.reply_text("❌ 你没有绑定的账号")
asyncio.create_task(auto_delete(update.message))
asyncio.create_task(auto_delete(reply))
-4
View File
@@ -55,7 +55,6 @@ async def cmd_checkin(update: Update, context: ContextTypes.DEFAULT_TYPE):
)
else:
reply = await update.message.reply_text("❌ 你今天已经签到过了")
asyncio.create_task(auto_delete(update.message))
asyncio.create_task(auto_delete(reply))
@@ -69,7 +68,6 @@ async def cmd_trivia(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.user_data["trivia_answer"] = q["a"]
context.user_data["trivia_options"] = q["opts"]
reply = await update.message.reply_text(text)
asyncio.create_task(auto_delete(update.message))
asyncio.create_task(auto_delete(reply))
@@ -85,7 +83,6 @@ async def cmd_score(update: Update, context: ContextTypes.DEFAULT_TYPE):
for s in scores:
text += f"🎮 {s.game_type}: {s.score} 分 (胜率 {s.wins}/{s.total})\n"
reply = await update.message.reply_text(text)
asyncio.create_task(auto_delete(update.message))
asyncio.create_task(auto_delete(reply))
@@ -101,7 +98,6 @@ async def cmd_rank(update: Update, context: ContextTypes.DEFAULT_TYPE):
medal = medals[i] if i < 3 else f" {i+1}."
text += f"{medal} @{s.username or s.user_id}{s.score}\n"
reply = await update.message.reply_text(text)
asyncio.create_task(auto_delete(update.message))
asyncio.create_task(auto_delete(reply))
+26 -21
View File
@@ -1,5 +1,6 @@
"""知识库问答 handler"""
import asyncio
import html
import logging
import re
@@ -26,10 +27,23 @@ def optimize_plain_answer(text: str) -> str:
return "*回答*\n" + "\n".join(f"- {line}" if not line.startswith(("-", "", "", "⚠️", "📚")) else line for line in lines)
def telegram_markdown(text: str) -> str:
"""把常见 Markdown 调整为 Telegram legacy Markdown 更容易接受的形式"""
text = re.sub(r"\*\*([^*]+)\*\*", r"*\1*", text)
text = re.sub(r"^#{1,3}\s+(.+)$", r"*\1*", text, flags=re.MULTILINE)
def markdown_to_html(text: str) -> str:
"""把常见 Markdown 转成 Telegram HTML,规避 Markdown 解析器对 _/链接/长文本的误判"""
code_blocks: list[str] = []
def stash_code_block(match: re.Match) -> str:
code = html.escape(match.group(1).strip())
code_blocks.append(f"<pre><code>{code}</code></pre>")
return f"@@CODE_BLOCK_{len(code_blocks) - 1}@@"
text = re.sub(r"```(?:\w+)?\n?(.*?)```", stash_code_block, text, flags=re.S)
text = html.escape(text)
text = re.sub(r"^#{1,3}\s+(.+)$", r"<b>\1</b>", text, flags=re.M)
text = re.sub(r"\*\*([^*\n]+)\*\*", r"<b>\1</b>", text)
text = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<b>\1</b>", text)
text = re.sub(r"`([^`\n]+)`", r"<code>\1</code>", text)
for i, block in enumerate(code_blocks):
text = text.replace(f"@@CODE_BLOCK_{i}@@", block)
return text
@@ -70,28 +84,21 @@ def split_answer(text: str, max_len: int = 3900, max_parts: int = 2) -> list[str
async def safe_send(update: Update, context: ContextTypes.DEFAULT_TYPE, text: str, markdown: bool | None = None):
"""优先回复原消息;Markdown 失败或原消息被删时自动降级。"""
"""优先回复原消息;Markdown 内容转 HTML 发送,失败自动降级纯文本"""
chat_id = update.effective_chat.id
use_markdown = looks_like_markdown(text) if markdown is None else markdown
send_text = telegram_markdown(text) if use_markdown else text
use_markup = looks_like_markdown(text) if markdown is None else markdown
send_text = markdown_to_html(text) if use_markup else text
async def _send_plain():
if use_markup:
try:
return await update.message.reply_text(text)
except Exception:
return await context.bot.send_message(chat_id=chat_id, text=text)
if use_markdown:
try:
return await update.message.reply_text(send_text, parse_mode="Markdown", disable_web_page_preview=True)
return await update.message.reply_text(send_text, parse_mode="HTML", disable_web_page_preview=True)
except Exception as e:
if "Message to be replied not found" in str(e):
try:
return await context.bot.send_message(chat_id=chat_id, text=send_text, parse_mode="Markdown", disable_web_page_preview=True)
return await context.bot.send_message(chat_id=chat_id, text=send_text, parse_mode="HTML", disable_web_page_preview=True)
except Exception:
return await context.bot.send_message(chat_id=chat_id, text=text)
logger.warning(f"Markdown 发送失败,降级普通文本: {e}")
return await _send_plain()
return await context.bot.send_message(chat_id=chat_id, text=text, disable_web_page_preview=True)
logger.warning(f"HTML 格式发送失败,降级普通文本: {e}")
try:
return await update.message.reply_text(text, disable_web_page_preview=True)
@@ -110,13 +117,11 @@ async def cmd_ask(update: Update, context: ContextTypes.DEFAULT_TYPE):
"例如: /ask 怎么安装收割机\n"
f"文档地址: {config.SITE_URL}"
)
asyncio.create_task(auto_delete(update.message, seconds=180))
asyncio.create_task(auto_delete(reply, seconds=180))
return
logger.info(f"📩 用户提问: [{update.message.from_user.username}] {query}")
searching_msg = await safe_send(update, context, "🔍 正在搜索知识库并整理详细答案...")
asyncio.create_task(auto_delete(update.message, seconds=180))
try:
from services.knowledge_base import ask_knowledge_base
-2
View File
@@ -25,7 +25,6 @@ async def cmd_night(update: Update, context: ContextTypes.DEFAULT_TYPE):
"/night on 开启\n"
"/night off 关闭"
)
asyncio.create_task(auto_delete(update.message))
return
cmd = args[0].lower()
@@ -38,7 +37,6 @@ async def cmd_night(update: Update, context: ContextTypes.DEFAULT_TYPE):
else:
reply = await update.message.reply_text("❓ 用法: /night on|off")
asyncio.create_task(auto_delete(update.message))
asyncio.create_task(auto_delete(reply))