from __future__ import annotations from dataclasses import dataclass, field, asdict from datetime import datetime from enum import Enum from typing import Any, Dict, List, Optional class Platform(str, Enum): TWITCH = "twitch" YOUTUBE = "youtube" class UserRole(str, Enum): BROADCASTER = "broadcaster" MODERATOR = "moderator" VIP = "vip" SUBSCRIBER = "subscriber" VIEWER = "viewer" @dataclass class Emote: """Represents an emote that can be rendered in chat.""" code: str url: str provider: str # "twitch", "ffz", "bttv", "7tv", "youtube" is_animated: bool = False emote_id: Optional[str] = None # For dynamic resolution selection @dataclass class ChatBadge: """User badge (mod, subscriber, etc.).""" name: str icon_url: Optional[str] = None @dataclass class ChatUser: """Represents a chat user.""" id: str username: str display_name: str platform: Platform color: Optional[str] = None roles: List[UserRole] = field(default_factory=list) badges: List[ChatBadge] = field(default_factory=list) def to_dict(self) -> Dict[str, Any]: return { "id": self.id, "username": self.username, "display_name": self.display_name, "platform": self.platform.value, "color": self.color, "roles": [r.value for r in self.roles], "badges": [{"name": b.name, "icon_url": b.icon_url} for b in self.badges], } @dataclass class ChatMessage: """Represents a single chat message from either platform.""" id: str platform: Platform user: ChatUser message: str timestamp: datetime emotes: List[Emote] = field(default_factory=list) is_deleted: bool = False is_action: bool = False # /me messages def to_dict(self) -> Dict[str, Any]: return { "id": self.id, "platform": self.platform.value, "user": self.user.to_dict(), "message": self.message, "timestamp": self.timestamp.isoformat(), "emotes": [ { "code": e.code, "url": e.url, "provider": e.provider, "is_animated": e.is_animated, "emote_id": e.emote_id, } for e in self.emotes ], "is_deleted": self.is_deleted, "is_action": self.is_action, } @dataclass class AuthTokens: """OAuth tokens for a platform.""" access_token: str refresh_token: Optional[str] = None expires_at: Optional[datetime] = None scope: List[str] = field(default_factory=list) username: Optional[str] = None # The authenticated user's username def is_expired(self) -> bool: if not self.expires_at: return False return datetime.now() >= self.expires_at def to_dict(self) -> Dict[str, Any]: return { "access_token": self.access_token, "refresh_token": self.refresh_token, "expires_at": self.expires_at.isoformat() if self.expires_at else None, "scope": self.scope, "username": self.username, } @dataclass class ChatConfig: """Configuration for the chat widget.""" # Authentication twitch_enabled: bool = False youtube_enabled: bool = False # Display settings max_messages: int = 50 show_timestamps: bool = True show_badges: bool = True show_platform_icons: bool = True unified_view: bool = True # True = mixed, False = separate columns # Emote providers enable_ffz: bool = True enable_bttv: bool = True enable_7tv: bool = True # Filtering filter_by_roles: List[UserRole] = field(default_factory=list) blocked_keywords: List[str] = field(default_factory=list) min_message_length: int = 0 # Twitch specific twitch_channel: str = "" # YouTube specific youtube_video_id: str = "" def to_dict(self) -> Dict[str, Any]: return asdict(self)