Do base message recognition
This commit is contained in:
parent
55cec3b876
commit
4f8e10df16
4 changed files with 240 additions and 2 deletions
11
.env.example
Normal file
11
.env.example
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
HOMESERVER=https://matrix.org
|
||||||
|
|
||||||
|
# Bot's Matrix username (full MXID)
|
||||||
|
MATRIX_USERNAME=@your_bot:matrix.org
|
||||||
|
|
||||||
|
# Either use password OR access token
|
||||||
|
PASSWORD=
|
||||||
|
ACCESS_TOKEN=syt_...
|
||||||
|
|
||||||
|
# Allowed rooms (comma-separated, no spaces)
|
||||||
|
ALLOWED_ROOMS=!roomid1:matrix.org,!roomid2:matrix.org
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
# b2b_assistants
|
# Бот для автоматической генерации отчётов
|
||||||
|
|
||||||
Репозиторий для разработки B to B рушений
|
Принимает фото/аудио файлы и текстовые сообщения, когда в течение 15 секунд нет новых сообщений,
|
||||||
|
формирует отчёт по отправленному.
|
||||||
226
main.py
Normal file
226
main.py
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
from typing import Dict, Optional, Tuple
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from nio import (
|
||||||
|
AsyncClient,
|
||||||
|
RoomMessageText,
|
||||||
|
RoomMessageImage,
|
||||||
|
RoomMessageAudio,
|
||||||
|
LoginResponse,
|
||||||
|
AsyncClientConfig,
|
||||||
|
ErrorResponse,
|
||||||
|
)
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
HOMESERVER = os.getenv("HOMESERVER", "https://matrix.org")
|
||||||
|
USERNAME = os.getenv("MATRIX_USERNAME")
|
||||||
|
PASSWORD = os.getenv("PASSWORD")
|
||||||
|
ALLOWED_ROOMS = set(room.strip() for room in os.getenv("ALLOWED_ROOMS", "").split(",") if room.strip())
|
||||||
|
|
||||||
|
TEMP_DIR = tempfile.gettempdir()
|
||||||
|
GROUPING_TIMEOUT = 15.0
|
||||||
|
|
||||||
|
client: AsyncClient = None
|
||||||
|
|
||||||
|
pending_by_conversation: Dict[Tuple[str, str], Dict] = {}
|
||||||
|
pending_by_event_id: Dict[str, Dict] = {}
|
||||||
|
|
||||||
|
|
||||||
|
async def process_audio(audio_bytes: bytes) -> str:
|
||||||
|
print(f"[AUDIO] Получено {len(audio_bytes)} байт аудио")
|
||||||
|
return "Placeholder"
|
||||||
|
|
||||||
|
|
||||||
|
async def process_image(image_bytes: bytes) -> str:
|
||||||
|
print(f"[IMAGE] Получено {len(image_bytes)} байт изображения")
|
||||||
|
return "Placeholder"
|
||||||
|
|
||||||
|
|
||||||
|
async def generate_report(text: str, image_descriptions: list, audio_texts: list) -> str:
|
||||||
|
print(f"[REPORT] text: {text}, images: {len(image_descriptions)}, audio: {len(audio_texts)}")
|
||||||
|
#TODO whisper + отчёт
|
||||||
|
return "Placeholder"
|
||||||
|
|
||||||
|
|
||||||
|
async def send_error_message(room_id: str, error_text: str):
|
||||||
|
await client.room_send(
|
||||||
|
room_id,
|
||||||
|
"m.room.message",
|
||||||
|
{"msgtype": "m.text", "body": f"❌ Ошибка: {error_text}"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def process_complete_message(data: Dict):
|
||||||
|
room_id = data["room_id"]
|
||||||
|
image_descriptions = []
|
||||||
|
for img_bytes in data.get("images", []):
|
||||||
|
desc = await process_image(img_bytes)
|
||||||
|
image_descriptions.append(desc)
|
||||||
|
audio_texts = []
|
||||||
|
for aud_bytes in data.get("audio", []):
|
||||||
|
text = await process_audio(aud_bytes)
|
||||||
|
audio_texts.append(text)
|
||||||
|
report = await generate_report(data.get("text", ""), image_descriptions, audio_texts)
|
||||||
|
await client.room_send(
|
||||||
|
room_id,
|
||||||
|
"m.room.message",
|
||||||
|
{"msgtype": "m.text", "body": report}
|
||||||
|
)
|
||||||
|
if "event_id" in data:
|
||||||
|
pending_by_event_id.pop(data["event_id"], None)
|
||||||
|
pending_by_conversation.pop((room_id, data["sender"]), None)
|
||||||
|
|
||||||
|
|
||||||
|
async def delayed_processing(data: Dict):
|
||||||
|
await asyncio.sleep(GROUPING_TIMEOUT)
|
||||||
|
key = (data["room_id"], data["sender"])
|
||||||
|
if pending_by_conversation.get(key) is data:
|
||||||
|
await process_complete_message(data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create_pending(room_id: str, sender: str, event_id: Optional[str] = None) -> Dict:
|
||||||
|
if event_id and event_id in pending_by_event_id:
|
||||||
|
return pending_by_event_id[event_id]
|
||||||
|
|
||||||
|
key = (room_id, sender)
|
||||||
|
if key in pending_by_conversation:
|
||||||
|
return pending_by_conversation[key]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"room_id": room_id,
|
||||||
|
"sender": sender,
|
||||||
|
"text": None,
|
||||||
|
"images": [],
|
||||||
|
"audio": [],
|
||||||
|
"timestamp": time.time(),
|
||||||
|
"task": None,
|
||||||
|
}
|
||||||
|
if event_id:
|
||||||
|
data["event_id"] = event_id
|
||||||
|
pending_by_conversation[key] = data
|
||||||
|
if event_id:
|
||||||
|
pending_by_event_id[event_id] = data
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def reset_timer(data: Dict):
|
||||||
|
if data["task"] and not data["task"].done():
|
||||||
|
data["task"].cancel()
|
||||||
|
data["timestamp"] = time.time()
|
||||||
|
data["task"] = asyncio.create_task(delayed_processing(data))
|
||||||
|
|
||||||
|
|
||||||
|
async def on_text_message(room, event: RoomMessageText):
|
||||||
|
if event.sender == client.user_id:
|
||||||
|
return
|
||||||
|
if room.room_id not in ALLOWED_ROOMS:
|
||||||
|
return
|
||||||
|
|
||||||
|
event_id = event.event_id
|
||||||
|
data = get_or_create_pending(room.room_id, event.sender, event_id)
|
||||||
|
data["text"] = event.body
|
||||||
|
reset_timer(data)
|
||||||
|
print(f"[TEXT] Добавлен текст в сообщение от {event.sender}: {event.body}")
|
||||||
|
|
||||||
|
|
||||||
|
async def on_image_message(room, event: RoomMessageImage):
|
||||||
|
if event.sender == client.user_id:
|
||||||
|
return
|
||||||
|
if room.room_id not in ALLOWED_ROOMS:
|
||||||
|
return
|
||||||
|
|
||||||
|
related_event_id = None
|
||||||
|
if hasattr(event, "source") and "content" in event.source:
|
||||||
|
content = event.source["content"]
|
||||||
|
if "m.relates_to" in content and "event_id" in content["m.relates_to"]:
|
||||||
|
related_event_id = content["m.relates_to"]["event_id"]
|
||||||
|
|
||||||
|
data = get_or_create_pending(room.room_id, event.sender, related_event_id)
|
||||||
|
|
||||||
|
download_result = await client.download(event.url)
|
||||||
|
if isinstance(download_result, ErrorResponse):
|
||||||
|
print(f"Ошибка скачивания изображения: {download_result.status_code}")
|
||||||
|
await send_error_message(room.room_id, "Не удалось загрузить изображение.")
|
||||||
|
return
|
||||||
|
|
||||||
|
data["images"].append(download_result.body)
|
||||||
|
reset_timer(data)
|
||||||
|
print(f"[IMAGE] Добавлено изображение в сообщение от {event.sender}")
|
||||||
|
|
||||||
|
|
||||||
|
async def on_audio_message(room, event: RoomMessageAudio):
|
||||||
|
if event.sender == client.user_id:
|
||||||
|
return
|
||||||
|
if room.room_id not in ALLOWED_ROOMS:
|
||||||
|
return
|
||||||
|
|
||||||
|
related_event_id = None
|
||||||
|
if hasattr(event, "source") and "content" in event.source:
|
||||||
|
content = event.source["content"]
|
||||||
|
if "m.relates_to" in content and "event_id" in content["m.relates_to"]:
|
||||||
|
related_event_id = content["m.relates_to"]["event_id"]
|
||||||
|
|
||||||
|
data = get_or_create_pending(room.room_id, event.sender, related_event_id)
|
||||||
|
|
||||||
|
download_result = await client.download(event.url)
|
||||||
|
if isinstance(download_result, ErrorResponse):
|
||||||
|
print(f"Ошибка скачивания аудио: {download_result.status_code}")
|
||||||
|
await send_error_message(room.room_id, "Не удалось загрузить аудио.")
|
||||||
|
return
|
||||||
|
|
||||||
|
data["audio"].append(download_result.body)
|
||||||
|
reset_timer(data)
|
||||||
|
print(f"[AUDIO] Добавлено аудио в сообщение от {event.sender}")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
global client
|
||||||
|
|
||||||
|
config = AsyncClientConfig(
|
||||||
|
max_timeouts=10,
|
||||||
|
store_sync_tokens=True,
|
||||||
|
encryption_enabled=False,
|
||||||
|
)
|
||||||
|
client = AsyncClient(
|
||||||
|
homeserver=HOMESERVER,
|
||||||
|
user=USERNAME,
|
||||||
|
device_id=None,
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if PASSWORD:
|
||||||
|
response = await client.login(PASSWORD)
|
||||||
|
else:
|
||||||
|
response = await client.login(token=os.environ.get("ACCESS_TOKEN", ""))
|
||||||
|
|
||||||
|
if isinstance(response, LoginResponse):
|
||||||
|
print(f"Бот {USERNAME} успешно авторизован на {HOMESERVER}")
|
||||||
|
print(f"Access token: {client.access_token}")
|
||||||
|
else:
|
||||||
|
print(f"Ошибка авторизации: {response}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Исключение при авторизации: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
client.add_event_callback(on_text_message, RoomMessageText)
|
||||||
|
client.add_event_callback(on_image_message, RoomMessageImage)
|
||||||
|
client.add_event_callback(on_audio_message, RoomMessageAudio)
|
||||||
|
|
||||||
|
print("Бот запущен, ожидание событий...")
|
||||||
|
try:
|
||||||
|
await client.sync_forever(timeout=30000)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Бот остановлен пользователем")
|
||||||
|
finally:
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue