fix: add try/except guards to all unprotected Telegram handler functions
All checks were successful
CD Pipeline / deploy (push) Successful in 1m29s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m29s
- Replace 2 silent `except Exception: pass` with logger.warning in handle_callback - Wrap _handle_await_callback, _handle_main_menu_callback with top-level try/except (query.answer on error) - Wrap _handle_complex_ai_response, _handle_simple_ai_response, _enhanced_keyword_matching, _process_await_input with top-level try/except (update.message.reply_text on error) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1412,134 +1412,155 @@ class TrendTelegramBot:
|
||||
|
||||
async def _handle_complex_ai_response(self, update: Update, ai_result: dict):
|
||||
"""Handle complex AI responses"""
|
||||
response_text = ai_result.get('response_text', '正在處理您的請求...')
|
||||
try:
|
||||
response_text = ai_result.get('response_text', '正在處理您的請求...')
|
||||
|
||||
await update.message.reply_text(
|
||||
f"⏳ 處理中...\n\n{response_text}",
|
||||
parse_mode='Markdown'
|
||||
)
|
||||
await update.message.reply_text(
|
||||
f"⏳ 處理中...\n\n{response_text}",
|
||||
parse_mode='Markdown'
|
||||
)
|
||||
|
||||
query_type = ai_result.get('query_type', 'general query')
|
||||
suggestions = self._get_query_suggestions(query_type)
|
||||
query_type = ai_result.get('query_type', 'general query')
|
||||
suggestions = self._get_query_suggestions(query_type)
|
||||
|
||||
await update.message.reply_text(
|
||||
"根據您的需求,建議使用以下功能:\n\n" +
|
||||
"\n".join([f"· {s}" for s in suggestions]) +
|
||||
"\n\n或透過選單直接操作:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
await update.message.reply_text(
|
||||
"根據您的需求,建議使用以下功能:\n\n" +
|
||||
"\n".join([f"· {s}" for s in suggestions]) +
|
||||
"\n\n或透過選單直接操作:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"[_handle_complex_ai_response] 處理失敗: {e}", exc_info=True)
|
||||
try:
|
||||
await update.message.reply_text("❌ 處理時發生錯誤,請稍後再試或使用 /help")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _handle_simple_ai_response(self, update: Update, ai_result: dict):
|
||||
"""Handle simple AI responses"""
|
||||
response_text = ai_result.get('response_text', '請問有什麼我可以幫您的嗎?')
|
||||
suggestions = ai_result.get('suggestions', [])
|
||||
show_menu = ai_result.get('show_menu', False)
|
||||
|
||||
if suggestions and not show_menu:
|
||||
# Show suggestions as buttons
|
||||
keyboard = []
|
||||
for i, suggestion in enumerate(suggestions):
|
||||
if i % 2 == 0:
|
||||
keyboard.append([])
|
||||
keyboard[-1].append({
|
||||
'text': suggestion,
|
||||
'callback_data': f'momo:cmd:suggestion:{suggestion.lower().replace(" ", "_")}'
|
||||
})
|
||||
try:
|
||||
response_text = ai_result.get('response_text', '請問有什麼我可以幫您的嗎?')
|
||||
suggestions = ai_result.get('suggestions', [])
|
||||
show_menu = ai_result.get('show_menu', False)
|
||||
|
||||
# Add main menu button
|
||||
keyboard.append([{'text': '主選單', 'callback_data': 'momo:menu:main'}])
|
||||
|
||||
await update.message.reply_text(
|
||||
response_text,
|
||||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||||
)
|
||||
else:
|
||||
# Show with main menu
|
||||
await update.message.reply_text(
|
||||
response_text,
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
if suggestions and not show_menu:
|
||||
# Show suggestions as buttons
|
||||
keyboard = []
|
||||
for i, suggestion in enumerate(suggestions):
|
||||
if i % 2 == 0:
|
||||
keyboard.append([])
|
||||
keyboard[-1].append({
|
||||
'text': suggestion,
|
||||
'callback_data': f'momo:cmd:suggestion:{suggestion.lower().replace(" ", "_")}'
|
||||
})
|
||||
|
||||
# Add main menu button
|
||||
keyboard.append([{'text': '主選單', 'callback_data': 'momo:menu:main'}])
|
||||
|
||||
await update.message.reply_text(
|
||||
response_text,
|
||||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||||
)
|
||||
else:
|
||||
# Show with main menu
|
||||
await update.message.reply_text(
|
||||
response_text,
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"[_handle_simple_ai_response] 處理失敗: {e}", exc_info=True)
|
||||
try:
|
||||
await update.message.reply_text("❌ 處理時發生錯誤,請稍後再試或使用 /help")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _enhanced_keyword_matching(self, update: Update, context, text: str):
|
||||
"""Enhanced keyword matching as fallback with Traditional Chinese responses"""
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Check for date range queries
|
||||
date_pattern = r'(\d{4}[./-]\d{2}[./-]\d{2})\s*[-~]\s*(\d{4}[./-]\d{2}[./-]\d{2})'
|
||||
date_match = re.search(date_pattern, text)
|
||||
|
||||
# Check for brand queries (Traditional Chinese and English)
|
||||
brands_mapping = {
|
||||
'neutrogena': 'Neutrogena',
|
||||
'aveeno': 'Aveeno',
|
||||
'nivea': 'Nivea',
|
||||
'loreal': 'Loreal',
|
||||
'shiseido': 'Shiseido',
|
||||
'sk-ii': 'SK-II',
|
||||
'kiehls': 'Kiehls',
|
||||
'clinique': 'Clinique',
|
||||
'dior': 'Dior',
|
||||
'chanel': 'Chanel'
|
||||
}
|
||||
|
||||
found_brands = []
|
||||
text_lower = text.lower()
|
||||
for brand_key, brand_name in brands_mapping.items():
|
||||
if brand_key in text_lower:
|
||||
found_brands.append(brand_name)
|
||||
|
||||
# Enhanced pattern matching with Traditional Chinese responses
|
||||
if date_match and any(word in text.lower() for word in ['momo', 'limited', 'flash', 'sale']):
|
||||
start_date = date_match.group(1).replace('/', '-').replace('.', '-')
|
||||
end_date = date_match.group(2).replace('/', '-').replace('.', '-')
|
||||
|
||||
brand_text = f"品牌:{', '.join(found_brands)}" if found_brands else "全部品牌"
|
||||
try:
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
await update.message.reply_text(
|
||||
f"⏳ 正在查詢 {start_date} 至 {end_date} 的促銷業績...\n\n"
|
||||
f"{brand_text}\n\n"
|
||||
f"請使用下方選單查看詳細分析:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
# Check for date range queries
|
||||
date_pattern = r'(\d{4}[./-]\d{2}[./-]\d{2})\s*[-~]\s*(\d{4}[./-]\d{2}[./-]\d{2})'
|
||||
date_match = re.search(date_pattern, text)
|
||||
|
||||
elif found_brands and any(word in text.lower() for word in ['momo', 'product', 'brand']):
|
||||
brand_list = ', '.join(found_brands)
|
||||
await update.message.reply_text(
|
||||
f"🔍 正在搜尋 {brand_list} 商品...\n\n"
|
||||
f"請使用下方選單查看詳細品牌分析:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
# Check for brand queries (Traditional Chinese and English)
|
||||
brands_mapping = {
|
||||
'neutrogena': 'Neutrogena',
|
||||
'aveeno': 'Aveeno',
|
||||
'nivea': 'Nivea',
|
||||
'loreal': 'Loreal',
|
||||
'shiseido': 'Shiseido',
|
||||
'sk-ii': 'SK-II',
|
||||
'kiehls': 'Kiehls',
|
||||
'clinique': 'Clinique',
|
||||
'dior': 'Dior',
|
||||
'chanel': 'Chanel'
|
||||
}
|
||||
|
||||
elif any(word in text for word in ['trend', 'popular']):
|
||||
await update.message.reply_text(
|
||||
"請選擇功能:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
found_brands = []
|
||||
text_lower = text.lower()
|
||||
for brand_key, brand_name in brands_mapping.items():
|
||||
if brand_key in text_lower:
|
||||
found_brands.append(brand_name)
|
||||
|
||||
elif any(word in text for word in ['search', 'query']):
|
||||
context.user_data['waiting_for'] = 'search_query'
|
||||
await update.message.reply_text(
|
||||
"🔍 請輸入搜尋關鍵字:",
|
||||
reply_markup=InlineKeyboardMarkup([[
|
||||
InlineKeyboardButton("❌ 取消", callback_data="menu:main")
|
||||
]])
|
||||
)
|
||||
# Enhanced pattern matching with Traditional Chinese responses
|
||||
if date_match and any(word in text.lower() for word in ['momo', 'limited', 'flash', 'sale']):
|
||||
start_date = date_match.group(1).replace('/', '-').replace('.', '-')
|
||||
end_date = date_match.group(2).replace('/', '-').replace('.', '-')
|
||||
|
||||
elif any(word in text for word in ['copy', 'generate']):
|
||||
context.user_data['waiting_for'] = 'copy_product'
|
||||
await update.message.reply_text(
|
||||
"✍️ 請輸入商品名稱:",
|
||||
reply_markup=InlineKeyboardMarkup([[
|
||||
InlineKeyboardButton("❌ 取消", callback_data="menu:main")
|
||||
]])
|
||||
)
|
||||
brand_text = f"品牌:{', '.join(found_brands)}" if found_brands else "全部品牌"
|
||||
|
||||
else:
|
||||
await update.message.reply_text(
|
||||
"🤔 收到您的訊息!請選擇下方功能,或輸入 /help 查看指令說明:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
await update.message.reply_text(
|
||||
f"⏳ 正在查詢 {start_date} 至 {end_date} 的促銷業績...\n\n"
|
||||
f"{brand_text}\n\n"
|
||||
f"請使用下方選單查看詳細分析:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
|
||||
elif found_brands and any(word in text.lower() for word in ['momo', 'product', 'brand']):
|
||||
brand_list = ', '.join(found_brands)
|
||||
await update.message.reply_text(
|
||||
f"🔍 正在搜尋 {brand_list} 商品...\n\n"
|
||||
f"請使用下方選單查看詳細品牌分析:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
|
||||
elif any(word in text for word in ['trend', 'popular']):
|
||||
await update.message.reply_text(
|
||||
"請選擇功能:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
|
||||
elif any(word in text for word in ['search', 'query']):
|
||||
context.user_data['waiting_for'] = 'search_query'
|
||||
await update.message.reply_text(
|
||||
"🔍 請輸入搜尋關鍵字:",
|
||||
reply_markup=InlineKeyboardMarkup([[
|
||||
InlineKeyboardButton("❌ 取消", callback_data="menu:main")
|
||||
]])
|
||||
)
|
||||
|
||||
elif any(word in text for word in ['copy', 'generate']):
|
||||
context.user_data['waiting_for'] = 'copy_product'
|
||||
await update.message.reply_text(
|
||||
"✍️ 請輸入商品名稱:",
|
||||
reply_markup=InlineKeyboardMarkup([[
|
||||
InlineKeyboardButton("❌ 取消", callback_data="menu:main")
|
||||
]])
|
||||
)
|
||||
|
||||
else:
|
||||
await update.message.reply_text(
|
||||
"🤔 收到您的訊息!請選擇下方功能,或輸入 /help 查看指令說明:",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"[_enhanced_keyword_matching] 處理失敗: {e}", exc_info=True)
|
||||
try:
|
||||
await update.message.reply_text("❌ 處理時發生錯誤,請稍後再試或使用 /help")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _get_query_suggestions(self, query_type: str) -> list:
|
||||
"""Get suggestions based on query type (Traditional Chinese)"""
|
||||
@@ -1585,65 +1606,72 @@ class TrendTelegramBot:
|
||||
|
||||
async def _process_await_input(self, update, context, await_type: str, text: str):
|
||||
"""處理所有 await: 狀態的用戶輸入,路由到對應 cmd"""
|
||||
import re
|
||||
chat_id = update.effective_chat.id
|
||||
try:
|
||||
import re
|
||||
chat_id = update.effective_chat.id
|
||||
|
||||
date_cmd_map = {
|
||||
'date_range_sales': ('sales', text.strip()),
|
||||
'date_top': ('top', text.strip()),
|
||||
'date_competitor': ('ppt', 'competitor ' + text.strip()),
|
||||
'date_ppt_daily': ('ppt', 'daily ' + text.strip()),
|
||||
'date_ppt_monthly': ('ppt', 'monthly ' + text.strip()),
|
||||
'date_ppt_vendor': ('ppt', 'vendor ' + text.strip()),
|
||||
'date_trend_month': ('trend', text.strip()),
|
||||
'date_trend_year': ('trend', text.strip()),
|
||||
'date_trend_quarter':('trend', text.strip()),
|
||||
'promo_range': ('promo', text.strip()),
|
||||
'search_compare': ('competitor', text.strip()),
|
||||
}
|
||||
goal_types = {
|
||||
'goal_daily': 'daily',
|
||||
'goal_monthly': 'monthly',
|
||||
'goal_quarterly': 'quarterly',
|
||||
'goal_half': 'half',
|
||||
'goal_yearly': 'yearly',
|
||||
}
|
||||
date_cmd_map = {
|
||||
'date_range_sales': ('sales', text.strip()),
|
||||
'date_top': ('top', text.strip()),
|
||||
'date_competitor': ('ppt', 'competitor ' + text.strip()),
|
||||
'date_ppt_daily': ('ppt', 'daily ' + text.strip()),
|
||||
'date_ppt_monthly': ('ppt', 'monthly ' + text.strip()),
|
||||
'date_ppt_vendor': ('ppt', 'vendor ' + text.strip()),
|
||||
'date_trend_month': ('trend', text.strip()),
|
||||
'date_trend_year': ('trend', text.strip()),
|
||||
'date_trend_quarter':('trend', text.strip()),
|
||||
'promo_range': ('promo', text.strip()),
|
||||
'search_compare': ('competitor', text.strip()),
|
||||
}
|
||||
goal_types = {
|
||||
'goal_daily': 'daily',
|
||||
'goal_monthly': 'monthly',
|
||||
'goal_quarterly': 'quarterly',
|
||||
'goal_half': 'half',
|
||||
'goal_yearly': 'yearly',
|
||||
}
|
||||
|
||||
if await_type in date_cmd_map:
|
||||
cmd, arg = date_cmd_map[await_type]
|
||||
await update.message.reply_text(f"⏳ 正在處理 `{text.strip()}`...", parse_mode='Markdown')
|
||||
import threading as _t
|
||||
_t.Thread(
|
||||
target=self._forward_cmd_to_openclaw,
|
||||
args=(cmd, arg, chat_id),
|
||||
daemon=True,
|
||||
).start()
|
||||
elif await_type in goal_types:
|
||||
period = goal_types[await_type]
|
||||
amount_str = re.sub(r'[^\d]', '', text)
|
||||
if not amount_str:
|
||||
if await_type in date_cmd_map:
|
||||
cmd, arg = date_cmd_map[await_type]
|
||||
await update.message.reply_text(f"⏳ 正在處理 `{text.strip()}`...", parse_mode='Markdown')
|
||||
import threading as _t
|
||||
_t.Thread(
|
||||
target=self._forward_cmd_to_openclaw,
|
||||
args=(cmd, arg, chat_id),
|
||||
daemon=True,
|
||||
).start()
|
||||
elif await_type in goal_types:
|
||||
period = goal_types[await_type]
|
||||
amount_str = re.sub(r'[^\d]', '', text)
|
||||
if not amount_str:
|
||||
await update.message.reply_text(
|
||||
"⚠️ 格式錯誤,請輸入純數字(例如:500000)",
|
||||
reply_markup=InlineKeyboardMarkup([[
|
||||
InlineKeyboardButton("🔙 返回目標管理", callback_data="menu:goals")
|
||||
]])
|
||||
)
|
||||
return
|
||||
await update.message.reply_text(
|
||||
"⚠️ 格式錯誤,請輸入純數字(例如:500000)",
|
||||
reply_markup=InlineKeyboardMarkup([[
|
||||
InlineKeyboardButton("🔙 返回目標管理", callback_data="menu:goals")
|
||||
]])
|
||||
f"⏳ 正在設定{period}目標 {int(amount_str):,} 元...",
|
||||
parse_mode='Markdown'
|
||||
)
|
||||
return
|
||||
await update.message.reply_text(
|
||||
f"⏳ 正在設定{period}目標 {int(amount_str):,} 元...",
|
||||
parse_mode='Markdown'
|
||||
)
|
||||
import threading as _t
|
||||
_t.Thread(
|
||||
target=self._forward_cmd_to_openclaw,
|
||||
args=('goal', f'set:{period}:{amount_str}', chat_id),
|
||||
daemon=True,
|
||||
).start()
|
||||
else:
|
||||
await update.message.reply_text(
|
||||
"⚠️ 無法識別輸入類型,請重新選擇功能",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
import threading as _t
|
||||
_t.Thread(
|
||||
target=self._forward_cmd_to_openclaw,
|
||||
args=('goal', f'set:{period}:{amount_str}', chat_id),
|
||||
daemon=True,
|
||||
).start()
|
||||
else:
|
||||
await update.message.reply_text(
|
||||
"⚠️ 無法識別輸入類型,請重新選擇功能",
|
||||
reply_markup=self._get_main_menu_keyboard()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"[_process_await_input] 處理失敗: {e}", exc_info=True)
|
||||
try:
|
||||
await update.message.reply_text("❌ 處理時發生錯誤,請稍後再試或使用 /help")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _process_search(self, update: Update, query: str):
|
||||
"""處理搜尋請求"""
|
||||
|
||||
Reference in New Issue
Block a user