surfaces/adapter/matrix/handlers/auth.py

285 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from typing import Any
import structlog
from nio.api import RoomVisibility
from nio.responses import RoomCreateError
from adapter.matrix.agent_registry import AgentRegistry
from adapter.matrix.store import (
get_user_meta,
next_platform_chat_id,
set_room_meta,
set_user_meta,
)
logger = structlog.get_logger(__name__)
def _default_room_name(chat_id: str) -> str:
suffix = chat_id[1:] if chat_id.startswith("C") else chat_id
return f"Чат {suffix}"
def default_agent_notice() -> str:
return (
"Внимание: ваш Matrix ID не найден в конфиге агентов. "
"Пока используется агент по умолчанию. После добавления вас в конфиг "
"бот переключит существующие комнаты на назначенного агента."
)
async def _invite_if_possible(client: Any, room_id: str, matrix_user_id: str) -> bool:
room_invite = getattr(client, "room_invite", None)
if not callable(room_invite):
return False
try:
await room_invite(room_id, matrix_user_id)
return True
except Exception as exc:
logger.warning(
"matrix_workspace_reinvite_failed",
room_id=room_id,
user=matrix_user_id,
error=str(exc),
)
return False
async def provision_workspace_chat(
client: Any,
matrix_user_id: str,
display_name: str,
platform,
store,
auth_mgr,
chat_mgr,
room_name_override: str | None = None,
registry: AgentRegistry | None = None,
) -> dict:
user = await platform.get_or_create_user(
external_id=matrix_user_id,
platform="matrix",
display_name=display_name,
)
await auth_mgr.confirm(matrix_user_id)
homeserver = matrix_user_id.split(":")[-1]
user_meta = await get_user_meta(store, matrix_user_id) or {}
space_id = user_meta.get("space_id")
if not space_id:
space_resp = await client.room_create(
name=f"Lambda — {display_name}",
space=True,
visibility=RoomVisibility.private,
invite=[matrix_user_id],
)
if isinstance(space_resp, RoomCreateError):
logger.error(
"space creation failed",
user=matrix_user_id,
error=getattr(space_resp, "status_code", None),
)
raise RuntimeError("Не удалось создать Space.")
space_id = space_resp.room_id
user_meta["space_id"] = space_id
await set_user_meta(store, matrix_user_id, user_meta)
next_chat_index = int(user_meta.get("next_chat_index", 1))
chat_id = f"C{next_chat_index}"
platform_chat_id = await next_platform_chat_id(store)
room_name = room_name_override or _default_room_name(chat_id)
agent_id = None
agent_assignment = "none"
if registry is not None:
assignment = registry.resolve_agent_for_user(matrix_user_id)
agent_id = assignment.agent_id
agent_assignment = assignment.source
chat_resp = await client.room_create(
name=room_name,
visibility=RoomVisibility.private,
is_direct=False,
invite=[matrix_user_id],
)
if isinstance(chat_resp, RoomCreateError):
logger.error(
"chat room creation failed",
user=matrix_user_id,
error=getattr(chat_resp, "status_code", None),
)
raise RuntimeError("Не удалось создать рабочий чат.")
chat_room_id = chat_resp.room_id
await client.room_put_state(
room_id=space_id,
event_type="m.space.child",
content={"via": [homeserver]},
state_key=chat_room_id,
)
user_meta["space_id"] = space_id
user_meta["next_chat_index"] = next_chat_index + 1
await set_user_meta(store, matrix_user_id, user_meta)
await set_room_meta(
store,
chat_room_id,
{
"room_type": "chat",
"chat_id": chat_id,
"display_name": room_name,
"matrix_user_id": matrix_user_id,
"space_id": space_id,
"platform_chat_id": platform_chat_id,
"agent_id": agent_id,
"agent_assignment": agent_assignment,
},
)
await chat_mgr.get_or_create(
user_id=matrix_user_id,
chat_id=chat_id,
platform="matrix",
surface_ref=chat_room_id,
name=room_name,
)
return {
"user": user,
"space_id": space_id,
"chat_room_id": chat_room_id,
"chat_id": chat_id,
"room_name": room_name,
"agent_assignment": agent_assignment,
"agent_id": agent_id,
}
async def restore_workspace_access(
client: Any,
matrix_user_id: str,
display_name: str,
platform,
store,
auth_mgr,
chat_mgr,
registry: AgentRegistry | None = None,
) -> dict:
user_meta = await get_user_meta(store, matrix_user_id) or {}
space_id = user_meta.get("space_id")
if not space_id:
created = await provision_workspace_chat(
client,
matrix_user_id,
display_name,
platform,
store,
auth_mgr,
chat_mgr,
room_name_override="Чат 1",
registry=registry,
)
return {**created, "reinvited_rooms": [], "created_new_chat": True}
await auth_mgr.confirm(matrix_user_id)
await _invite_if_possible(client, space_id, matrix_user_id)
chats = await chat_mgr.list_active(matrix_user_id)
if not chats:
created = await provision_workspace_chat(
client,
matrix_user_id,
display_name,
platform,
store,
auth_mgr,
chat_mgr,
registry=registry,
)
return {**created, "reinvited_rooms": [], "created_new_chat": True}
reinvited_rooms = []
for chat in chats:
if chat.surface_ref:
if await _invite_if_possible(client, chat.surface_ref, matrix_user_id):
reinvited_rooms.append(chat.surface_ref)
return {
"space_id": space_id,
"reinvited_rooms": reinvited_rooms,
"created_new_chat": False,
}
async def handle_invite(
client: Any,
room: Any,
event: Any,
platform,
store,
auth_mgr,
chat_mgr,
registry: AgentRegistry | None = None,
) -> None:
matrix_user_id = getattr(event, "sender", "")
display_name = getattr(room, "display_name", None) or matrix_user_id
await client.join(room.room_id)
existing = await get_user_meta(store, matrix_user_id)
if existing and existing.get("space_id"):
restored = await restore_workspace_access(
client,
matrix_user_id,
display_name,
platform,
store,
auth_mgr,
chat_mgr,
registry=registry,
)
body = "Я отправил повторные приглашения в пространство Lambda и рабочие чаты."
if restored.get("created_new_chat"):
body = (
f"Создал новый рабочий чат {restored['room_name']} "
f"({restored['chat_id']}) и отправил приглашение."
)
if restored.get("agent_assignment") == "default":
body = f"{body}\n\n{default_agent_notice()}"
await client.room_send(
room.room_id,
"m.room.message",
{"msgtype": "m.text", "body": body},
)
return
try:
created = await provision_workspace_chat(
client,
matrix_user_id,
display_name,
platform,
store,
auth_mgr,
chat_mgr,
room_name_override="Чат 1",
registry=registry,
)
except RuntimeError as exc:
logger.error("invite_workspace_provision_failed", user=matrix_user_id, error=str(exc))
return
welcome = (
f"Привет, {created['user'].display_name or matrix_user_id}! Пиши — я здесь.\n\n"
"Команды: !new · !chats · !rename · !archive · !clear · !help"
)
if created.get("agent_assignment") == "default":
welcome = f"{welcome}\n\n{default_agent_notice()}"
await client.room_send(
created["chat_room_id"],
"m.room.message",
{"msgtype": "m.text", "body": welcome},
)