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>
57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
from fastapi.requests import Request
|
|
from fastapi.responses import HTMLResponse
|
|
|
|
from app.database import get_layout, get_settings
|
|
from app.dispatch import dispatch_event
|
|
from app.templates import templates
|
|
from app.ws import manager
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/overlay", response_class=HTMLResponse)
|
|
async def overlay_page(request: Request):
|
|
settings = get_settings()
|
|
layout = get_layout()
|
|
return templates.TemplateResponse("overlay.html", {
|
|
"request": request,
|
|
"channel": settings.twitch_channel,
|
|
"gap_silence": settings.gap_silence_threshold,
|
|
"gap_scale_end": settings.gap_scale_end,
|
|
"gap_pitch_end": settings.gap_pitch_scale_end,
|
|
"layout": layout,
|
|
"custom_css": settings.custom_css,
|
|
"custom_js": settings.custom_js,
|
|
})
|
|
|
|
|
|
@router.get("/overlay/preview", response_class=HTMLResponse)
|
|
async def overlay_preview(request: Request):
|
|
settings = get_settings()
|
|
layout = get_layout()
|
|
return templates.TemplateResponse("overlay_preview.html", {
|
|
"request": request,
|
|
"layout": layout,
|
|
"custom_css": settings.custom_css,
|
|
"custom_js": settings.custom_js,
|
|
"alert_duration_ms": layout.alert_duration * 1000,
|
|
})
|
|
|
|
|
|
@router.websocket("/ws/overlay")
|
|
async def overlay_ws(websocket: WebSocket):
|
|
await manager.connect(websocket)
|
|
try:
|
|
while True:
|
|
data = await websocket.receive_json()
|
|
if data.get("type") == "command":
|
|
await dispatch_event(
|
|
"command",
|
|
username=data.get("user"),
|
|
event_detail=data.get("name"),
|
|
)
|
|
except WebSocketDisconnect:
|
|
manager.disconnect(websocket)
|
|
except Exception:
|
|
manager.disconnect(websocket)
|