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>
75 lines
2.4 KiB
Python
75 lines
2.4 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from sqlmodel import Session, select
|
|
|
|
from app.auth import TWITCH_AUTH_URL, exchange_code, get_twitch_user, get_current_user
|
|
from app.database import get_session
|
|
from app.models import Settings, User
|
|
from app.templates import templates
|
|
|
|
router = APIRouter(prefix="/auth")
|
|
|
|
|
|
@router.get("/login", response_class=HTMLResponse)
|
|
async def login_page(request: Request, user=Depends(get_current_user)):
|
|
if user:
|
|
return RedirectResponse(url="/")
|
|
return templates.TemplateResponse("login.html", {"request": request})
|
|
|
|
|
|
@router.get("/twitch")
|
|
async def twitch_redirect():
|
|
return RedirectResponse(url=TWITCH_AUTH_URL)
|
|
|
|
|
|
@router.get("/callback")
|
|
async def twitch_callback(request: Request, code: str, session: Session = Depends(get_session)):
|
|
try:
|
|
tokens = await exchange_code(code)
|
|
twitch_user = await get_twitch_user(tokens["access_token"])
|
|
except Exception:
|
|
raise HTTPException(status_code=400, detail="OAuth failed")
|
|
|
|
user = session.exec(select(User).where(User.twitch_id == twitch_user["id"])).first()
|
|
is_first = user is None
|
|
|
|
if not user:
|
|
user = User(
|
|
twitch_id=twitch_user["id"],
|
|
twitch_login=twitch_user["login"],
|
|
twitch_display_name=twitch_user["display_name"],
|
|
broadcaster_id=twitch_user["id"],
|
|
access_token=tokens["access_token"],
|
|
refresh_token=tokens.get("refresh_token", ""),
|
|
is_admin=True,
|
|
)
|
|
else:
|
|
user.access_token = tokens["access_token"]
|
|
user.refresh_token = tokens.get("refresh_token", user.refresh_token)
|
|
user.twitch_display_name = twitch_user["display_name"]
|
|
|
|
session.add(user)
|
|
|
|
if is_first:
|
|
settings = session.exec(select(Settings)).first()
|
|
if not settings:
|
|
settings = Settings()
|
|
settings.twitch_channel = twitch_user["login"]
|
|
session.add(settings)
|
|
|
|
session.commit()
|
|
session.refresh(user)
|
|
request.session["user_id"] = user.id
|
|
|
|
if is_first:
|
|
from app.main import start_eventsub
|
|
import asyncio
|
|
asyncio.create_task(start_eventsub())
|
|
|
|
return RedirectResponse(url="/", status_code=303)
|
|
|
|
|
|
@router.get("/logout")
|
|
async def logout(request: Request):
|
|
request.session.clear()
|
|
return RedirectResponse(url="/auth/login")
|