# matrix_service.py from nio import ( AsyncClient, LoginResponse, RoomMessageText, InviteMemberEvent, ClientConfig, MegolmEvent, exceptions, KeyVerificationEvent, KeyVerificationStart, KeyVerificationCancel, KeyVerificationKey, KeyVerificationMac, ToDeviceError ) import config import asyncio import schedule import requests from datetime import datetime, timedelta import os import json from typing import Optional, Tuple import aiofiles from openai import OpenAI STORE_FOLDER = "store/" SESSION_DETAILS_FILE = "credentials.json" MODEL_SELECT_RETRIES = 3 # 模型选择最大重试次数 # 初始化XAI客户端 xai_client = OpenAI( api_key=config.XAI_API_KEY, base_url=config.XAI_BASE_URL, ) class MatrixService: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(MatrixService, cls).__new__(cls) # 確保存儲目錄存在 if not os.path.exists(STORE_FOLDER): os.makedirs(STORE_FOLDER) # 創建客戶端配置 client_config = ClientConfig( store_sync_tokens=True, encryption_enabled=True ) # 初始化客戶端 cls._instance.client = AsyncClient( config.MATRIX_SERVER, config.USERNAME, device_id=config.DEVICE_ID, store_path=STORE_FOLDER, config=client_config, ) cls._instance.is_logged_in = False cls._instance.start_time = datetime.now() # 用戶AI對話狀態和會話歷史 # (room_id, user_id) -> (last_activity_time, messages, model_key) cls._instance.ai_chat_users = {} # 用户等待AI响应状态 # (room_id, user_id) -> (waiting_since, last_question) cls._instance.ai_waiting_users = {} # 用户模型使用记录 # user_id -> {model_key -> (last_reset_time, count)} cls._instance.model_usage = {} # 模型选择状态 # (room_id, user_id) -> (retry_count) cls._instance.model_select_state = {} # 已發送歡迎消息的房間記錄 cls._instance.welcomed_rooms = set() # 记录所有私聊房间信息 # room_id -> user_id cls._instance.private_chat_rooms = {} # 添加事件回調 cls._instance.client.add_event_callback( cls._instance.message_callback, RoomMessageText ) cls._instance.client.add_event_callback( cls._instance.encrypted_message_callback, MegolmEvent ) cls._instance.client.add_event_callback( cls._instance.invite_callback, InviteMemberEvent ) # 添加设备验证回调 cls._instance.client.add_to_device_callback( cls._instance.to_device_callback, (KeyVerificationEvent,) ) # 啟動AI超時檢查任務 asyncio.create_task(cls._instance.check_ai_chat_timeout()) # 启动AI响应超时检查任务 asyncio.create_task(cls._instance.check_ai_response_timeout()) return cls._instance async def to_device_callback(self, event): """处理设备验证事件,自动接受所有验证请求""" try: if isinstance(event, KeyVerificationStart): print(f"收到验证请求,来自 {event.sender}") # 自动接受所有验证请求,不限制验证方式 resp = await self.client.accept_key_verification(event.transaction_id) if isinstance(resp, ToDeviceError): print(f"接受验证请求失败: {resp}") return sas = self.client.key_verifications[event.transaction_id] todevice_msg = sas.share_key() resp = await self.client.to_device(todevice_msg) if isinstance(resp, ToDeviceError): print(f"发送设备密钥失败: {resp}") elif isinstance(event, KeyVerificationCancel): print(f"验证被 {event.sender} 取消,原因: {event.reason}") elif isinstance(event, KeyVerificationKey): print(f"收到验证密钥,自动接受验证") resp = await self.client.confirm_short_auth_string(event.transaction_id) if isinstance(resp, ToDeviceError): print(f"确认验证失败: {resp}") elif isinstance(event, KeyVerificationMac): sas = self.client.key_verifications[event.transaction_id] try: todevice_msg = sas.get_mac() resp = await self.client.to_device(todevice_msg) if isinstance(resp, ToDeviceError): print(f"发送MAC失败: {resp}") else: print(f"验证完成,设备已验证: {sas.verified_devices}") # 重新信任所有已验证的设备 for device_id in sas.verified_devices: if event.sender in self.client.device_store: if device_id in self.client.device_store[event.sender]: olm_device = ( self.client.device_store[event.sender][device_id] ) self.client.verify_device(olm_device) print( f"已信任设备 {device_id} 来自用户 {event.sender}") except Exception as e: print(f"处理MAC时出错: {e}") except Exception as e: print(f"处理设备验证事件时出错: {e}") def trust_devices(self, user_id: str) -> None: """信任用戶的所有設備,包括自己的设备""" print(f"信任用戶 {user_id} 的所有設備") try: if user_id not in self.client.device_store: print(f"找不到用户 {user_id} 的设备信息") return for device_id, olm_device in self.client.device_store[user_id].items(): # 不跳过任何设备,包括自己的设备 print(f"信任設備: {device_id}") self.client.verify_device(olm_device) print(f"已信任设备 {device_id} 来自用户 {user_id}") except Exception as e: print(f"信任设备时出错: {e}") async def check_ai_response_timeout(self): """检查AI响应超时""" while True: try: current_time = datetime.now() timeout_users = [] for (room_id, user_id), (waiting_since, _) in ( self.ai_waiting_users.items() ): timeout = timedelta( minutes=config.AI_RESPONSE_TIMEOUT_MINUTES ) if current_time - waiting_since > timeout: timeout_users.append((room_id, user_id)) # 处理超时的用户 for room_id, user_id in timeout_users: chat_key = (room_id, user_id) if chat_key in self.ai_waiting_users: del self.ai_waiting_users[chat_key] if chat_key in self.ai_chat_users: del self.ai_chat_users[chat_key] await self.send_message( "AI系统貌似并没有反应了,目前先退出这次AI对话," "可能有其他问题,并反馈给管理员知道并等待修复", room_id=room_id, encrypted=True ) await asyncio.sleep(60) # 每分钟检查一次 except Exception as e: print(f"检查AI响应超时时出错: {e}") await asyncio.sleep(60) def get_model_list_message(self, user_id: str) -> str: """生成模型列表消息""" message = "请问你需要使用下面模型?\n\n" # 首先添加XAI模型作为第一个选项 # 获取XAI使用统计 xai_usage = self.get_model_usage(user_id, "xai") if config.XAI_DAILY_LIMIT == -1: xai_limit_text = "无限制" else: xai_limit_text = f"每天最多{config.XAI_DAILY_LIMIT}条" if xai_usage > 0: xai_limit_text += f"(已使用{xai_usage}条)" message += f"(1){config.XAI_MODEL_DIYNAME} {xai_limit_text}\n" # 然后添加其他OpenRouter模型 for i, (key, model) in enumerate(config.OPENROUTER_MODELS.items(), 2): # 获取使用统计 usage = self.get_model_usage(user_id, key) # 构建显示文本 if model["daily_limit"] == -1: limit_text = "无限制" else: limit_text = f"每天最多{model['daily_limit']}条" if usage > 0: limit_text += f"(已使用{usage}条)" message += f"({i}){model['name']} {limit_text}\n" message += ( "\n如果你想使用第一个默认模型,请回复1或者'好的'就开始AI对话," "如果使用其他模型请输入对应的数字就可以啦" ) return message def get_model_key_by_index(self, index: int) -> Optional[str]: """根据索引获取模型key""" if index == 1: # XAI模型 return "xai" try: return list(config.OPENROUTER_MODELS.keys())[index - 2] except IndexError: return None async def chat_with_xai(self, message: str, history: list) -> tuple[str, Optional[str]]: """与XAI API交互获取回复""" try: if not config.XAI_API_KEY: return "抱歉,XAI服务暂时不可用,请选择其他模型。", None completion = xai_client.chat.completions.create( model="grok-beta", messages=history + [{"role": "user", "content": message}], ) content = completion.choices[0].message.content # 检查是否有富文本格式 formatted_content = None if "```" in content: formatted_content = content.replace( "```", "
", 1
).replace("```", "", 1)
return content, formatted_content
except Exception as e:
print(f"XAI API调用出错: {e}")
return "抱歉,XAI服务暂时无法使用,请稍后再试。", None
def get_model_usage(self, user_id: str, model_key: str) -> int:
"""获取用户对特定模型的当天使用次数"""
if user_id not in self.model_usage:
self.model_usage[user_id] = {}
if model_key not in self.model_usage[user_id]:
self.model_usage[user_id][model_key] = (datetime.now(), 0)
last_reset, count = self.model_usage[user_id][model_key]
# 检查是否需要重置计数
if datetime.now().date() > last_reset.date():
self.model_usage[user_id][model_key] = (datetime.now(), 0)
return 0
return count
def increment_model_usage(self, user_id: str, model_key: str) -> int:
"""增加用户对特定模型的使用次数,返回新的使用次数"""
current_usage = self.get_model_usage(user_id, model_key)
self.model_usage[user_id][model_key] = (
self.model_usage[user_id][model_key][0],
current_usage + 1
)
return current_usage + 1
def check_model_limit(
self,
user_id: str,
model_key: str
) -> Tuple[bool, Optional[str]]:
"""检查模型使用限制,返回(是否可用, 提示消息)"""
if model_key == "xai": # XAI模型使用XAI_DAILY_LIMIT
daily_limit = config.XAI_DAILY_LIMIT
else:
model_info = config.OPENROUTER_MODELS[model_key]
daily_limit = model_info["daily_limit"]
if daily_limit == -1:
return True, None
current_usage = self.get_model_usage(user_id, model_key)
if current_usage >= daily_limit:
model_name = config.XAI_MODEL_DIYNAME if model_key == "xai" else config.OPENROUTER_MODELS[
model_key]["name"]
return False, (
f"感谢你使用{model_name},已经到达当天最大条数,"
f"欢迎你明天之后继续使用该模型!你也可以使用其他的模型!"
)
# 检查是否需要发送提醒
remaining = daily_limit - current_usage
if remaining in [6, 2, 1]:
return True, f"(温馨提示:今天还剩余{remaining-1}条对话次数)"
return True, None
def process_model_selection(
self,
user_id: str,
room_id: str,
message: str
) -> Optional[str]:
"""处理模型选择,返回选择的模型key或None"""
chat_key = (room_id, user_id)
# 检查是否是默认选择
if message.lower() in ['1', '好的']:
return "xai" # 返回XAI作为默认模型
# 尝试解析数字选择
try:
index = int(message)
model_key = self.get_model_key_by_index(index)
if model_key:
return model_key
except ValueError:
pass
# 更新重试次数
if chat_key not in self.model_select_state:
self.model_select_state[chat_key] = 1
else:
self.model_select_state[chat_key] += 1
return None
async def reestablish_room_session(self, room_id: str, user_id: str):
"""重新建立房间的加密会话"""
try:
print(f"正在重新建立与用户 {user_id} 在房间 {room_id} 的加密会话...")
# 清除现有的房间密钥
if room_id in self.client.rooms:
self.client.rooms[room_id].encrypted = True
# 上传新的设备密钥
if self.client.should_upload_keys:
await self.client.keys_upload()
# 信任所有设备,包括自己的设备
self.trust_devices(self.client.user_id)
self.trust_devices(user_id)
# 强制同步以获取最新状态
await self.client.sync()
# 发送测试消息以触发新的密钥交换
test_content = {
"msgtype": "m.text",
"body": "正在重新建立加密会话..."
}
await self.client.room_send(
room_id=room_id,
message_type="m.room.message",
content=test_content
)
print(f"已重新建立与用户 {user_id} 的加密会话")
return True
except Exception as e:
print(f"重新建立加密会话失败: {e}")
return False
def trust_devices(self, user_id: str) -> None:
"""信任用戶的所有設備"""
print(f"信任用戶 {user_id} 的所有設備")
try:
for device_id, olm_device in self.client.device_store[user_id].items():
if user_id == self.client.user_id and device_id == self.client.device_id:
continue
print(f"信任設備: {device_id}")
self.client.verify_device(olm_device)
print(f"已经信任设备:{device_id}")
except Exception as e:
print(f"信任用户设备时出错:{e}")
async def trust_room_devices(self, room_id: str):
"""信任房間中所有用戶的所有設備"""
try:
room = self.client.rooms[room_id]
for user_id in room.users:
self.trust_devices(user_id)
except Exception as e:
print(f"信任房間設備時出錯: {e}")
async def login(self):
"""登錄並處理默認房間"""
if not self.is_logged_in:
try:
# 嘗試恢復會話
if os.path.exists(SESSION_DETAILS_FILE):
async with aiofiles.open(SESSION_DETAILS_FILE) as f:
print("密钥文件存在,开始读取")
contents = await f.read()
session_data = json.loads(contents)
self.client.access_token = session_data["access_token"]
self.client.user_id = session_data["user_id"]
self.client.device_id = session_data["device_id"]
print("准备加载STORE")
self.client.load_store()
print(f"使用已保存的憑證登錄: {self.client.user_id}")
self.is_logged_in = True
# 上传设备密钥
if self.client.should_upload_keys:
await self.client.keys_upload()
# 信任自己的设备
self.trust_devices(self.client.user_id)
print("正在同步房間狀態...")
await self.client.sync()
return True
# 如果沒有保存的會話,使用密碼登錄
print("准备开始使用账号密码登陆")
response = await self.client.login(
password=config.PASSWORD,
device_name=config.DEVICE_NAME
)
print("Login response:", response)
if isinstance(response, LoginResponse):
self.is_logged_in = True
print("登錄成功!")
# 保存會話信息
async with aiofiles.open(SESSION_DETAILS_FILE, "w") as f:
await f.write(json.dumps({
"access_token": response.access_token,
"device_id": response.device_id,
"user_id": response.user_id,
}))
# 上传设备密钥
if self.client.should_upload_keys:
await self.client.keys_upload()
# 信任自己的设备
self.trust_devices(self.client.user_id)
print("正在同步房間狀態...")
await self.client.sync()
if hasattr(config, 'ROOM_IDS') and config.ROOM_IDS:
for room_id in config.ROOM_IDS:
if room_id in self.client.rooms:
print(f"已在房間 {room_id} 中")
else:
print(f"嘗試加入房間 {room_id}")
await self.ensure_joined(room_id)
else:
print("未配置默認房間,等待用戶邀請...")
return True
else:
print("登錄失敗!")
return False
except Exception as e:
print(f"登錄過程出錯: {e}")
return False
return True
async def check_ai_chat_timeout(self):
"""定期檢查AI對話超時"""
while True:
try:
current_time = datetime.now()
timeout_sessions = []
for (room_id, user_id), (last_activity, _, _) in (
self.ai_chat_users.items()
):
if isinstance(last_activity, datetime):
timeout = timedelta(
minutes=config.AI_CHAT_TIMEOUT_MINUTES
)
if current_time - last_activity > timeout:
timeout_sessions.append((room_id, user_id))
# 移除超時會話
for room_id, user_id in timeout_sessions:
chat_key = (room_id, user_id)
if chat_key in self.ai_chat_users:
del self.ai_chat_users[chat_key]
if chat_key in self.ai_waiting_users:
del self.ai_waiting_users[chat_key]
print(f"用戶 {user_id} 在房間 {room_id} 的AI對話已超時")
await asyncio.sleep(60) # 每分鐘檢查一次
except Exception as e:
print(f"檢查AI對話超時時出錯: {e}")
await asyncio.sleep(60)
def get_welcome_message(self) -> str:
"""獲取歡迎消息"""
return (
"你好!我是一個自動回覆機器人。\n"
"你可以使用以下命令:\n"
"- !ping: 測試我是否在線\n"
"- 加密货币: 獲取當前加密貨幣價格\n"
"- 人工智能: 開始AI對話"
)
async def invite_callback(self, room, event):
"""當 BOT 被邀請到房間時自動加入並處理驗證"""
try:
print(f"收到來自 {event.sender} 的邀請")
# 自動加入房間
join_response = await self.client.join(room.room_id)
if join_response:
print(f"已加入與 {event.sender} 的房間")
# 同步以獲取房間狀態
await self.client.sync()
# 信任房間中的設備
await self.trust_room_devices(room.room_id)
# 检查是否为私聊房间(只有两个用户)
room_obj = self.client.rooms[room.room_id]
if len(room_obj.users) == 2:
# 记录私聊房间信息
self.private_chat_rooms[room.room_id] = event.sender
print(f"记录新的私聊房间: {room.room_id} - 用户: {event.sender}")
# 發送歡迎消息
await self.send_message(
self.get_welcome_message(),
room_id=room.room_id,
encrypted=True
)
# 記錄已發送歡迎消息
self.welcomed_rooms.add(room.room_id)
except Exception as e:
print(f"處理邀請時出錯: {e}")
def is_room_encrypted(self, room_id: str) -> bool:
"""檢查房間是否加密"""
try:
if room_id not in self.client.rooms:
return False
room = self.client.rooms[room_id]
return getattr(room, "encrypted", False)
except Exception as e:
print(f"檢查房間加密狀態時出錯: {e}")
return False
async def ensure_joined(self, room_id: str):
"""確保加入指定房間"""
try:
if room_id not in self.client.rooms:
response = await self.client.join(room_id)
if isinstance(response, LoginResponse):
print(f"加入房間失敗: {response.message}")
return
print(f"成功加入房間: {room_id}")
await self.client.sync()
# 信任房間中的設備
await self.trust_room_devices(room_id)
except Exception as e:
print(f"確保加入房間時出錯: {e}")
async def get_crypto_prices(self):
"""使用 CoinGecko API 獲取加密貨幣價格和漲跌幅信息"""
try:
# CoinGecko API 基礎URL
base_url = "https://api.coingecko.com/api/v3"
# 獲取BTC、ETH和ADA的數據
coins = {
"bitcoin": "BTCUSDT",
"ethereum": "ETHUSDT" # ,
# "cardano": "ADAUSDT"
}
# 獲取當前價格和24小時漲跌幅
current_prices_url = (
f"{base_url}/simple/price"
f"?ids={','.join(coins.keys())}"
f"&vs_currencies=usd"
f"&include_24hr_change=true"
)
response = requests.get(
current_prices_url,
headers={"accept": "application/json"}
)
response.raise_for_status()
current_data = response.json()
# 獲取7天前的日期
seven_days_ago = (
datetime.now() - timedelta(days=7)
).strftime('%d-%m-%Y')
# 獲取30天前的日期
thirty_days_ago = (
datetime.now() - timedelta(days=30)
).strftime('%d-%m-%Y')
prices = {}
for coin_id, symbol in coins.items():
try:
# 獲取當前價格和24小時漲跌幅
current_price = current_data[coin_id]["usd"]
price_change_24h = current_data[coin_id]["usd_24h_change"]
# 獲取7天前的價格
history_url_7d = (
f"{base_url}/coins/{coin_id}/history?date={seven_days_ago}"
)
response = requests.get(
history_url_7d,
headers={"accept": "application/json"}
)
response.raise_for_status()
history_data_7d = response.json()
price_7d_ago = (
history_data_7d["market_data"]["current_price"]["usd"]
)
# 獲取30天前的價格
history_url_30d = (
f"{base_url}/coins/{coin_id}/history?date={thirty_days_ago}"
)
response = requests.get(
history_url_30d,
headers={"accept": "application/json"}
)
response.raise_for_status()
history_data_30d = response.json()
price_30d_ago = (
history_data_30d["market_data"]["current_price"]["usd"]
)
# 計算7天和30天漲跌幅
price_change_7d = (
(current_price - price_7d_ago) / price_7d_ago) * 100
price_change_30d = (
(current_price - price_30d_ago) / price_30d_ago) * 100
prices[symbol] = {
"current_price": current_price,
"price_change_percent_24h": price_change_24h,
"price_change_percent_7d": price_change_7d,
"price_change_percent_30d": price_change_30d
}
except Exception as e:
print(f"處理 {coin_id} 數據時出錯: {e}")
continue
return prices if prices else None
except requests.exceptions.HTTPError as e:
print(f"HTTP API請求錯誤: {e}")
if e.response.status_code == 429:
return "获取加密货币的接口请求过于频繁,请等一段时间之后再获取!"
else:
return "网络接口出现问题不能连通,请稍后再尝试或者等待修复之后再使用!"
except requests.exceptions.RequestException as e:
err_msg = (f"API請求錯誤: {e}")
return err_msg
except KeyError as e:
print(f"數據解析錯誤: {e}")
return None
except Exception as e:
print(f"獲取加密貨幣價格時出錯: {e}")
return None
async def send_message(
self,
message: str,
room_id: str = config.ROOM_ID,
formatted_message: Optional[str] = None,
encrypted: bool = False
):
"""發送消息(支持加密和未加密房間)"""
if not self.is_logged_in:
print("Error: Not logged in!")
return
content = {"msgtype": "m.text", "body": message}
if formatted_message:
content["format"] = "org.matrix.custom.html"
content["formatted_body"] = formatted_message
try:
# 如果需要加密或房間是加密的
if encrypted or self.is_room_encrypted(room_id):
# 获取房间中的所有用户
room = self.client.rooms[room_id]
for user_id in room.users:
if user_id != self.client.user_id:
# 确保信任所有设备
self.trust_devices(user_id)
await self.client.room_send(
room_id=room_id,
message_type="m.room.message",
content=content
)
print(f"Message sent successfully to room {room_id}")
except exceptions.OlmUnverifiedDeviceError as e:
print(f"房間有未驗證的設備: {e}")
try:
# 获取房间中的所有用户
room = self.client.rooms[room_id]
for user_id in room.users:
if user_id != self.client.user_id:
# 重新信任设备
self.trust_devices(user_id)
# 重试发送
await self.client.room_send(
room_id=room_id,
message_type="m.room.message",
content=content
)
print("重试发送消息成功")
except Exception as retry_e:
print(f"重试发送消息失败: {retry_e}")
except Exception as e:
print(f"Error sending message: {e}")
# else:
# print("Error: Not logged in!")
async def sync(self):
print("Start Sync...")
if self.is_logged_in:
while True:
print("Sync task running...")
await self.client.sync_forever(timeout=60000, full_state=True)
def schedule_daily_message(self, message):
schedule.every().day.at("09:00").do(
lambda: asyncio.run(self.send_message(message))
)
def schedule_hourly_updates(self):
schedule.every().hour.do(self.fetch_and_send_news)
async def send_crypto_prices(self, room_id=config.ROOM_ID):
"""發送加密貨幣價格信息"""
crypto_prices = await self.get_crypto_prices()
if crypto_prices and not isinstance(crypto_prices, str):
# 準備富文本消息
formatted_message = (
"| 幣種 | " "當前價格 | " "24h漲跌 | " "7d漲跌 | " "30d漲跌 | " "
|---|---|---|---|---|
| {coin_names[symbol]} | " f"" f"${info['current_price']:,.2f} | " f"" f"{info['price_change_percent_24h']:+.2f}% | " f"" f"{info['price_change_percent_7d']:+.2f}% | " f"" f"{info['price_change_percent_30d']:+.2f}% | " f"
", 1
).replace("```", "", 1)
return content, formatted_content
except requests.exceptions.HTTPError as e:
if e.response.status_code == 402:
return "对不起,由于系统后台余额不足,请等待管理员充值之后再重新进行对话", None
print(f"AI API調用出錯: {e}")
return "抱歉,AI服務暫時無法使用,請稍後再試。", None
except Exception as e:
print(f"AI API調用出錯: {e}")
return "抱歉,AI服務暫時無法使用,請稍後再試。", None
async def encrypted_message_callback(self, room, event):
"""處理加密消息"""
print(f"收到加密消息,房間ID: {room.room_id}")
# print(f"输出event{event}")
try:
# 对于MegolmEvent,我们需要从解密后的内容中获取消息
if hasattr(event, "decrypted_data"):
# 从解密数据中获取消息内容
message_content = event.decrypted_data.get("content", {})
# 确保event对象有body属性供process_message使用
event.body = message_content.get("body", "")
await self.process_message(room, event)
else:
# 检查是否是因为缺少会话ID导致的解密失败
error_msg = str(event.decryption_error) if hasattr(
event, "decryption_error") else ""
if "no session found with session id" in error_msg:
print(f"解密失败,尝试重新建立会话: {error_msg}")
# 获取发送者ID
sender_id = event.sender
# 尝试重新建立会话
success = await self.reestablish_room_session(room.room_id, sender_id)
if success:
# 通知用户重新建立了会话
await self.send_message(
"已重新建立加密会话,请重新发送您的消息。",
room_id=room.room_id,
encrypted=True
)
else:
print("重新建立会话失败")
else:
print("无法处理未解密的消息")
except Exception as e:
print(f"处理加密消息时出错: {e}")
async def message_callback(self, room, event):
"""處理未加密消息"""
print(f"收到未加密消息,房間ID: {room.room_id}")
await self.process_message(room, event)
async def process_message(self, room, event):
"""統一處理消息的邏輯"""
# 忽略自己發送的消息
if event.sender == self.client.user_id:
return
print(
f"處理來自房間 {room.room_id} 的消息: {event.body}"
f", 事件ID: {event.event_id}"
)
# 將消息時間戳轉換為datetime
message_time = datetime.fromtimestamp(event.server_timestamp / 1000)
if message_time >= self.start_time: # 只響應程序啟動後的消息
user_id = event.sender
room_id = room.room_id
chat_key = (room_id, user_id)
# 檢查是否為群聊房間
is_group_chat = room_id in config.ROOM_IDS
# 如果是私聊房间且尚未记录,则记录下来
if not is_group_chat and room_id not in self.private_chat_rooms:
print(f"新的私聊房间:{room.users}")
if len(room.users) == 2: # 确认是私聊房间
self.private_chat_rooms[room_id] = user_id
print(f"记录新的私聊房间: {room_id} - 用户: {user_id}")
# 處理命令
if event.body == "!ping":
await self.send_message(
"Pong!",
room_id=room_id,
encrypted=True
)
elif event.body == "加密货币":
await self.send_crypto_prices(room_id)
elif event.body == "人工智能":
if is_group_chat:
# 在群聊中提示私聊
await self.send_message(
"如果想要AI对话,請你點擊我頭像,向我進行私聊,"
"就重新进行指令就可以进行AI對話",
room_id=room_id,
encrypted=True
)
else:
# 显示模型列表
await self.send_message(
self.get_model_list_message(user_id),
room_id=room_id,
encrypted=True
)
# 初始化模型选择状态
self.model_select_state[chat_key] = 0
elif chat_key in self.model_select_state:
# 处理模型选择
model_key = self.process_model_selection(
user_id, room_id, event.body)
if model_key:
# 检查模型使用限制
can_use, limit_msg = self.check_model_limit(
user_id, model_key)
if can_use:
# 初始化AI对话
self.ai_chat_users[chat_key] = (
datetime.now(),
[], # 空的会话历史
model_key # 记录选择的模型
)
# 获取模型名称
model_name = (config.XAI_MODEL_DIYNAME if model_key == "xai"
else config.OPENROUTER_MODELS[model_key]["name"])
welcome_msg = (
f"歡迎你使用AI智哥哥功能1.02,請你回覆我你想要知道的內容"
f"[如果是更大的模型的回复会更详细,可能需要很多的时间来准备内容,"
f"所以发送后请耐心等候回复,不要重复发送],"
f"閒置{config.AI_CHAT_TIMEOUT_MINUTES}分鐘之後功能會自動關閉,"
f"或者也可以手动输入'退出'就可以手动退出AI对话功能"
f"[目前选择的大模型是{model_name}]"
)
# 如果有使用限制提示,添加到欢迎消息
if limit_msg:
welcome_msg += f"\n{limit_msg}"
await self.send_message(
welcome_msg,
room_id=room_id,
encrypted=True
)
else:
# 发送限制消息并清除选择状态
await self.send_message(
limit_msg,
room_id=room_id,
encrypted=True
)
# 清除模型选择状态
del self.model_select_state[chat_key]
else:
# 更新重试次数
self.model_select_state[chat_key] += 1
if self.model_select_state[chat_key] >= MODEL_SELECT_RETRIES:
# 超过重试次数,取消操作
del self.model_select_state[chat_key]
await self.send_message(
"已超过重试次数,如需使用AI对话请重新输入'人工智能'",
room_id=room_id,
encrypted=True
)
else:
# 重新显示模型列表
await self.send_message(
self.get_model_list_message(user_id),
room_id=room_id,
encrypted=True
)
elif chat_key in self.ai_chat_users:
if event.body == "退出":
# 清除所有相关状态
if chat_key in self.ai_chat_users:
del self.ai_chat_users[chat_key]
if chat_key in self.ai_waiting_users:
del self.ai_waiting_users[chat_key]
await self.send_message(
"已经退出了AI对话啦!欢迎你的使用!",
room_id=room_id,
encrypted=True
)
else:
# 检查用户是否正在等待AI响应
if chat_key in self.ai_waiting_users:
await self.send_message(
"AI已经接收到你的问题啦,不过还需要时间进行反应,请你耐心等待...",
room_id=room_id,
encrypted=True
)
return
# 获取会话状态
last_activity, history, model_key = self.ai_chat_users[chat_key]
# 检查模型使用限制
can_use, limit_msg = self.check_model_limit(
user_id, model_key)
if not can_use:
# 达到限制,结束对话
del self.ai_chat_users[chat_key]
await self.send_message(
limit_msg,
room_id=room_id,
encrypted=True
)
return
# 记录用户正在等待AI响应
self.ai_waiting_users[chat_key] = (
datetime.now(), event.body)
# 发送等待提示
await self.send_message(
"已经收到你的请求,请耐心等待AI智哥哥的思考...",
room_id=room_id,
encrypted=True
)
# 更新用户消息到历史
history.append({"role": "user", "content": event.body})
try:
# 获取AI回复
response, formatted_response = await self.chat_with_ai(
event.body,
history,
model_key
)
# 更新AI回复到历史
history.append(
{"role": "assistant", "content": response})
# 增加使用次数
self.increment_model_usage(user_id, model_key)
# 检查是否需要添加剩余次数提示
if limit_msg:
response += f"\n{limit_msg}"
# 更新会话状态
self.ai_chat_users[chat_key] = (
datetime.now(),
history,
model_key
)
# 发送回复
await self.send_message(
response,
room_id=room_id,
formatted_message=formatted_response,
encrypted=True
)
except Exception as e:
print(f"处理AI回复时出错: {e}")
await self.send_message(
"抱歉,处理你的请求时出现了错误,请稍后重试。",
room_id=room_id,
encrypted=True
)
finally:
# 无论成功还是失败,都清除等待状态
if chat_key in self.ai_waiting_users:
del self.ai_waiting_users[chat_key]
else:
# 如果不是AI對話且不是命令,發送歡迎消息
await self.send_message(
self.get_welcome_message(),
room_id=room_id,
encrypted=True
)
else:
print(
f"忽略消息,發送時間 {message_time},"
f"早於程序啟動時間 {self.start_time}"
)
async def run_schedule(self):
while True:
schedule.run_pending()
await asyncio.sleep(1)
async def send_shutdown_notice(self):
"""向所有私聊房间发送关闭通知"""
shutdown_message = (
"服务器准备进行升级,当服务器升级完毕之后,这个房间可能会无法正常解密消息。"
"如果发送内容没有任何反应,请长按头像'离开'这个私聊房间,重新从公共房间中跟AI智哥哥"
"再发送私聊信息,这样就可以重新建立连接啦,就可以使用啦!"
)
for room_id, user_id in self.private_chat_rooms.items():
try:
print(f"正在向房间 {room_id} 发送关闭通知...")
await self.send_message(
shutdown_message,
room_id=room_id,
encrypted=True
)
# 等待一小段时间确保消息发送成功
await asyncio.sleep(0.5)
except Exception as e:
print(f"向房间 {room_id} 发送关闭通知失败: {e}")
async def close(self):
"""关闭服务前发送通知并清理资源"""
try:
if self.private_chat_rooms:
# 暂时不发送关闭时候的通知
print("程序正在关闭...")
# print("正在发送关闭通知...")
# await self.send_shutdown_notice()
# 等待一段时间确保所有通知都发送完成
# await asyncio.sleep(2)
except Exception as e:
print(f"发送关闭通知时出错: {e}")
finally:
await self.client.close()