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>
60 lines
1.6 KiB
Python
60 lines
1.6 KiB
Python
import asyncio
|
|
import os
|
|
from contextlib import asynccontextmanager
|
|
from typing import Optional
|
|
|
|
from dotenv import load_dotenv
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
from starlette.middleware.sessions import SessionMiddleware
|
|
|
|
from app.database import create_db, get_admin_user
|
|
from app.routers import actions, auth, dashboard, layout, media, overlay, settings
|
|
|
|
load_dotenv()
|
|
|
|
_eventsub_task: Optional[asyncio.Task] = None
|
|
|
|
|
|
def get_eventsub_task() -> Optional[asyncio.Task]:
|
|
return _eventsub_task
|
|
|
|
|
|
async def start_eventsub() -> None:
|
|
global _eventsub_task
|
|
from app.eventsub import run_eventsub
|
|
|
|
admin = get_admin_user()
|
|
if not admin:
|
|
return
|
|
|
|
if _eventsub_task and not _eventsub_task.done():
|
|
_eventsub_task.cancel()
|
|
try:
|
|
await _eventsub_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
_eventsub_task = asyncio.create_task(run_eventsub(admin.broadcaster_id, admin.access_token))
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
create_db()
|
|
await start_eventsub()
|
|
yield
|
|
if _eventsub_task and not _eventsub_task.done():
|
|
_eventsub_task.cancel()
|
|
|
|
|
|
app = FastAPI(title="BashyOverlay", lifespan=lifespan)
|
|
app.add_middleware(SessionMiddleware, secret_key=os.getenv("SECRET_KEY", "dev-secret-change-me"))
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
app.include_router(auth.router)
|
|
app.include_router(dashboard.router)
|
|
app.include_router(overlay.router)
|
|
app.include_router(actions.router)
|
|
app.include_router(media.router)
|
|
app.include_router(layout.router)
|
|
app.include_router(settings.router)
|