This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/src/chat_bridge/base.py
Claude 15596ca325 feat: add Discord integration with chat_bridge abstraction layer
Introduces a vendor-agnostic chat platform architecture:

- chat_bridge/base.py: ChatPlatform ABC, ChatMessage, ChatThread
- chat_bridge/registry.py: PlatformRegistry singleton
- chat_bridge/invite_parser.py: QR + Ollama vision invite extraction
- chat_bridge/vendors/discord.py: DiscordVendor with native threads

Workflow: paste a screenshot of a Discord invite or QR code at
POST /discord/join → Timmy extracts the invite automatically.

Every Discord conversation gets its own thread, keeping channels clean.
Bot responds to @mentions and DMs, routes through Timmy agent.

43 new tests (base classes, registry, invite parser, vendor, routes).

https://claude.ai/code/session_01WU4h3cQQiouMwmgYmAgkMM
2026-02-25 01:11:14 +00:00

148 lines
4.1 KiB
Python

"""ChatPlatform — abstract base class for all chat vendor integrations.
Each vendor (Discord, Telegram, Slack, etc.) implements this interface.
The dashboard and agent code interact only with this contract, never
with vendor-specific APIs directly.
Architecture:
ChatPlatform (ABC)
|
+-- DiscordVendor (discord.py)
+-- TelegramVendor (future migration)
+-- SlackVendor (future)
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum, auto
from typing import Any, Optional
class PlatformState(Enum):
"""Lifecycle state of a chat platform connection."""
DISCONNECTED = auto()
CONNECTING = auto()
CONNECTED = auto()
ERROR = auto()
@dataclass
class ChatMessage:
"""Vendor-agnostic representation of a chat message."""
content: str
author: str
channel_id: str
platform: str
timestamp: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
message_id: Optional[str] = None
thread_id: Optional[str] = None
attachments: list[str] = field(default_factory=list)
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass
class ChatThread:
"""Vendor-agnostic representation of a conversation thread."""
thread_id: str
title: str
channel_id: str
platform: str
created_at: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
archived: bool = False
message_count: int = 0
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass
class InviteInfo:
"""Parsed invite extracted from an image or text."""
url: str
code: str
platform: str
guild_name: Optional[str] = None
source: str = "unknown" # "qr", "vision", "text"
@dataclass
class PlatformStatus:
"""Current status of a chat platform connection."""
platform: str
state: PlatformState
token_set: bool
guild_count: int = 0
thread_count: int = 0
error: Optional[str] = None
def to_dict(self) -> dict[str, Any]:
return {
"platform": self.platform,
"state": self.state.name.lower(),
"connected": self.state == PlatformState.CONNECTED,
"token_set": self.token_set,
"guild_count": self.guild_count,
"thread_count": self.thread_count,
"error": self.error,
}
class ChatPlatform(ABC):
"""Abstract base class for chat platform integrations.
Lifecycle:
configure(token) -> start() -> [send/receive messages] -> stop()
All vendors implement this interface. The dashboard routes and
agent code work with ChatPlatform, never with vendor-specific APIs.
"""
@property
@abstractmethod
def name(self) -> str:
"""Platform identifier (e.g., 'discord', 'telegram')."""
@property
@abstractmethod
def state(self) -> PlatformState:
"""Current connection state."""
@abstractmethod
async def start(self, token: Optional[str] = None) -> bool:
"""Start the platform connection. Returns True on success."""
@abstractmethod
async def stop(self) -> None:
"""Gracefully disconnect."""
@abstractmethod
async def send_message(
self, channel_id: str, content: str, thread_id: Optional[str] = None
) -> Optional[ChatMessage]:
"""Send a message. Optionally within a thread."""
@abstractmethod
async def create_thread(
self, channel_id: str, title: str, initial_message: Optional[str] = None
) -> Optional[ChatThread]:
"""Create a new thread in a channel."""
@abstractmethod
async def join_from_invite(self, invite_code: str) -> bool:
"""Join a server/workspace using an invite code."""
@abstractmethod
def status(self) -> PlatformStatus:
"""Return current platform status."""
@abstractmethod
def save_token(self, token: str) -> None:
"""Persist token for restarts."""
@abstractmethod
def load_token(self) -> Optional[str]:
"""Load persisted token."""