Twitch stream event platform with OBS browser source overlay. Features: - Twitch OAuth login (streamer + mod access) - EventSub WebSocket for subs, gift subs, bits, channel points, follows, raids - Chat commands via tmi.js in the overlay - Gap audio — tone plays on new chat message after silence, volume/pitch scale with gap length - Self-hosted media library (upload sounds and videos) - Action configuration — map any event to sound, video, or alert - Per-action cooldowns, test button, enable/disable toggle - Multiple actions per event (all matching actions fire) - Activity log dashboard with EventSub connection status - Layout editor — iframe-based WYSIWYG with drag handles, live style preview - Custom CSS and custom JS injection into overlay - Custom DOM events (bashyoverlay:sub, bashyoverlay:raid, etc.) for custom JS hooks - deploy.sh — one-shot setup and launch script Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
from datetime import datetime
|
|
from typing import List, Optional
|
|
|
|
from sqlmodel import Field, Relationship, SQLModel
|
|
|
|
|
|
class User(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
twitch_id: str = Field(unique=True, index=True)
|
|
twitch_login: str
|
|
twitch_display_name: str
|
|
broadcaster_id: str
|
|
access_token: str
|
|
refresh_token: str
|
|
is_admin: bool = False
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
class MediaFile(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
filename: str
|
|
original_name: str
|
|
file_type: str # "audio" | "video"
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
actions: List["EventAction"] = Relationship(back_populates="media_file")
|
|
|
|
|
|
class EventAction(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
event_type: str # "sub" | "gift_sub" | "bits" | "channel_points" | "command"
|
|
event_detail: Optional[str] = None # command name, reward ID, sub tier, bits minimum
|
|
action_type: str # "sound" | "video" | "alert"
|
|
media_file_id: Optional[int] = Field(default=None, foreign_key="mediafile.id")
|
|
alert_text: Optional[str] = None
|
|
cooldown_seconds: int = 0
|
|
enabled: bool = True
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
media_file: Optional["MediaFile"] = Relationship(back_populates="actions")
|
|
|
|
|
|
class ActivityLog(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
event_type: str
|
|
username: Optional[str] = None
|
|
detail: Optional[str] = None
|
|
action_taken: Optional[str] = None
|
|
triggered_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
class Settings(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
twitch_channel: str = ""
|
|
gap_silence_threshold: int = 30
|
|
gap_scale_end: int = 120
|
|
gap_pitch_scale_end: int = 300
|
|
custom_css: str = ""
|
|
custom_js: str = ""
|
|
|
|
|
|
class OverlayLayout(SQLModel, table=True):
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
# Chat widget — position/size as % of canvas
|
|
chat_left: float = 72.0
|
|
chat_top: float = 5.0
|
|
chat_width: float = 27.0
|
|
chat_height: float = 88.0
|
|
chat_font_size: int = 14
|
|
chat_bg_opacity: float = 0.55
|
|
chat_text_color: str = "#ffffff"
|
|
chat_max_messages: int = 20
|
|
# Alert widget — position as % of canvas (centered via transform)
|
|
alert_left: float = 50.0
|
|
alert_top: float = 40.0
|
|
alert_font_size: int = 24
|
|
alert_bg_opacity: float = 0.8
|
|
alert_text_color: str = "#ffffff"
|
|
alert_duration: int = 4
|