170 lines
5.5 KiB
Python
170 lines
5.5 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
from yuxi.channel.extensions.googlechat.formatting import sanitize_text
|
|
from yuxi.channel.extensions.googlechat.media import build_attachment_placeholder
|
|
from yuxi.channel.extensions.googlechat.types import GoogleChatEvent, ResolvedGoogleChatAccount
|
|
from yuxi.channel.message.models import (
|
|
GroupContext,
|
|
MessageType,
|
|
PeerInfo,
|
|
UnifiedMessage,
|
|
)
|
|
from yuxi.channel.routing.models import PeerKind
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class GoogleChatMonitor:
|
|
def __init__(self):
|
|
self._last_event_time: float = 0
|
|
|
|
def event_to_unified_message(
|
|
self,
|
|
raw_payload: dict,
|
|
event: GoogleChatEvent,
|
|
text: str | None = None,
|
|
account: ResolvedGoogleChatAccount | None = None,
|
|
) -> UnifiedMessage | None:
|
|
if not event.message:
|
|
return None
|
|
|
|
msg = event.message
|
|
sender = msg.sender
|
|
space_name = event.effective_space_name
|
|
space_type = event.space_type
|
|
|
|
safe_text = text if text is not None else sanitize_text(msg.argument_text or msg.text)
|
|
if not safe_text:
|
|
return None
|
|
|
|
is_dm = space_type == "DM" or event.effective_space.single_user_bot_dm if event.effective_space else False
|
|
is_group = space_type in ("GROUP_CHAT", "SPACE") and not is_dm
|
|
|
|
sender_kind = PeerKind.DIRECT if is_dm else PeerKind.GROUP
|
|
|
|
sender_info = PeerInfo(
|
|
kind=sender_kind,
|
|
id=sender.name or "unknown",
|
|
display_name=sender.display_name,
|
|
username=sender.email,
|
|
is_bot=sender.type == "BOT",
|
|
)
|
|
|
|
group = None
|
|
if is_group:
|
|
group = GroupContext(
|
|
id=space_name,
|
|
name=event.effective_space.display_name if event.effective_space else None,
|
|
kind="group",
|
|
)
|
|
|
|
thread_id = None
|
|
if msg.thread:
|
|
thread_id = msg.thread.get("name")
|
|
|
|
timestamp = None
|
|
if event.event_time:
|
|
try:
|
|
from datetime import datetime as dt
|
|
|
|
timestamp = dt.fromisoformat(event.event_time.replace("Z", "+00:00"))
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
media_urls = []
|
|
media_types = []
|
|
for att in msg.attachment:
|
|
dr = att.get("driveDataRef") or att.get("attachmentDataRef")
|
|
if dr:
|
|
media_urls.append(dr.get("resourceName", ""))
|
|
media_types.append(att.get("contentType", ""))
|
|
|
|
if msg.attachment:
|
|
placeholder = build_attachment_placeholder(msg.attachment)
|
|
if placeholder:
|
|
safe_text = f"{placeholder}\n{safe_text}"
|
|
|
|
was_mentioned = _detect_mention(msg.annotations)
|
|
slash_cmd = _detect_slash_command(msg.annotations)
|
|
|
|
metadata = {
|
|
"ChatType": "direct" if is_dm else "group",
|
|
"WasMentioned": was_mentioned,
|
|
"CommandAuthorized": True,
|
|
"space_type": space_type,
|
|
"space_name": space_name,
|
|
}
|
|
if slash_cmd:
|
|
metadata["SlashCommand"] = slash_cmd
|
|
if is_group and event.effective_space:
|
|
metadata["GroupSpace"] = event.effective_space.display_name
|
|
|
|
return UnifiedMessage(
|
|
msg_id=msg.name or f"gc:{int(datetime.now(tz=timezone.utc).timestamp())}",
|
|
channel_type="googlechat",
|
|
account_id=account.account_id if account else "default",
|
|
content=safe_text,
|
|
sender=sender_info,
|
|
message_type=MessageType.TEXT,
|
|
media_urls=media_urls,
|
|
media_types=media_types,
|
|
group=group,
|
|
timestamp=timestamp,
|
|
raw_payload=raw_payload,
|
|
message_thread_id=thread_id,
|
|
metadata=metadata,
|
|
was_mentioned=was_mentioned,
|
|
)
|
|
|
|
async def inject_thread_context(
|
|
self,
|
|
account: ResolvedGoogleChatAccount,
|
|
space_name: str,
|
|
thread_name: str,
|
|
) -> str | None:
|
|
try:
|
|
from yuxi.channel.extensions.googlechat.api import list_thread_messages
|
|
from yuxi.channel.extensions.googlechat.formatting import format_thread_context
|
|
|
|
messages = await list_thread_messages(account, space_name, thread_name, page_size=20)
|
|
if not messages:
|
|
return None
|
|
|
|
history = []
|
|
for msg in messages:
|
|
sender = msg.get("sender", {})
|
|
history.append({
|
|
"author": sender.get("displayName", sender.get("name", "Unknown")),
|
|
"content": msg.get("text", msg.get("argumentText", "")),
|
|
})
|
|
|
|
return format_thread_context(history)
|
|
except Exception:
|
|
logger.debug("Failed to fetch thread history for %s in %s", thread_name, space_name)
|
|
return None
|
|
|
|
|
|
def _detect_mention(annotations: list[dict]) -> bool:
|
|
if not annotations:
|
|
return False
|
|
for ann in annotations:
|
|
if ann.get("type") == "USER_MENTION":
|
|
um = ann.get("userMention", {})
|
|
mention_user = um.get("user", {})
|
|
if mention_user.get("name") == "users/app":
|
|
return True
|
|
return False
|
|
|
|
|
|
def _detect_slash_command(annotations: list[dict]) -> str | None:
|
|
if not annotations:
|
|
return None
|
|
for ann in annotations:
|
|
if ann.get("type") == "SLASH_COMMAND":
|
|
sc = ann.get("slashCommand", {})
|
|
return sc.get("commandId")
|
|
return None
|