Spaces:
Running
Running
File size: 8,115 Bytes
c5b9686 943860b e1abaf3 943860b dee9359 943860b e42c87a 943860b e42c87a 943860b e42c87a 943860b e42c87a dee9359 943860b e42c87a 943860b e42c87a 943860b e42c87a 943860b dee9359 943860b dee9359 e42c87a 7243eb3 50d24f5 e42c87a dee9359 943860b dee9359 e42c87a dee9359 e42c87a dee9359 e42c87a dee9359 e42c87a dee9359 e42c87a 943860b e42c87a 943860b e42c87a 943860b e42c87a dee9359 e42c87a 943860b e42c87a dee9359 943860b e42c87a 943860b e42c87a dee9359 e42c87a 943860b e42c87a dee9359 943860b e42c87a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage, ImageMessage, AudioMessage, AudioSendMessage
import os
import traceback
class LineBotApp:
"""
Line Bot 應用程式類別,負責處理 Line 平台的互動,包括 webhook 接收、訊息處理和狀態管理。
"""
def __init__(self, channel_access_token, channel_secret, chatbot):
"""
初始化 LineBotApp 實例。
"""
self.line_bot_api = LineBotApi(channel_access_token)
self.line_handler = WebhookHandler(channel_secret)
self.chatbot = chatbot
self.working_status = os.getenv("DEFALUT_TALKING", default="false").lower() == "true"
print(f"Default working status: {self.working_status}")
# 啟動/停用關鍵字保持不變
self.activation_word = "嘿橘橘"
self.deactivation_word = "退下橘橘"
self.bot_activated_states = {}
self._setup_message_handler()
def _setup_message_handler(self):
"""
設定 Line Message Handler。
"""
@self.line_handler.add(MessageEvent, message=(ImageMessage, TextMessage, AudioMessage))
def handle_message_event(event):
# 將事件傳遞給 handle_message 處理
self.handle_message(event)
def handle_webhook(self, body, signature):
"""
處理 Line Webhook 請求。
"""
try:
self.line_handler.handle(body, signature)
return "ok"
except InvalidSignatureError:
print("Invalid signature error.")
raise HTTPException(status_code=400, detail="Invalid signature")
except Exception as e:
print(f"Error handling webhook: {e}")
traceback.print_exc()
return "ok"
def handle_message(self, event):
"""
處理 Line 訊息事件,包含啟動/停用邏輯。
"""
user_id = event.source.user_id # 發送訊息的使用者 ID
source_id = self._get_source_id(event) # 訊息來源 ID (user/group/room)
print(f"Received message event from source_id: {source_id}, user_id: {user_id}, type: {event.message.type}")
# 優先處理文字訊息中的控制指令
if isinstance(event.message, TextMessage):
message_text = event.message.text.strip()
# 檢查是否為啟動指令
if message_text == self.activation_word:
print(f"Activation command received for {source_id}")
self.bot_activated_states[source_id] = True
try:
self.line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text="""喵!橘橘來囉~有什麼可以協助你的嗎?文生圖=["生成一張","畫一張"],編輯圖片=["編輯圖片","幫我改圖","編輯這張圖"]增強檢索=["開啟查詢模式","關閉查詢模式"]""")
)
except Exception as reply_err:
print(f"Error replying to activation command: {reply_err}")
return # 處理完啟動指令後結束
# 檢查是否為停用指令
elif message_text == self.deactivation_word:
print(f"Deactivation command received for {source_id}")
self.bot_activated_states[source_id] = False
try:
self.line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text="喵~橘橘先去睡覺了,需要我時再叫我~")
)
except Exception as reply_err:
print(f"Error replying to deactivation command: {reply_err}")
return # 處理完停用指令後結束
# --- 如果不是控制指令,檢查是否已啟動 ---
# 從狀態字典獲取狀態,如果字典中沒有,則使用預設的 self.working_status (現在是 False)
is_activated = self.bot_activated_states.get(source_id, self.working_status)
print(f"Bot activated state for {source_id}: {is_activated}")
# 如果未啟動,則忽略所有非控制指令的訊息
if not is_activated:
print(f"Bot not activated for {source_id}. Ignoring message.")
return # 直接結束,不處理
# --- 如果已啟動,則根據訊息類型轉發給 ChatBot ---
print(f"Bot is activated for {source_id}. Processing message...")
try:
if isinstance(event.message, TextMessage):
# 處理文字訊息 (已排除控制指令)
self.chatbot.handle_text_message(event, self.line_bot_api)
elif isinstance(event.message, ImageMessage):
# 處理圖片訊息
self.chatbot.handle_image_message(event, self.line_bot_api, self) # 傳遞 self (LineBotApp 實例)
elif isinstance(event.message, AudioMessage):
# 處理語音訊息
self.chatbot.handle_audio_message(event, self.line_bot_api, self) # 傳遞 self (LineBotApp 實例)
# 可以根據需要添加對其他訊息類型 (如貼圖、位置等) 的處理
else:
print(f"Ignoring unsupported message type: {event.message.type}")
except Exception as e:
# 捕捉 ChatBot 處理過程中可能發生的錯誤
print(f"Error during chatbot message handling: {e}")
traceback.print_exc()
# 可以選擇是否回覆通用錯誤訊息給使用者
try:
self.line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text="喵~糟糕,橘橘好像有點問題,請稍後再試一次!")
)
except Exception as reply_error:
print(f"Failed to send error reply after chatbot error: {reply_error}")
def _get_source_id(self, event):
"""
根據訊息來源取得唯一識別 ID。
"""
source_type = event.source.type
if source_type == 'user': return f"user_{event.source.user_id}"
elif source_type == 'group': return f"group_{event.source.group_id}"
elif source_type == 'room': return f"room_{event.source.room_id}"
user_id = getattr(event.source, 'user_id', None)
return f"unknown_{user_id}" if user_id else "unknown_source"
def get_image_url(self, message_id):
try:
message_content = self.line_bot_api.get_message_content(message_id)
tmp_dir = "/tmp/images"; os.makedirs(tmp_dir, exist_ok=True)
file_path = os.path.join(tmp_dir, f"{message_id}.jpg")
print(f"Downloading image {message_id} to {file_path}")
with open(file_path, 'wb') as fd:
for chunk in message_content.iter_content(): fd.write(chunk)
print(f"Image {message_id} downloaded successfully.")
return file_path
except Exception as e:
print(f"獲取圖片時出錯 (message_id: {message_id}): {e}"); traceback.print_exc(); return None
def get_audio_url(self, message_id):
try:
message_content = self.line_bot_api.get_message_content(message_id)
tmp_dir = "/tmp/audio"; os.makedirs(tmp_dir, exist_ok=True)
file_path = os.path.join(tmp_dir, f"{message_id}.m4a")
print(f"Downloading audio {message_id} to {file_path}")
with open(file_path, 'wb') as fd:
for chunk in message_content.iter_content(): fd.write(chunk)
print(f"Audio {message_id} downloaded successfully.")
return file_path
except Exception as e:
print(f"獲取語音檔案時出錯 (message_id: {message_id}): {e}"); traceback.print_exc(); return None |