Compare commits

..

95 Commits

Author SHA1 Message Date
ba1d486d07 Dashboard: Changed stylesheet and started prepping html for population. 2025-11-17 02:17:15 +01:00
0967983483 Update: Logic with Twitch login now works. 2025-11-17 02:10:00 +01:00
8829228295 change: Added separate stylesheet under /static and added task to TASKS.md 2025-11-17 01:59:35 +01:00
8236d5e837 feat: Implement session management and dashboard page 2025-11-17 01:56:03 +01:00
f2ebde841d Added: Dashboard html file and routing 2025-11-17 01:50:06 +01:00
cafb39d9df Day close: Added memory for AI agent. Added small corrections to DEVELOPMENT_PLAN. 2025-11-17 01:45:25 +01:00
35c2210305 prep: Making way for the planned 'Dashboard' page and correcting Twitch bot scope, corrected. 2025-11-17 01:28:47 +01:00
6ed78cf3fb prep: Making way for the planned 'Dashboard' page and correcting Twitch bot scope 2025-11-17 01:16:21 +01:00
3fb360aa77 prep: Making way for the planned 'Dashboard' page 2025-11-17 01:03:31 +01:00
f15f339de8 refactor: Move login.html to static directory 2025-11-17 01:01:36 +01:00
603eb84d34 feat: Implement static login page and fix server errors. Attempt 2 2025-11-17 00:54:13 +01:00
6a7c96a82c feat: Implement static login page and fix server errors 2025-11-17 00:49:39 +01:00
9b44c088b8 chore: Added redirect URL. APP_BASE_URL set in .env 2025-11-17 00:46:15 +01:00
98c1417bf1 chore: Add static directory for frontend assets 2025-11-17 00:37:44 +01:00
c4edd88b71 Update: Building start of login page, based on previous attempt. 2025-11-17 00:34:13 +01:00
3827744154 Update: requirements were missing security features. Module 'itsdangerous' incorporated 2025-11-17 00:28:21 +01:00
11254095ff docs: Update task board and align development plan 2025-11-17 00:23:54 +01:00
264e4c276d Connecting local program to Twitch dev-portal credentials 2025-11-17 00:19:13 +01:00
60417d4594 Preparing storage of generated keys 2025-11-17 00:10:47 +01:00
8825421335 Starting implementation of helper functions to encrypt and decrypt Oauth tokens before storing them. Added requirement python-dotenv and cryptography 2025-11-17 00:05:46 +01:00
e4df8d0e15 Update: Finished automated updates for Discord. Added some details concerning communications 2025-11-16 23:58:50 +01:00
21f175af9a chore: Trigger webhook for testing 2025-11-16 23:47:50 +01:00
aa5c63296a chore: Trigger webhook for testing 2025-11-16 23:40:00 +01:00
5931a0bdfe chore: Trigger webhook for testing 2025-11-16 23:38:17 +01:00
407a6447fd chore: Trigger webhook for testing 2025-11-16 23:34:38 +01:00
643f6f4272 chore: Trigger webhook for testing 2025-11-16 23:29:45 +01:00
dab3fb4bb4 chore: Trigger webhook for testing 2025-11-16 23:27:50 +01:00
582a12fbe7 chore: Trigger webhook for testing 2025-11-16 23:27:08 +01:00
55672d4631 chore: Trigger webhook for testing 2025-11-16 23:26:33 +01:00
ecd8518bd9 chore: Trigger webhook for testing 2025-11-16 23:25:35 +01:00
3f462e34a1 Update: Added info in the Readme 2025-11-16 23:21:20 +01:00
a661ab0b9f Update: Added info in the Readme 2025-11-16 23:17:07 +01:00
2b6a4cc3ab Update: Corrected info in the Readme 2025-11-16 23:09:04 +01:00
bd9e801042 Update: Corrected info in the Readme 2025-11-16 23:06:04 +01:00
761659fffb Update: Added info in the Readme 2025-11-16 23:04:20 +01:00
1a53135050 Update: Changed minor detail in the task list. 2025-11-16 22:51:59 +01:00
3d6e78b356 chore: Trigger webhook for testing 2025-11-16 22:46:31 +01:00
49dcf3b8a8 chore: Trigger webhook for testing 2025-11-16 22:45:15 +01:00
8c09a9ac82 chore: Trigger webhook for testing 2025-11-16 22:43:53 +01:00
7ee83501ba chore: Trigger webhook for testing 2025-11-16 22:40:02 +01:00
48747edb9c chore: Trigger webhook for testing 2025-11-16 22:25:08 +01:00
78da04fece chore: Trigger webhook for testing 2025-11-16 22:20:26 +01:00
f6e2fedebc chore: Trigger webhook for testing 2025-11-16 22:17:33 +01:00
1ad2be8dea chore: Trigger webhook for testing 2025-11-16 22:12:35 +01:00
0475315bab chore: Trigger webhook for testing 2025-11-16 21:57:08 +01:00
36efa91e1f chore: Trigger webhook for testing 2025-11-16 21:55:05 +01:00
545ec0ad0f chore: Trigger webhook for testing 2025-11-16 21:52:39 +01:00
fb551af208 chore: Trigger webhook for testing 2025-11-16 21:46:52 +01:00
e31b6701db chore: Trigger webhook for testing 2025-11-16 21:00:33 +01:00
8678b508b7 chore: Trigger webhook for testing 2025-11-16 20:59:38 +01:00
9e4f7da306 chore: Trigger webhook for testing 2025-11-16 20:57:04 +01:00
4a0b08fa25 chore: Trigger webhook for testing 2025-11-16 20:56:23 +01:00
81338c1ecf chore: Trigger webhook for testing 2025-11-16 20:55:45 +01:00
ce8dd866ec chore: Trigger webhook for testing 2025-11-16 20:55:07 +01:00
f157c8e7df chore: Trigger webhook for testing 2025-11-16 20:54:43 +01:00
2265d24578 chore: Trigger webhook for testing 2025-11-16 20:48:46 +01:00
b7c7ca6745 chore: Trigger webhook for testing 2025-11-16 20:45:37 +01:00
c4dbb7eca9 chore: Trigger webhook for testing 2025-11-16 20:45:12 +01:00
7aa6b8a675 chore: Trigger webhook for testing 2025-11-16 20:43:17 +01:00
ca096e8639 chore: Trigger webhook for testing 2025-11-16 20:38:22 +01:00
99c46f2e47 chore: Trigger webhook for testing 2025-11-16 20:37:33 +01:00
bb70dcfa05 chore: Trigger webhook for testing 2025-11-16 20:37:08 +01:00
be23f184c9 chore: Trigger webhook for testing 2025-11-16 20:36:54 +01:00
d48188cfa7 chore: Trigger webhook for testing 2025-11-16 20:36:04 +01:00
098dc52248 chore: Trigger webhook for testing 2025-11-16 20:35:16 +01:00
2e6e605f24 chore: Trigger webhook for testing 2025-11-16 20:13:30 +01:00
1b9bf938d6 chore: Trigger webhook for testing 2025-11-16 20:12:29 +01:00
232d24fd10 chore: Trigger webhook for testing 2025-11-16 20:10:13 +01:00
8ff003e393 Update: Tasks updated. More icons. 2025-11-16 20:05:07 +01:00
ac87daec68 Update: Tasks updated 2025-11-16 20:03:16 +01:00
d09e15ae86 chore: Trigger webhook for testing 2025-11-16 20:02:13 +01:00
873dc6e736 chore: Trigger webhook for testing 2025-11-16 20:00:49 +01:00
55c55a7055 chore: Trigger webhook for testing 2025-11-16 20:00:20 +01:00
2e19fd6f40 chore: Trigger webhook for testing 2025-11-16 19:56:38 +01:00
6aec15c408 chore: Trigger webhook for testing 2025-11-16 19:53:27 +01:00
d702343db6 Update: Tasks updated 2025-11-16 19:49:59 +01:00
0eacb12c73 Update: main.py and models.py had incorrect imports 2025-11-16 19:34:19 +01:00
1186c91dcf chore: Update dependencies and task list 2025-11-16 18:27:38 +01:00
56bf044bb6 Set up database.py
:wq

A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
B
B
B
B
B
B
A
A
2025-11-16 18:16:10 +01:00
4a951df300 Fix: Correct gitignore 2025-11-16 17:58:27 +01:00
8f16c2c4df Updated tasks 2025-11-16 17:21:38 +01:00
4d2566b48a Fix: Correct Markdown links in README 2025-11-16 17:14:28 +01:00
62ee9ad954 howto, tasks and readme update 2025-11-16 17:04:19 +01:00
e31ccbbb19 Start 2025-11-16 16:58:28 +01:00
cdc5c32429 Deleted sensitive data 2025-11-16 16:23:14 +01:00
2427be3a53 Updated Markdown formatting 2025-11-16 16:21:59 +01:00
8554be1c51 Research notes 2025-11-16 16:19:13 +01:00
Jo Eskil
6a50156ca8 Clarify SESSION_SECRET usage in setup instructions 2025-11-14 00:26:25 +01:00
Jo Eskil
8e713ec506 Emphasize filling in Twitch API credentials and session secret in config.php 2025-11-14 00:26:09 +01:00
Jo Eskil
56bb514efa Clarify external domain configuration for Twitch OAuth and Base URL 2025-11-14 00:24:03 +01:00
Jo Eskil
562e7792e9 Fix Twitch OAuth redirect URI and update setup instructions 2025-11-14 00:23:03 +01:00
Jo Eskil
b1b7759e13 Add setup and testing instructions 2025-11-13 23:51:15 +01:00
Jo Eskil
fcd2b30b58 Update development plan to clarify YouTube integration timeline 2025-11-13 23:44:52 +01:00
Jo Eskil
2ed89fecbc Cleaned project directory and added new development plan for simplified stack 2025-11-13 23:43:42 +01:00
Jo Eskil
384d5364a8 Add new development plan for simplified stack 2025-11-13 23:43:42 +01:00
36 changed files with 1453 additions and 522 deletions

9
.env.example Normal file
View File

@@ -0,0 +1,9 @@
# This is an example file. Copy it to .env and fill in your actual secrets.
# The .env file is ignored by Git and should NEVER be committed.
ENCRYPTION_KEY=your_32_byte_url_safe_base64_encoded_key_goes_here
TWITCH_CLIENT_ID=your_twitch_client_id_goes_here
TWITCH_CLIENT_SECRET=your_twitch_client_secret_goes_here
APP_BASE_URL=http://localhost:8000

49
.gitignore vendored
View File

@@ -1 +1,48 @@
.gemini/
# Byte-compiled / optimized / DLL files
__pycache__/
*.pyc
*.pyo
*.pyd
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environments
venv/
env/
.venv/
.env/
# SQLite databases
*.db
*.sqlite
*.sqlite3
# Environment variables
.env
.env.*
!.env.example
# Personal notes - will not be tracked by git
personal_notes.md
# Local-only directory for untracked notes and context
local/

189
CONTEXT.md Normal file
View File

@@ -0,0 +1,189 @@
# Gemini Code Assist: Project Context
This document outlines my core instructions and my current understanding of the MultiChatOverlay project.
---
## Part 1: My Core Instructions
My primary function is to act as a world-class senior software engineering assistant. My goal is to provide insightful answers that prioritize code quality, clarity, and adherence to best practices.
My core instructions are:
* **Persona:** Gemini Code Assist, a very experienced coding assistant.
* **Objective:** Answer questions thoroughly, review code, and suggest improvements.
* **Output Format:**
* Provide all code changes as diffs in the unified format, using full absolute file paths.
* Ensure all code blocks are valid and well-formatted.
* Suggest relevant next steps or prompts for our workflow.
* Maintain a conversational, accurate, and helpful tone.
---
## Part 2: My Understanding of the Project
### Project Goal
The objective is to build a multi-platform chat overlay SaaS (Software as a Service) for streamers. The service will aggregate chat from Twitch and YouTube into a single, customizable browser source for use in streaming software like OBS.
### Current Status
The project is in **Phase 1: User Authentication & Database**. Most of this phase is complete.
* A solid FastAPI application skeleton is in place.
* The database schema (`User`, `Setting` models) is defined using SQLAlchemy and a SQLite database.
* A secure Twitch OAuth2 authentication flow is fully functional. It correctly:
1. Redirects users to Twitch.
2. Handles the callback.
3. Exchanges the authorization code for tokens.
4. Fetches user details from the Twitch API.
5. Encrypts the tokens using the `cryptography` library.
6. Saves or updates the user's record in the database.
* A basic HTML login page is served at the root URL (`/`).
* Configuration and secrets are managed securely via a `config.py` file that reads from a `.env` file.
### Core Architecture
The project is built on the "hybrid architecture" detailed in the `RESEARCH_REPORT.md`:
* **Authentication:** Always use the official, secure OAuth2 flows for each platform.
* **Twitch Chat Ingestion:** Use the stable and scalable Twitch IRC protocol (via `twitchio`).
* **YouTube Chat Ingestion:** Use an unofficial, reverse-engineered "InnerTube" API (via `pytchat`). This is the primary technical risk of the project due to its fragility and will require careful implementation with proxy rotation and monitoring.
### Immediate Next Task
Based on the `TASKS.md` file, the only remaining task for Phase 1 is:
* **Task 1.4: Basic Session Management:** After a user successfully logs in, we need to create a persistent session for them. This will allow us to "remember" who is logged in, protect routes like the future `/dashboard`, and provide a seamless user experience. The current flow correctly authenticates the user but does not yet establish this persistent session.
## References:
### Development plan
```
# Multi-Platform Chat Overlay Development Plan (v4 - Simplified Stack)
This document outlines the development plan for a multi-user, web-based chat overlay service using a simplified technology stack.
## 1. Project Overview
The goal is to create a service where streamers can log in using their platform accounts (Twitch, YouTube), configure a personalized chat overlay, and use it in their streaming software (e.g., OBS). The service will aggregate chat from their connected accounts and provide moderation tools.
## 2. Technology Stack
* **Team Communications:** Discord and Nextcloud, primarily. This can change. There's a list of links in the [README.md](README.md)
* **Backend:** Python 3.13+ (FastAPI)
* **Database:** SQLite (for initial development) with SQLAlchemy ORM
* **Frontend:** Vanilla HTML, CSS, and JavaScript
* **Chat Listeners:** `twitchio` (Twitch), `pytchat` (YouTube)
## 3. Implementation Roadmap
### Phase 1: User Authentication & Database (FastAPI)
1. **Project Skeleton:** Establish the core FastAPI application structure, dependencies, and version control.
2. **Database Schema:** Define the data models for users and settings using SQLAlchemy.
3. **Twitch OAuth2:** Implement the server-side OAuth2 flow within FastAPI to authenticate users and securely store encrypted tokens in the database.
4. **Session Management:** Create a system to manage logged-in user sessions.
5. **Basic Frontend:** Develop a simple login page.
### Phase 2: User Dashboard & Configuration
1. **Dashboard UI:** Create a dashboard page accessible only to authenticated users.
2. **Settings API:** Build API endpoints for users to save and retrieve their overlay settings (e.g., custom CSS).
3. **Overlay URL Generation:** Display a unique, persistent overlay URL for each user on their dashboard.
### Phase 3: Dynamic Listeners & Basic Overlay
1. **Dynamic Listener Manager:** Design and build a background service that starts and stops chat listener processes (`twitchio`, `pytchat`) based on user activity.
2. **Real-time Message Broadcasting:** Implement a WebSocket system within FastAPI to push chat messages to the correct user's overlay in real-time.
3. **Basic Overlay UI:** Create the `overlay.html` page that connects to the WebSocket and renders incoming chat messages.
### Phase 4: Integration & Refinement
1. **YouTube Integration:** Implement the full YouTube OAuth2 flow and integrate the `pytchat` listener into the dynamic listener manager.
2. **Advanced Overlay Customization:** Add more features for users to customize their overlay's appearance and behavior.
3. **Twitch Chat Writeback:** Re-introduce the `chat:write` scope during authentication to allow the service (and potentially moderators, as per Issue #2) to send messages to the user's Twitch chat.
## 4. Requirements for Completion (Initial Version)
The project will be considered complete for its initial version when Phases 1, 2, and 3 are functional:
1. Users can log in with their Twitch account.
2. Users can see their unique overlay URL on a dashboard.
3. The overlay successfully connects to their Twitch chat and displays messages when opened in a browser source.
## 6. Future Enhancements from Gitea Issues
These are enhancement suggestions gathered from the project's Gitea issues, representing potential future features or considerations:
* **Issue #1: Multi select chat display order**
* Allow streamer to click on messages that appear whilst discussing chat message already on screen. This will enable quick progress through important messages without having to scroll back up chat.
* **Issue #2: Moderator chat assistance with streamer over ride**
* Moderators can select messages on their end, marking them for discussion, freeing up the streamer to simply stream. The streamer would have override to reject messages as the stream owner.
* **Issue #3: Chat Speed toggle for busier chat streams**
* Implement a toggle to adjust the display speed of chat messages, useful for very active streams.
* **Issue #4: Auto add YT Superchats to Highlights**
* Add a setting to automatically include YouTube Superchats in the highlighted messages.
* **Issue #5: Donations page somewhere**
* Consider integrating a donations page or feature within the service.
```
### Tasks
```
# Project Task List
This file tracks all active development tasks. It is based on the official `DEVELOPMENT_PLAN.md`.
## 📋 How to Use This List
1. Find a task in the "To Do" section that you want to work on.
2. Add your name next to it (e.g., `[ ] Task Name - @YourName`).
3. When you start, move it to "In Progress" and follow the `CONTRIBUTING.md` workflow.
4. When your Pull Request is *merged*, move it to "Done."
If you want to use emojis for visibility, here's some I have used:
✔️ - Done | 🧑‍🔧 - In progress | ↗️ - Task evolved (should correspond with an edit in the [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md)
---
## 🚀 Phase 1: User Authentication & Database
* **Goal:** Get the basic API, database, and Twitch login flow working.
### To Do
* `[ ]` **1.4: Basic Session Management:** Create a simple session/JWT system to know *who* is logged in.
### In Progress
### Done
* `[✔️]` **1.0: Project Skeleton** - @ramforth
* *Task:* Setup `main.py`, `requirements.txt`, and `.gitignore`.
* `[✔️]` **1.1: Database Schema:** Define SQLAlchemy models for `User` (id, username, platform, encrypted_tokens) and `Settings`. @ramforth
* `[✔️]` **1.1.5: Discord Overview:** Create an automated 'TASK-LIST' and post to Discord whenever someone pushes a change to the repository. @ramforth
* `[✔️]` **1.2: Twitch OAuth API:** Create FastAPI endpoints for `/login/twitch` (redirect) and `/auth/twitch/callback` (handles token exchange). @ramforth
* `[✔️]` **1.3: Secure Token Storage:** Implement helper functions to `encrypt` and `decrypt` OAuth tokens before storing them in the database. @ramforth
* `[✔️]` **1.5: Login Frontend:** Create a basic `login.html` file with a "Login with Twitch" button. @ramforth
---
## ⏳ Phase 2: User Dashboard & Configuration
* **Goal:** Allow logged-in users to see a dashboard, get their overlay URL, and save settings.
* *(All tasks for this phase are on hold until Phase 1 is complete)*
### To Do
* `[ ]` **2.1: Dashboard UI:** Create `dashboard.html` (only for logged-in users).
* `[ ]` **2.2: Config API:** Create API endpoints (`GET`, `POST`) for `/api/settings` to save/load user preferences (e.g., custom CSS).
* `[ ]` **2.3: Overlay URL:** Generate and display the unique overlay URL for the user (e.g., `/overlay/{user_id}`).
---
## 💬 Phase 3: Dynamic Listeners & Basic Overlay
* **Goal:** The core magic. Start chat listeners for users and show messages in the overlay.
* *(All tasks for this phase are on hold until Phase 2 is complete)*
### To Do
* `[ ]` **3.1: Dynamic Listener Manager (The Big One):** Design a system (e.g., background service) to start/stop listener processes for users.
* `[ ]` **3.2: User-Specific Broadcasting:** Update the WebSocket system to use "rooms" (e.g., `/ws/{user_id}`) so users only get their *own* chat.
* `[ ]` **3.3: Basic Overlay UI:** Create the `overlay.html` page that connects to the WebSocket and displays messages.
---
## 💡 Backlog & Future Features
* *(Tasks from Phase 4, Gitea Issues, etc., will be added here as we go)*
* `[ ]` Implement YouTube OAuth & `pytchat` listener (Phase 4).
* `[ ]` "Single Message Focus" feature (Issue #1).
* `[ ]` Moderator panels (Issue #2).
* `[ ]` Custom CSS storage & injection (Issue #6).
```

99
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,99 @@
# Contribution & Workflow Guide
Welcome to the MultiChatOverlay project! To ensure we can collaborate effectively and avoid errors, we follow a strict and professional development workflow.
## 📜 The Golden Rules
1. **Gitea is the Source of Truth.** The `main` branch on our Gitea server is the *only* source of truth.
2. **NEVER Commit to `main`.** All work must be done in a separate "feature branch" and submitted as a Pull Request.
3. **NEVER Work on the Server.** The staging server (`192.168.10.33`) is for *testing* the `main` branch. It is **NOT** a development environment. All development must be done on your **local machine**.
---
## 🛠️ Your Local Setup (One Time)
You only need to do this once.
1. **Install Tools:**
* [Git](https://git-scm.com/downloads)
* [Visual Studio Code](https://code.visualstudio.com/)
* [Python 3.9+](https://www.python.org/downloads/)
2. **Clone the Repo:** Clone the project from our Gitea server to your local computer:
```bash
git clone [https://gitea.ramforth.net/ramforth/MultiChatOverlay.git](https://gitea.ramforth.net/ramforth/MultiChatOverlay.git)
cd MultiChatOverlay
```
3. **Install VS Code Extensions:**
* Open the `MultiChatOverlay` folder in VS Code.
* Go to the Extensions tab and install:
* `Python` (Microsoft)
* `Gemini` (Google)
4. **Create Your Virtual Environment:**
```bash
# From the terminal in VS Code
python -m venv venv
```
* VS Code should auto-detect this and ask to use it. Click "Yes."
5. **Install Dependencies:**
```bash
# Make sure your 'venv' is activated
pip install -r requirements.txt
```
You are now ready to develop!
---
## 💡 Your Daily Workflow (The "Loop")
This is the process you will follow *every time* you want to add a new feature or fix a bug.
1. **Get Latest Code:** Make sure your local `main` branch is up-to-date.
```bash
git checkout main
git pull
```
2. **Create a New Branch:** Create a new branch for your task. Name it clearly (e.g., `feature/twitch-auth`, `bugfix/css-error`).
```bash
git checkout -b feature/my-new-feature
```
3. **Write Code!**
* This is where you do your work.
* Use the **Gemini plugin** in VS Code to help you.
* **Pro-tip:** Open the Gemini chat and give it context by pasting in files like `DEVELOPMENT_PLAN.md` or the `TASKS.md` file so it understands the goal.
4. **Test Locally:** Run the FastAPI server on your *local* machine to make sure your feature works and doesn't break anything.
```bash
uvicorn main:app --reload
```
5. **Commit Your Work:** Once it's working, save your changes.
```bash
git add .
git commit -m "Add new feature: brief description here"
```
6. **Push Your Branch:** Push your *new branch* (not `main`) to Gitea.
```bash
git push -u origin feature/my-new-feature
```
7. **Open a Pull Request:**
* Go to the Gitea website.
* You will see a prompt to "Open a Pull Request" for your new branch.
* Fill it out, describe your changes, and submit it for review.
A project lead will then review your code, and once approved, it will be merged into the `main` branch and deployed to the staging server for final testing.
🚀 Automatic Deployment (The Webhook)
We have set up an automated "hotline" that connects our code storage (Gitea) to our live server.
Here's how it works:
**Code is Saved**: A developer saves new code to our Gitea project.
**Gitea Calls the Server**: Gitea immediately "calls" a special, secret address on our server.
**Server Verifies the Call**: A "listener" program on the server answers and checks a secret password to make sure the call is genuinely from Gitea and not an impostor.
**Server Updates Itself**: Once verified, the listener automatically runs our deploy.sh script. This script fetches all the new code and restarts the application.
The result: The server is always running the latest version of the code, and no one has to log in to update it manually. It's completely automatic.

View File

@@ -1,79 +1,44 @@
# Multi-Platform Chat Overlay Development Plan (v3)
# Multi-Platform Chat Overlay Development Plan (v4 - Simplified Stack)
This document outlines the development plan for a multi-user, web-based chat overlay service.
This document outlines the development plan for a multi-user, web-based chat overlay service using a simplified technology stack.
## 1. Project Overview
The goal is to create a service where streamers can log in using their platform accounts (Twitch, YouTube), configure a personalized chat overlay, and use it in their streaming software (e.g., OBS). The service will aggregate chat from their connected accounts and provide moderation tools.
## 2. Architecture
## 2. Technology Stack
The system will be a web application with a Python backend and a web-based frontend.
* **Team Communications:** Discord and Nextcloud, primarily. This can change. There's a list of links in the [README.md](README.md)
* **Backend:** Python 3.13+ (FastAPI)
* **Database:** SQLite (for initial development) with SQLAlchemy ORM
* **Frontend:** Vanilla HTML, CSS, and JavaScript
* **Chat Listeners:** `twitchio` (Twitch), `pytchat` (YouTube)
1. **Python Backend (FastAPI):**
* Manages user authentication (OAuth2 for Twitch and YouTube).
* Stores user data, configuration, and encrypted access tokens in a database (SQLite initially).
* Provides a REST API for the frontend to manage user settings.
* Dynamically starts, stops, and manages chat listener processes for active users.
* Runs a WebSocket server to broadcast chat messages to the correct user's overlay.
## 3. Implementation Roadmap
2. **Frontend (HTML/CSS/JavaScript):**
* **Login Page:** Allows users to sign in with their Twitch or YouTube accounts.
* **Dashboard:** A secure area for users to manage their connected accounts, configure their overlay, and get their unique overlay URL.
* **Web Overlay:** The customizable chat display, loaded as a Browser Source in OBS.
## 3. Technology Stack
* **Backend:**
* **Language:** Python 3.9+
* **Web Framework:** FastAPI
* **Database:** SQLite with SQLAlchemy
* **Authentication:** OAuth2 (via `httpx` or a similar library)
* **Chat Listeners:** `pytchat` (YouTube), `TwitchIO` (Twitch)
* **Frontend:**
* **Markup/Styling:** HTML5, CSS3
* **JavaScript:** Vanilla JavaScript
* **Communication:** Fetch API, WebSockets
## 4. Implementation Roadmap
### Phase 1: User Authentication & Database
1. **Database Setup:**
* Initialize a SQLite database.
* Define the database schema using SQLAlchemy for a `users` table (storing profile info, channel IDs, and encrypted OAuth tokens).
2. **Install Dependencies:** Add `SQLAlchemy` and an OAuth library to the project.
3. **OAuth2 for Twitch:**
* Implement the FastAPI backend routes for Twitch login (`/login/twitch`) and the callback (`/auth/twitch/callback`).
* Securely store user profile information and tokens in the database.
4. **Login Frontend:** Create a `login.html` page with a "Login with Twitch" button.
5. **Session Management:** Implement basic session management to keep users logged in.
### Phase 1: User Authentication & Database (FastAPI)
1. **Project Skeleton:** Establish the core FastAPI application structure, dependencies, and version control.
2. **Database Schema:** Define the data models for users and settings using SQLAlchemy.
3. **Twitch OAuth2:** Implement the server-side OAuth2 flow within FastAPI to authenticate users and securely store encrypted tokens in the database.
4. **Session Management:** Create a system to manage logged-in user sessions.
5. **Basic Frontend:** Develop a simple login page.
### Phase 2: User Dashboard & Configuration
1. **Dashboard UI:** Create a `dashboard.html` page that is only accessible to logged-in users.
2. **Display User Data:** Show the user's connected Twitch account information.
3. **Configuration API:** Create backend API endpoints for users to submit and update their settings (e.g., which chat platforms to connect, overlay appearance settings).
4. **Configuration Form:** Add a form to the dashboard for users to manage these settings.
5. **Overlay URL:** Generate and display a unique, persistent overlay URL for the user (e.g., `/overlay/{user_id}`).
1. **Dashboard UI:** Create a dashboard page accessible only to authenticated users.
2. **Settings API:** Build API endpoints for users to save and retrieve their overlay settings (e.g., custom CSS).
3. **Overlay URL Generation:** Display a unique, persistent overlay URL for each user on their dashboard.
### Phase 3: Dynamic Chat Listeners & Basic Overlay
1. **Listener Management:**
* Design a system to start and stop chat listener processes based on user activity (e.g., when a user is "live" or has the dashboard open). This may involve a background task manager.
* Refactor the existing `chat_listeners.py` to be controlled by this manager.
2. **User-Specific Broadcasting:**
* Update the WebSocket system to route messages from a specific listener to that user's overlay instance.
3. **Basic User Overlay:**
* When a user accesses their unique overlay URL, the backend serves the `index.html` overlay.
* The overlay connects to the WebSocket and receives only that user's chat messages.
### Phase 3: Dynamic Listeners & Basic Overlay
1. **Dynamic Listener Manager:** Design and build a background service that starts and stops chat listener processes (`twitchio`, `pytchat`) based on user activity.
2. **Real-time Message Broadcasting:** Implement a WebSocket system within FastAPI to push chat messages to the correct user's overlay in real-time.
3. **Basic Overlay UI:** Create the `overlay.html` page that connects to the WebSocket and renders incoming chat messages.
### Phase 4: YouTube Integration & Advanced Features
1. **OAuth2 for YouTube:**
* Implement the backend routes for YouTube login and callback.
* Update the database and dashboard to handle a second connected account.
2. **Moderator & Host Panels:** Re-introduce the concepts of the Host and Moderator panels from v2, but adapted for the multi-user model. This will involve secure access and user-specific controls.
3. **Advanced Overlay Customization:** Add more options for users to customize the look and feel of their overlay via the dashboard.
### Phase 4: Integration & Refinement
1. **YouTube Integration:** Implement the full YouTube OAuth2 flow and integrate the `pytchat` listener into the dynamic listener manager.
2. **Advanced Overlay Customization:** Add more features for users to customize their overlay's appearance and behavior.
3. **Twitch Chat Writeback:** Re-introduce the `chat:write` scope during authentication to allow the service (and potentially moderators, as per Issue #2) to send messages to the user's Twitch chat.
## 5. Requirements for Completion (Initial Version)
## 4. Requirements for Completion (Initial Version)
The project will be considered complete for its initial version when Phases 1, 2, and 3 are functional:
1. Users can log in with their Twitch account.

View File

@@ -1,17 +0,0 @@
# Use an official Python runtime as a parent image
FROM python:3.9-slim
# Set the working directory in the container
WORKDIR /app
# Copy the requirements file into the container
COPY requirements.txt .
# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application code into the container
COPY . .
# Command to run the application
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-w", "4", "-b", "0.0.0.0:8000", "main:app"]

View File

@@ -1,38 +0,0 @@
# Project Progress Report
This document summarizes the key steps and decisions made during the initial setup and planning of the MultiChat Overlay project.
## 1. Initial Setup & Environment Assessment
- **Environment Assessed:** The initial container environment was assessed, and `python3` and `git` were confirmed to be installed.
- **Project Directory:** The project directory was created at `/home/joe/MultiChatOverlay`.
- **Collaboration:** The directory permissions were configured to allow for collaboration by setting the group to `collaborators` and permissions to `775`.
- **Git Repository:** The project was cloned from the Gitea repository at `https://gitea.ramforth.net/ramforth/MultiChatOverlay`.
## 2. Initial Development (Single-User Model)
- **Virtual Environment:** A Python virtual environment was created at `/home/joe/MultiChatOverlay/venv`.
- **Dependencies:** Initial Python dependencies (`fastapi`, `uvicorn`, `websockets`, `pytchat`, `twitchio`) were installed.
- **Basic Framework:** A basic FastAPI application was created with a WebSocket endpoint and a simple HTML overlay.
- **Chat Listeners:** Placeholder chat listener modules were created for YouTube and Twitch.
## 3. Pivot to a Multi-User Service
- **New Requirement:** The project direction was updated to create a multi-user, web-based service where users can log in with their streaming accounts.
- **Revised Development Plan:** A new development plan (`DEVELOPMENT_PLAN.md` v3) was created to reflect this change. The new plan focuses on user authentication, a database, and dynamic management of chat listeners.
- **Plan Synced:** The revised development plan was pushed to the Gitea repository.
## 4. Implementation of the Multi-User Framework
- **Database:** A SQLite database was initialized, and a `users` table schema was defined using SQLAlchemy.
- **Authentication:** A placeholder Twitch OAuth2 implementation was created in `auth.py`.
- **Login Frontend:** A `login.html` page and a corresponding FastAPI endpoint were created.
- **Session Management:** Basic session management using signed cookies was implemented to keep users logged in.
- **Dashboard:** A protected `/dashboard` endpoint and a simple `dashboard.html` page were created.
- **Usage Guide:** A `USAGE.md` file was created to document the login process for end-users.
- **Code Synced:** All changes for the initial multi-user framework were pushed to the Gitea repository.
## 5. Pivot to a Production-Ready Docker-Based Framework
- **New Requirement:** The need for a more robust, scalable, and easily accessible framework was identified.
- **Revised Plan:** A new plan was created to use Docker Compose to manage the application services.
- **Docker Compose Plan:**
- A `web` service for the FastAPI application.
- A `db` service using PostgreSQL.
- The use of `gunicorn` with `uvicorn` workers for the production server.
- The use of Docker volumes to allow for easy collaboration on frontend files.

View File

@@ -1,8 +0,0 @@
There is a need for a chat overlay in live streaming circles that allows streamers to read chat from several of the primary platforms. Twitch and Youtube at the very least.
The idea:
Use self hosted web server to present the user integration with account linking and setup instructions. Let the end user log in with Youtube and Twitch. Have preset templates for html overlay for OBS use.
Must have functions to highlight single messages, preferably from a dockable html browser dock in OBS studio.
This is a specific extension of project: /home/joe/Cloud9/Documents/Obisdian/projects/youtube-chat-webhook-v2

View File

@@ -1,30 +1,43 @@
# Multi-Platform Chat Overlay
# MultiChatOverlay
A self-hosted tool to aggregate chat from multiple streaming platforms (YouTube, Twitch) and display it in a customizable OBS overlay. This project also provides a suite of tools for both the streamer (host) and their moderators to manage the chat.
MultiChatOverlay is a web-based, multi-platform chat overlay service designed for streamers. The goal is to create a "SaaS" (Software as a Service) project where users can log in with their platform accounts (Twitch, YouTube, etc.) and get a single, unified, and customizable chat overlay for their stream.
## Core Features
This project is currently in **Phase 1: Initial Development**.
* **Combined Chat:** Aggregates chat from YouTube and Twitch into a single feed.
* **OBS Overlay:** A customizable web-based overlay to display the chat in your stream.
* **Host Control Panel:** A dockable OBS panel for the streamer to:
* Highlight important messages to feature them on the overlay.
* View a queue of messages tagged by moderators.
* Dismiss queued messages after they have been addressed.
* **Moderator Panel:** A separate, secure web page for moderators to:
* View the combined chat feed.
* "Tag" or "Queue" important messages for the host's attention.
## 🚀 Project Goal
## Technology Stack
* **Unified Chat:** Aggregate chat from multiple platforms (starting with Twitch & YouTube) into one browser source.
* **Customization:** Allow users to save their own custom CSS and use templates.
* **Interaction:** Provide "single message focus" and other moderation tools for streamers and their teams.
* **Self-Hosted:** The service is hosted by the project owner (you) and provided to users.
* **Backend:** Python 3.9+ with FastAPI and WebSockets.
* **Frontend:** HTML5, CSS3, and vanilla JavaScript.
* **Chat Libraries:** `pytchat` for YouTube and `TwitchIO` for Twitch.
## 🔒 Security & Privacy
## Getting Started
User privacy and security are paramount. All sensitive user credentials, such as OAuth access and refresh tokens from external platforms, are **always encrypted** before being stored in the database. They are never stored in plain text, ensuring a high standard of security for user data.
_(Instructions will be added here once the initial version is complete)_
## <20> Technology Stack
1. Clone the repository.
2. Install Python dependencies.
3. Run the backend server.
4. Add the frontend components to OBS as Browser Sources/Docks.
* **Backend:** Python 3.9+ (FastAPI)
* **Database:** SQLite (initially, for simplicity) with SQLAlchemy
* **Chat Listeners:** `twitchio` (Twitch), `pytchat` (YouTube)
* **Frontend:** Vanilla HTML5, CSS3, and JavaScript (Fetch API, WebSockets)
* **Authentication:** OAuth2 for all external platforms.
## 📖 Development & Contribution
This project follows a professional development workflow. Gitea is our single source of truth.
* **Want to contribute?** See our [CONTRIBUTING.md](CONTRIBUTING.md) file for the complete setup guide and workflow rules.
* **Looking for a task?** See the [TASKS.md](TASKS.md) file for a list of current jobs, broken down by phase.
* **Want the full plan?** See the [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md) for the complete project roadmap.
## ⁉️ Acknowledgements
For the project we are using Discord 💬 and Nextcloud ☁️ for communications.
* ☁️ [Nextcloud](https://cloud9.ramforth.net/)
* 💬 [Discord](https://discord.gg/Zaxp6ch9hs)
* 🌐 [Public website](https://multichat.ramforth.net/)
##
👨‍💻 - Coded on and for Linux - 2025

497
RESEARCH_REPORT.md Normal file
View File

@@ -0,0 +1,497 @@
# Architect's Design Report: A Hybrid Model for Scalable Multi-Platform Chat Ingestion
## Executive Summary: The Hybrid Architecture for Scalable Chat Ingestion
This document serves as the principal architectural blueprint for the proposed multi-tenant (SaaS) chat overlay platform. It provides a definitive technical design that directly addresses the core challenge: building a scalable, low-latency ingestion pipeline for real-time chat from Twitch and YouTube, capable of supporting thousands of concurrent users on a Python (FastAPI) backend.
The central conflict at the heart of this design problem is the profound mismatch between the platform's real-time, high-frequency requirements and the intended use of official, public-facing APIs. The official APIs, particularly the YouTube Data API v3, are designed for low-frequency data retrieval and information management, not for high-frequency, low-latency streaming. This is enforced via a strict quota system that makes them quantitatively non-viable for this application.
For example, the YouTube Data API v3's default quota of 10,000 units per day is the primary blocker.[1, 2, 3] A single call to the `liveChatMessages.list` endpoint, which is the official method for fetching chat, costs 5 quota units.[4] A reasonable poll rate of 3 seconds (20 polls per minute) for a single user would exhaust the _entire_ platform's 10,000-unit quota in approximately 100 minutes of streaming.[4] This renders the official API completely unusable for a scalable SaaS.
The mandated solution is a "hybrid" architecture that bifurcates the system's logic, separating user-facing authentication from high-performance chat ingestion.
1. **Authentication Path:** This path will use the 100% official, secure, and documented OAuth 2.0 Authorization Code Grant Flows for both Twitch and Google.[5, 6] This ensures that all user-facing interactions are secure, trustworthy, and handled according to industry-best practices. The platform will securely manage user tokens for API calls.
2. **Ingestion Path:** This path will completely bypass the non-viable "front door" APIs, opting instead for more direct, high-performance protocols.
- **For Twitch:** The system will bypass the modern EventSub API [7] and instead utilize the legacy, but massively scalable, Twitch IRC protocol over a secure WebSocket.[8] This protocol is purpose-built for high-volume, "unlimited read" chat.[9]
- **For YouTube:** The system will bypass the _entire_ official Data API v3. Ingestion will be handled by a "scraper" component that reverse-engineers YouTube's internal, unauthenticated, and undocumented "InnerTube" API.[10] This is the same internal API used by the YouTube web application itself to display chat.
This hybrid model presents a clear architectural trade-off. For Twitch, the solution is robust and relies on a stable, albeit legacy, protocol. For YouTube, the solution is highly efficient but operationally fragile. The primary technical bottleneck for the entire platform will be the maintenance and risk-management of the YouTube "InnerTube" client, which is subject to unannounced changes by Google that could break ingestion for all users. The architecture must be built with this fragility as a core assumption, incorporating robust mitigation strategies.
The following table summarizes the definitive architectural choices detailed in this report.
| | | | |
|---|---|---|---|
|**Platform**|**Challenge**|**Recommended Method**|**Rationale**|
|**Twitch**|**Authentication**|Official OAuth 2.0 Authorization Code Flow [5]|Server-side security standard; required for `client_secret` storage.|
|**Twitch**|**Chat Ingestion**|**Twitch IRC** (over WebSocket) [8]|Massively scalable; no read/connection limits.[9] Architecturally simpler for this use case than EventSub.[7, 11]|
|**YouTube**|**Authentication**|Official Google OAuth 2.0 Server-Side Flow [6]|Server-side security standard; allows for `offline` access to get refresh tokens.[12]|
|**YouTube**|**Chat Ingestion**|**Unofficial "InnerTube" API** (The `pytchat` method) [10, 13]|The _only_ viable method. Official API quota is catastrophically insufficient (10k units/day).[3, 4]|
## Part 1: Twitch Platform Integration Blueprint
### 1.1. User Authentication Protocol (OAuth 2.0)
For a server-side application (FastAPI) that must securely store a `client_secret` and manage tokens on behalf of users, the **Authorization Code Grant Flow** is the required and recommended OAuth 2.0 flow.[5, 14, 15]
#### Step-by-Step Technical Walkthrough
The flow involves a secure, five-step server-side process:
1. **Step 1: Redirect User to Twitch:** The FastAPI server generates a unique `state` token for CSRF protection and constructs a URL. The user is then redirected to the Twitch authorization endpoint.[16]
- **Endpoint:** `GET https://id.twitch.tv/oauth2/authorize` [17]
- **Query Parameters:**
- `client_id`: Your application's registered client ID.[5]
- `redirect_uri`: Your server's pre-registered callback endpoint.[18]
- `response_type`: Must be `code`.[18]
- `scope`: A space-delimited string of requested scopes (see below).[17]
- `state`: The server-generated CSRF token.
2. **Step 2: User Authorizes:** The user is prompted to log into Twitch (if not already) and presented with the consent screen detailing the requested `scope` permissions.[14] Upon clicking "Authorize," Twitch proceeds to the next step.
3. **Step 3: Twitch Redirects Back to Server:** Twitch redirects the user's browser back to the `redirect_uri` specified in Step 1. This request will include two query parameters:
- `code`: A temporary, single-use authorization `code`.[5, 16]
- `state`: The original CSRF token. Your server must first validate that the returned `state` matches the one generated in Step 1.
4. **Step 4: Server Exchanges Code for Token:** Upon validating the `state`, your FastAPI backend must _immediately_ make a secure, server-to-server HTTP `POST` request to Twitch's token endpoint to exchange the `code` for a permanent token.[5]
- **Endpoint:** `POST https://id.twitch.tv/oauth2/token` [5, 18]
- **Request Body (`application/x-www-form-urlencoded`):**
- `client_id`: Your app's client ID.[5]
- `client_secret`: Your app's client secret.[5]
- `code`: The `code` received in Step 3.[5]
- `grant_type`: Must be `authorization_code`.[5, 18]
- `redirect_uri`: The _exact_ same URI used in Step 1.[18]
5. **Step 5: Store Tokens and Validate User:** Twitch will respond with a JSON object containing the `access_token` and `refresh_token`.[5, 19] These must be encrypted (e.g., using the `cryptography` library) and stored securely in the database, associated with the user's account.
#### Minimum Scope Requirements
For this architecture, the minimum required scopes are:
- **`chat:read`**: Explicitly required to connect to the IRC server and read chat messages.[8]
- **`chat:write`**: (Recommended) Required to send chat messages via IRC, which is a likely feature for an overlay.[8]
The scope `user:read:chat` is associated with the EventSub method [20, 21] and is **not required** for the recommended IRC architecture.
#### Post-Authentication User Validation
Immediately following Step 5, the service must perform a validation call to fetch the user's stable identifiers. This call bridges the gap between the modern OAuth system and the legacy IRC system. The `access_token` just received is used to call the `Get Users` endpoint.[22, 23]
- **API Call:** `GET https://api.twitch.tv/helix/users`
- **Headers:**
- `Authorization: Bearer <user_access_token>` [22, 23]
- `Client-Id: <your_client_id>` [22]
This request, made without any query parameters, returns the user object associated with the token.[22] The response contains a `data` array with the user's:
- **`id`**: The stable, unique User ID. This must be stored as the primary key for this user in the database.
- **`login`**: The user's lowercase login name (e.g., `twitchdev`).
This step is non-negotiable. The `login` name is **required** by the IRC protocol for the `NICK` command [8] and to `JOIN` the correct channel. This API call is the critical link that translates an OAuth token into the credentials needed for the chat ingestion system.
### 1.2. Critical Analysis: Real-time Chat Ingestion
This is the core of the Twitch problem. A choice must be made between Twitch's modern, recommended API (EventSub) and its legacy, high-performance protocol (IRC).
#### Method A: EventSub (Webhooks or WebSocket)
- **Mechanism:** EventSub is a modern, push-based system where your application subscribes to specific event topics, such as `channel.chat.message`.[11, 24] When a chat message occurs, Twitch sends your server a JSON payload notification. This can be delivered via two transports:
1. **Webhooks:** Twitch sends an HTTP `POST` to a public endpoint you provide and manage.[11]
2. **WebSocket:** Your server maintains a persistent WebSocket connection to Twitch, which then pushes event messages to you.[11]
- **Latency:** Generally low, but it is an _event notification system_, not a raw stream.[7] It is designed for "at least once" delivery, meaning your service must be architected to handle and de-duplicate messages.[11]
- **Viability Assessment:** This method is **not recommended** for this specific multi-tenant SaaS architecture. While Twitch's documentation _recommends_ EventSub over IRC [7, 8], this advice is aimed at smaller, single-channel bots. For a SaaS platform supporting thousands of users, the EventSub-via-webhook model creates massive architectural complexity. The service would need to create, manage, and renew thousands of individual webhook subscriptions, and its API (FastAPI) would be subjected to a high-volume "storm" of inbound HTTP `POST` requests from Twitch. The WebSocket transport is better, but the IRC method is simpler, more direct, and purpose-built for this exact task.
#### Method B: Twitch IRC (via `twitchio` or similar)
- **Mechanism:** This is the definitive, recommended method. Twitch's chat system is, at its core, a modified IRC server.[8] The Python `twitchio` library is a robust, async-first (asyncio) client for this service.[25] Under the hood, the client opens a single, persistent, secure WebSocket (or raw TCP) connection to Twitch's chat server.[8, 26]
- **Server URI:** `wss://irc-ws.chat.twitch.tv:443` (Secure WebSocket) [8]
- **Authentication:** Authentication is performed _per-connection_ immediately after the socket is opened. The client must send three commands:
1. `PASS oauth:<user_access_token>` (This is the token obtained in section 1.1) [8]
2. `NICK <user_login_name>` (This is the `login` name obtained in section 1.1) [8]
3. `JOIN #<user_login_name>` (To join the user's own channel)
- **Rate Limits and Scalability:** This is the most critical factor.
- **Connections:** "There is no limit to connections a single bot can have".[9] Furthermore, connecting multiple clients from a single IP address is an explicitly supported scaling strategy.[27, 28]
- **Read Rate:** "There is no limit to messages these connections can receive".[9] This means the platform can scale to thousands of users by simply opening one persistent connection for each authenticated, active user. A single server running an asynchronous Python application can handle thousands of concurrent WebSocket connections.
- **The Real Bottleneck:** The _only_ significant scaling bottleneck for Twitch is not reading chat, but managing the **`JOIN` rate** during a "thundering herd" scenario (e.g., your service restarts and 10,000 clients try to reconnect and `JOIN` channels simultaneously).
- The `JOIN` rate limit is **20 `JOIN`s per 10 seconds**.[9]
- Your connection management service _must_ implement a global, distributed rate-limiter (e.g., using Redis) to ensure that `JOIN` commands are queued and dispatched at a rate just under this limit.
#### Recommendation
Use **Method B (Twitch IRC)**. It is purpose-built for high-volume, low-latency, "unlimited read" chat ingestion [9] and scales horizontally by simply adding more connections from one or more servers.[27] The `twitchio` library [25] is a suitable async Python client for this architecture.
### 1.3. Service-Side Token Lifecycle Management
The service must be built to handle the entire lifecycle of an OAuth token, including refresh and revocation.
#### Refresh Logic
Access tokens expire. When an API call (e.g., to `/helix/users`) returns a 401 Unauthorized error [14], or when an IRC connection fails with a login error [8], the server must assume the token is expired and trigger the refresh logic.
- **API Call:** `POST https://id.twitch.tv/oauth2/token`
- **Headers:** `Content-Type: application/x-www-form-urlencoded` [19]
- **Request Body (URL-encoded):**
- `grant_type=refresh_token` [19]
- `refresh_token`: The user's stored `refresh_token` [19]
- `client_id`: Your app's `client_id` [19]
- `client_secret`: Your app's `client_secret` [19]
The server will respond with a JSON object containing a _new_ `access_token` and, crucially, a _new_ `refresh_token`.[5, 19] The server _must_ update both of these new credentials in the database, overwriting the old ones.
#### Revocation Logic
When a user disconnects their account from the SaaS platform, their token must be revoked and their data processing must stop. This is a critical two-step process.
1. **Step 1: API Revocation:** The server must call the revocation endpoint to externally invalidate the token, preventing future use.
- **API Call:** `POST https://id.twitch.tv/oauth2/revoke` [29]
- **Headers:** `Content-Type: application/x-www-form-urlencoded`
- **Request Body (URL-encoded):**
- `client_id`: Your app's `client_id` [30]
- `token`: The `access_token` that is being revoked [30, 31]
2. **Step 2: Internal Connection Termination:** Calling the `/revoke` endpoint **does not** disconnect an already-active IRC session.[32] The OAuth token is only validated by the IRC server _at the time of login_.[32] An active connection will remain connected and continue to receive chat messages even after its token is revoked.
- Therefore, your application _must_ maintain an in-memory mapping (e.g., a dictionary or Redis cache) of `user_id` to its active `twitchio` client or WebSocket connection.
- Immediately after a successful revocation API call, the server must look up the user's active connection and **forcibly close the socket**. This ensures all data processing for that user ceases immediately.
## Part 2: YouTube Platform Integration Blueprint
### 2.1. User Authentication Protocol (Google OAuth 2.0)
The process for YouTube (Google) is analogous to Twitch, using the **Server-Side Web Apps Flow**.[6, 33]
#### Step-by-Step Technical Walkthrough
1. **Step 1: Redirect User to Google:** The server generates a `state` token and redirects the user to Google's OAuth 2.0 server.[6]
- **Endpoint:** `GET https://accounts.google.com/o/oauth2/v2/auth`
- **Query Parameters:**
- `client_id`: Your app's client ID.[34]
- `redirect_uri`: Your server's pre-registered callback.[6]
- `response_type`: Must be `code`.
- `scope`: A space-delimited string of requested scopes (see below).
- `access_type`: Must be `offline`. This is **critical** as it is the only way to obtain a `refresh_token`.[12]
- `prompt`: Recommended to be `consent` to ensure a `refresh_token` is returned even on re-authentication.
2. **Step 2: User Authorizes:** The user logs in, selects the Google Account associated with their YouTube Channel, and grants the requested permissions.[6]
3. **Step 3: Google Redirects Back to Server:** Google redirects the user to your `redirect_uri` with the `code` and `state`.[6]
4. **Step 4: Server Exchanges Code for Token:** The FastAPI backend validates the `state` and makes a secure, server-to-server `POST` request.[6]
- **Endpoint:** `POST https://www.googleapis.com/oauth2/v4/token` [35]
- **Request Body (`application/x-www-form-urlencoded` or JSON):**
- `client_id`: Your client ID.[35]
- `client_secret`: Your client secret.[35]
- `code`: The `code` from Step 3.
- `grant_type`: Must be `authorization_code`.[6]
- `redirect_uri`: The _exact_ same URI from Step 1.
5. **Step 5: Store Tokens and Validate Channel:** Google responds with an `access_token` and `refresh_token` (because `access_type=offline` was specified).[36] These must be encrypted and stored.
#### Minimum Scope Requirements
The minimum scope required for this hybrid architecture is:
- **`https://www.googleapis.com/auth/youtube.readonly`** [6, 37]
This is a significant finding. The full-access `.../auth/youtube` scope [38] is _not_ required. The `.../readonly` scope is sufficient to "View your YouTube account" [37], which allows for the necessary post-authentication API calls (like `channels.list` and `liveBroadcasts.list`).[39, 40]
The chat _ingestion_ (reading messages) will be handled by the unauthenticated "scraper" method (see 2.2.B), which requires **no scopes at all**. This allows the platform to request minimal, "read-only" permissions, which vastly increases user trust.
#### Post-Authentication Channel Validation
Immediately after getting the token, the server must find the user's stable YouTube Channel ID.
- **API Call:** `GET https://www.googleapis.com/youtube/v3/channels?part=id&mine=true` [41, 42]
- **Headers:** `Authorization: Bearer <user_access_token>`
- **Quota Cost:** 1 Unit.[3, 41] This is a negligible, one-time cost.
This request will return a JSON object containing the `channelId` for the authenticated user.[42] This `channelId` must be stored as the primary identifier for the user's YouTube account.
### 2.2. Critical Analysis: Real-time Chat Ingestion
This is the most critical design problem for the entire platform. The official API method is unworkable, necessitating an unofficial approach.
#### Method A: Official Data API v3 (`liveChatMessages.list`)
- **Mechanism:** A polling-based REST endpoint. The service would repeatedly call `liveChatMessages.list` with the `liveChatId` of an active stream.[43] New messages are retrieved by passing the `nextPageToken` from the previous response on the next poll.[44]
- **Rate Limits & Quota:** This method is **catastrophically non-viable** for a SaaS application.
- **Default Daily Quota:** 10,000 units per project.[1, 2, 3, 45]
- **Quota Cost:** A single call to `liveChatMessages.list` costs **5 quota units**.[4]
#### Feasibility Analysis (The "Quota Burn" Calculation)
The following analysis demonstrates the non-viability of the official API for even a single user.
| | | |
|---|---|---|
|**Parameter**|**Value**|**Source**|
|Default Daily Quota|10,000 units|[3]|
|Cost of `liveChatMessages.list`|5 units / poll|[4]|
|Total Polls Available (per day)|10,000 / 5 = **2,000 polls**||
|Target Poll Rate (for low latency)|1 poll every 3 seconds|(Query)|
|Polls per Minute|20||
|Polls per Hour|1,200||
|Time for **One User** to Exhaust **Entire 10k Quota**|2,000 polls / 20 polls/min = **100 minutes**||
**Conclusion:** A single user streaming for just over an hour and a half would exhaust the entire 10,000-unit quota for the _entire platform_, shutting down chat services for all other users.[4] This endpoint was not designed for real-time, high-frequency polling.
#### Method B: Unofficial/Scraping (The `pytchat` Method)
- **Mechanism:** This is the **only viable method**. This approach is not traditional HTML scraping (e.g., with BeautifulSoup), which `pytchat` explicitly avoids.[13, 46] Instead, this method involves reverse-engineering and mimicking the internal, undocumented JSON API that the YouTube web application itself uses to populate the chat window. This internal API is sometimes referred to as the "InnerTube" API.[10] The process involves:
1. An initial HTTP request to get a "continuation" token.
2. Subsequent HTTP `POST` requests to an internal endpoint (like `.../get_live_chat`) with the `video_id` and the latest "continuation" token.
3. The server responds with a JSON payload containing a list of new messages and the _next_ "continuation" token. The `pytchat` library [13] is a Python implementation of this reverse-engineered client.
- **Authentication:** **None required.** The client operates in an unauthenticated "visitor" state [10], identical to an anonymous user watching the stream in a browser. This is a massive architectural advantage, as it completely bypasses the OAuth requirement for ingestion.
- **Finding the Chat:** This method only requires the `video_id` of the live stream.[13] It does _not_ need the `liveChatId` from the official API.
- **Rate Limits and Risk:** This is the primary trade-off.
- **Risk 1: Mechanism Breakage:** Because this API is undocumented, Google can (and does) change the endpoint, the request parameters, or the JSON response structure at any time without warning.[10] This can instantly break the entire YouTube ingestion pipeline.
- **Risk 2: IP-Banning:** The rate limits are unknown and enforced by Google's anti-bot detection systems.[47] A single server IP making thousands of high-frequency polls (one for each active user) will be quickly identified as a bot, rate-limited, served CAPTCHAs, or permanently IP-banned.[48, 49]
- **Viability:** This is the only technically feasible method for low-latency, high-frequency, multi-tenant YouTube chat ingestion. The entire architecture must be designed to mitigate its inherent risks.
### 2.3. The `liveChatId` / `video_id` Discovery Problem: A Quota-Free Solution
The `pytchat` method (2.2.B) requires a `video_id` to start. The official API methods for finding a channel's active `video_id` (`search.list`, `liveBroadcasts.list`) cost quota.[3, 50, 51]
Polling the official API _even for discovery_ is unviable at scale.
- `search.list` costs 100 units.[3] Polling this is impossible.
- `liveBroadcasts.list` costs 1 unit.[4] This _seems_ cheap, but polling this for 1,000 users every 2 minutes (to check _if_ they are live) would consume `(1,000 users * 30 polls/hr * 24 hr) = 720,000` units per day. This is 72 times the default 10k quota.
Therefore, the discovery of the `video_id` must _also_ be a quota-free, "scraping" operation. This will be a **Two-Stage Scrape**:
1. **Stage 1: Low-Frequency "Live" Polling:** The service will run a low-frequency background worker (e.g., every 1-2 minutes) for each authenticated YouTube user. This worker will perform a simple `GET` request on the user's public channel page.
- `GET https://www.youtube.com/channel/<channel_id>`
- It will parse the returned HTML for a simple, unique string that indicates a live stream is in progress. Reliable indicators include the presence of a "live" thumbnail (e.g., `hqdefault_live.jpg` [52]) or, more reliably, the string `"text":" watching"`.[53]
- Alternatively, the worker can poll the channel's public RSS feed: `https://www.youtube.com/feeds/videos.xml?channel_id=<channel_id>`.[54, 55] A new entry in this feed often corresponds to a stream starting.
2. **Stage 2: `video_id` Extraction and Handoff:**
- Once the worker detects the "live" string, it knows a stream is active. It then performs a more detailed parse of the _same_ channel page HTML.
- The `video_id` is located within a large JSON blob embedded inside a `<script>` tag, assigned to a variable named `ytInitialData`.[56] The worker will parse this JSON to extract the `video_id` of the live stream.
- This `video_id` is then passed (e.g., via a Redis queue) to the high-frequency ingestion service, which will "spin up" a `pytchat` instance (Method B) for that `video_id`.
This two-stage process allows the platform to discover active streams for thousands of users without consuming a single unit of API quota.
### 2.4. Service-Side Token Lifecycle Management
While the ingestion path is unauthenticated, the service still needs to manage tokens for the initial (and rare) official API calls, such as `channels.list`.
#### Refresh Logic
When an official API call returns a 401, the server must use the `refresh_token`.
- **API Call:** `POST https://www.googleapis.com/oauth2/v4/token` [35]
- **Headers:** `Content-Type: application/x-www-form-urlencoded`
- **Request Body (URL-encoded):**
- `client_id`: Your app's `client_id` [35]
- `client_secret`: Your app's `client_secret` [3T]
- `refresh_token`: The user's stored `refresh_token` [35]
- `grant_type`: Must be `refresh_token` [35]
The server will respond with a JSON object containing a _new_ `access_token`.[35] Unlike Twitch, Google refresh tokens generally do not expire, so a new one is not typically issued.[36] The service must store the new `access_token`.
#### Revocation Logic
When a user disconnects their Google account, the server must revoke the token. Google's revocation endpoint is a simple `GET` request.
- **API Call:** `GET https://accounts.google.com/o/oauth2/revoke?token=<token_to_revoke>` [57]
- The `<token_to_revoke>` can be either the `access_token` or the `refresh_token`. Revoking the refresh token will invalidate the entire grant.
This is simpler than Twitch's revocation, as it's a single `GET` request and does not require a `client_id` or `client_secret`.[57]
## Part 3: Synthesis and Recommended Architecture
### 3.1. The Definitive Hybrid Model
The analysis compels the adoption of a hybrid architecture. The following table provides the definitive model for the platform's authentication and ingestion stack.
| | | | |
|---|---|---|---|
|**Platform**|**Challenge**|**Recommended Method**|**Implementation Detail**|
|**Twitch**|**Authentication**|Official OAuth 2.0 Authorization Code Flow [5]|`POST /oauth2/token` with `grant_type=authorization_code`|
|**Twitch**|**Chat Ingestion**|**Twitch IRC** (via `twitchio`) [8]|`wss://irc-ws.chat.twitch.tv:443`. Auth with `PASS oauth:...` and `NICK...`. One connection per user.|
|**YouTube**|**Authentication**|Official Google OAuth 2.0 Server-Side Flow [6]|`POST /oauth2/v4/token` with `grant_type=authorization_code` and `access_type=offline`.|
|**YouTube**|**Chat Ingestion**|**Unofficial "InnerTube" API** (The `pytchat` method) [10, 13]|Unauthenticated. Polls internal `get_live_chat` endpoint using a `video_id` and "continuation" tokens.|
### 3.2. Primary Architectural Bottleneck and Mitigation
The single biggest technical bottleneck for this hybrid model is the **extreme fragility and platform risk of the YouTube ingestion method**.
The Twitch IRC protocol is stable, documented, and built to be scaled.[9, 27] It is a "solved problem."
The YouTube ingestion method, conversely, relies on a chain of three distinct, undocumented, and fragile reverse-engineered steps:
1. Scraping the channel HTML page for a "live" indicator string.[53]
2. Parsing an embedded `ytInitialData` JSON blob from that HTML [56] to find the `video_id`.
3. Calling an undocumented, internal API (`get_live_chat`) [10] with the correct parameters to get chat "continuation" tokens.[13]
A change by Google to any of these three components—which can happen at any time without warning—will instantly break the platform's entire YouTube chat ingestion pipeline. Furthermore, the high-frequency polling from a central SaaS IP block creates a high risk of being programmatically identified as a bot and IP-banned.[47, 48, 49]
#### Mitigation Strategy
The architecture must be designed from the ground up to treat this fragility as a given.
- **IP Rotation:** All HTTP requests directed at YouTube (for both Stage 1/2 discovery and Stage 3 ingestion) **must not** originate directly from the service's own IP addresses. All requests must be routed through a **large, commercial-grade, rotating proxy pool**.[49] This distributes the load and makes it difficult for Google's anti-bot systems to identify the service's servers as a single entity.
- **User-Agent and Header Randomization:** Every request sent via the proxy pool must also mimic a real web browser by rotating its `User-Agent` string and other HTTP headers (e.g., `Accept-Language`, `Accept-Encoding`) from a large list of valid browser profiles.[58]
- **Circuit Breaker and Monitoring:** The ingestion service must have robust, real-time monitoring and a "circuit breaker" pattern. If the `get_live_chat` endpoint starts returning non-200 status codes, or if the `ytInitialData` JSON parsing fails, the system must:
1. Immediately stop polling for that stream (open the circuit) to avoid triggering a ban.
2. Trigger a high-priority alert to the on-call engineering team, who must be prepared to investigate and patch the scraper.
- **Library Maintenance:** Using a library like `pytchat` [13] offloads the initial maintenance, but the platform must be prepared to fork the library or write its own internal client if `pytchat` breaks and is not updated quickly.
### 3.3. Data Flow Summary: A Single YouTube Chat Message
This is the complete data flow for a YouTube message, from inception to overlay, based on the recommended hybrid model.
**Context:**
- User "Streamer_A" has authenticated with the SaaS. The database contains their `channel_id` (from the one-time `channels.list?mine=true` call [42]).
- A low-frequency "live-check" worker is assigned to `Streamer_A`'s `channel_id`.
- A viewer, "Viewer_B," is watching the stream.
**Data Flow:**
1. **** The "live-check" worker sends a `GET` request to `https://www.youtube.com/channel/Streamer_A_channel_id`. This request is routed through a rotating proxy [49] and has a randomized `User-Agent` header.
2. **** The worker receives the channel's HTML and scans it for the string `"text":" watching"`.[53] It finds the string, confirming the stream is live.
3. **** The worker now parses the _same_ HTML, finds the `<script>` tag containing `var ytInitialData = {...};`, and extracts the JSON blob.[56] It traverses this JSON to find the active `video_id` (e.g., `xyz123`).
4. **** The worker publishes a message to an internal queue (e.g., Redis or RabbitMQ) containing `{"platform": "youtube", "video_id": "xyz123", "user_id": "Streamer_A_internal_id"}`.
5. **** The high-frequency "Chat Ingestion" service consumes this message. It spawns a new asynchronous task (e.g., an `asyncio` coroutine) that instantiates a `pytchat` client [13] for `video_id` `xyz123`.
6. **** This new task begins its polling loop, sending HTTP `POST` requests to YouTube's internal `get_live_chat` API [10] every 3 seconds. These requests are _also_ routed through the rotating proxy pool.[49]
7. **[Viewer Action]** "Viewer_B" types "Hello!" into `Streamer_A`'s YouTube chat and hits send.
8. **** Within 3 seconds, the `pytchat` task's next poll to `get_live_chat` receives a JSON response from YouTube's "InnerTube" server. This JSON payload contains a list of new chat actions, including "Viewer_B"'s "Hello!" message.[13]
9. **** The `pytchat` client parses this JSON, extracts the message, author, timestamp, and other metadata, and yields a standardized Python object.
10. **** The ingestion service places this normalized message onto an internal bus (e.g., Redis pub/sub).
11. **** The main FastAPI server, which holds an active WebSocket connection to `Streamer_A`'s browser overlay, receives this message from the bus and immediately pushes it to the browser.
12. **** The "Hello!" message appears in `Streamer_A`'s chat overlay, having been ingested with low latency, at scale, and without consuming any official API quota.

72
SETUP_AND_TESTING.md Normal file
View File

@@ -0,0 +1,72 @@
# MultiChat Overlay - Setup and Testing Instructions
This document provides instructions to set up and test the MultiChat Overlay application.
## 1. MySQL Database Setup
1. **Install MySQL:** Ensure you have MySQL server installed and running on your system.
2. **Create Database:** Log in to your MySQL server and create a new database.
```sql
CREATE DATABASE multichat_overlay;
```
3. **Create User (Optional but Recommended):** Create a dedicated MySQL user for the application and grant it privileges to the `multichat_overlay` database.
```sql
CREATE USER 'multichat_user'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT ALL PRIVILEGES ON multichat_overlay.* TO 'multichat_user'@'localhost';
FLUSH PRIVILEGES;
```
4. **Update `php/config.php`:** Open `/home/joe/MultiChatOverlay/php/config.php` and update the `DB_USER` and `DB_PASS` constants with your MySQL credentials.
## 2. PHP Development Server Setup
1. **Install PHP:** Ensure you have PHP installed on your system (PHP 7.4+ recommended).
2. **Start Server:** Navigate to the `/home/joe/MultiChatOverlay/php` directory in your terminal and start the PHP built-in web server:
```bash
cd /home/joe/MultiChatOverlay/php
php -S localhost:80 -t .
```
* **Note:** The `-t .` flag tells the server to use the current directory (`php/`) as the document root. This means your application will be accessible at `http://localhost/`.
* If port 80 is already in use, you can choose another port (e.g., `php -S localhost:8000 -t .`) and update the `BASE_URL` in `php/config.php` accordingly.
## 3. Configure Twitch API Credentials and Application Secrets
**IMPORTANT:** Before proceeding, you must update the following values in `/home/joe/MultiChatOverlay/php/config.php`:
* `TWITCH_CLIENT_ID`: Your Twitch application's Client ID.
* `TWITCH_CLIENT_SECRET`: Your Twitch application's Client Secret.
* `SESSION_SECRET`: A very long, random, and secure string for session encryption. Generate this once and keep it consistent. **DO NOT use the placeholder value in production.**
1. **Create Twitch Application:** Go to the Twitch Developer Console (dev.twitch.tv/console/apps) and create a new application.
* Set the **OAuth Redirect URLs** to `http://localhost/auth.php` for local development. When deploying to your external domain (e.g., `https://multichat.ramforth.net`), you must update this to `https://multichat.ramforth.net/auth.php` in both the Twitch Developer Console and `php/config.php`.
2. **Get Client ID and Secret:** Note down your Client ID and generate a Client Secret.
3. **Update `php/config.php`:** Open `/home/joe/MultiChatOverlay/php/config.php` and update the `TWITCH_CLIENT_ID`, `TWITCH_CLIENT_SECRET` constants with your application's credentials. Also, ensure `BASE_URL` is set correctly for your environment (e.g., `http://localhost` for local, `https://multichat.ramforth.net` for external).
## 4. Test the Application
1. **Ensure PHP Server is Running:** Make sure your PHP development server is running as described in step 2.
2. **Access Application:** Open your web browser and navigate to `http://localhost/`.
3. **Login with Twitch:**
* Click on "Get Started" or navigate to `http://localhost/login`.
* Click "Login with Twitch".
* You will be redirected to Twitch for authorization. Ensure you grant the requested permissions (`user:read:email` and `chat:read`).
* You should then be redirected to the dashboard (`http://localhost/dashboard`).
4. **Verify Dashboard:**
* Check if your Twitch username is displayed.
* Verify that the "Twitch: Connected" status is shown.
* Note the "Your Overlay URL" it should now contain your `user_id`.
5. **Test Twitch Chat Listener (Manual):**
* Open a new terminal.
* Navigate to the `/home/joe/MultiChatOverlay` directory.
* Activate your Python virtual environment: `source python/venv/bin/activate`
* Run the Twitch chat listener script, replacing placeholders with your actual values:
```bash
export TWITCH_CLIENT_ID="YOUR_TWITCH_CLIENT_ID"
export TWITCH_CLIENT_SECRET="YOUR_TWITCH_CLIENT_SECRET"
python python/twitch_chat_listener.py "YOUR_TWITCH_ACCESS_TOKEN" "YOUR_TWITCH_CHANNEL_NAME" "YOUR_USER_ID_FROM_DASHBOARD"
```
* **Note:** You will need to manually retrieve your `TWITCH_ACCESS_TOKEN` from the MySQL database for now (e.g., by querying the `users` table).
* `YOUR_TWITCH_CHANNEL_NAME` is your Twitch username.
* `YOUR_USER_ID_FROM_DASHBOARD` is the `user_id` displayed in your overlay URL on the dashboard.
* Send a message in your Twitch channel. You should see the chat message printed in the terminal where `twitch_chat_listener.py` is running.
This completes the basic setup and verification of the Twitch login and chat listener. The next step will be to integrate the Node.js WebSocket server.

67
TASKS.md Normal file
View File

@@ -0,0 +1,67 @@
# Project Task List
This file tracks all active development tasks. It is based on the official `DEVELOPMENT_PLAN.md`.
## 📋 How to Use This List
1. Find a task in the "To Do" section that you want to work on.
2. Add your name next to it (e.g., `[ ] Task Name - @YourName`).
3. When you start, move it to "In Progress" and follow the `CONTRIBUTING.md` workflow.
4. When your Pull Request is *merged*, move it to "Done."
If you want to use emojis for visibility, here's some I have used:
✔️ - Done | 🧑‍🔧 - In progress | ↗️ - Task evolved (should correspond with an edit in the [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md)
---
## 🚀 Phase 1: User Authentication & Database
* **Goal:** Get the basic API, database, and Twitch login flow working.
### To Do
### In Progress
### Done
* `[✔️]` **1.0: Project Skeleton** - @ramforth
* *Task:* Setup `main.py`, `requirements.txt`, and `.gitignore`.
* `[✔️]` **1.1: Database Schema:** Define SQLAlchemy models for `User` (id, username, platform, encrypted_tokens) and `Settings`. @ramforth
* `[✔️]` **1.1.5: Discord Overview:** Create an automated 'TASK-LIST' and post to Discord whenever someone pushes a change to the repository. @ramforth
* `[✔️]` **1.2: Twitch OAuth API:** Create FastAPI endpoints for `/login/twitch` (redirect) and `/auth/twitch/callback` (handles token exchange). @ramforth
* `[✔️]` **1.3: Secure Token Storage:** Implement helper functions to `encrypt` and `decrypt` OAuth tokens before storing them in the database. @ramforth
* `[✔️]` **1.4: Basic Session Management:** Create a simple session/JWT system to know *who* is logged in. @ramforth
* `[✔️]` **1.5: Login Frontend:** Create a basic `login.html` file with a "Login with Twitch" button. @ramforth
---
## ⏳ Phase 2: User Dashboard & Configuration
* **Goal:** Allow logged-in users to see a dashboard, get their overlay URL, and save settings.
* *(All tasks for this phase are on hold until Phase 1 is complete)*
### To Do
* `[ ]` **2.0: CSS Refactor & Styling:** Improve the general look and feel of the application pages.
* `[ ]` **2.1: Dashboard UI:** Create `dashboard.html` (only for logged-in users).
* `[ ]` **2.2: Config API:** Create API endpoints (`GET`, `POST`) for `/api/settings` to save/load user preferences (e.g., custom CSS).
* `[ ]` **2.3: Overlay URL:** Generate and display the unique overlay URL for the user (e.g., `/overlay/{user_id}`).
---
## 💬 Phase 3: Dynamic Listeners & Basic Overlay
* **Goal:** The core magic. Start chat listeners for users and show messages in the overlay.
* *(All tasks for this phase are on hold until Phase 2 is complete)*
### To Do
* `[ ]` **3.1: Dynamic Listener Manager (The Big One):** Design a system (e.g., background service) to start/stop listener processes for users.
* `[ ]` **3.2: User-Specific Broadcasting:** Update the WebSocket system to use "rooms" (e.g., `/ws/{user_id}`) so users only get their *own* chat.
* `[ ]` **3.3: Basic Overlay UI:** Create the `overlay.html` page that connects to the WebSocket and displays messages.
---
## 💡 Backlog & Future Features
* *(Tasks from Phase 4, Gitea Issues, etc., will be added here as we go)*
* `[ ]` Implement YouTube OAuth & `pytchat` listener (Phase 4).
* `[ ]` "Single Message Focus" feature (Issue #1).
* `[ ]` Moderator panels (Issue #2).
* `[ ]` Custom CSS storage & injection (Issue #6).

View File

@@ -1,41 +0,0 @@
# MultiChat Overlay Usage Guide
This guide explains how to log in to the MultiChat Overlay service and connect your streaming accounts to generate a personalized chat overlay.
## 1. Logging In
To use the MultiChat Overlay, you first need to log in with your streaming platform account.
1. **Access the Login Page:** Open your web browser and navigate to the service's login page (e.g., `http://localhost:8000/login`).
2. **Choose Your Platform:** On the login page, you will see options to log in with different streaming platforms.
3. **Authorize MultiChat Overlay:**
* Click on the "Login with Twitch" button.
* You will be redirected to Twitch's authorization page. Review the permissions requested by MultiChat Overlay and click "Authorize" to grant access.
* After successful authorization, you will be redirected back to the MultiChat Overlay dashboard.
## 2. Connecting Services
Currently, only Twitch is supported for connection. YouTube integration will be added in a future update.
### Connecting Twitch
Once you have logged in with your Twitch account, your Twitch channel will automatically be connected. You can verify this on your dashboard.
## 3. Your Personalized Overlay
After logging in and connecting your services, you will find your unique overlay URL on the dashboard.
1. **Copy Overlay URL:** On the dashboard, locate and copy the provided "Overlay URL". This URL is unique to your account.
2. **Add to OBS (or other streaming software):**
* In OBS Studio, add a new "Browser Source".
* Paste your copied Overlay URL into the "URL" field of the Browser Source properties.
* Adjust the width and height of the browser source to match your desired overlay size.
* Click "OK".
Your chat overlay should now appear in your streaming software, displaying messages from your connected Twitch channel.
## 4. Future Integrations (Coming Soon)
* **YouTube Integration:** Connect your YouTube channel to aggregate chat from both platforms.
* **Customization Options:** Personalize the look and feel of your chat overlay directly from the dashboard.
* **Moderator & Host Panels:** Access dedicated panels for advanced moderation and stream management.

Binary file not shown.

Binary file not shown.

Binary file not shown.

137
auth.py
View File

@@ -1,87 +1,110 @@
import os
from fastapi import APIRouter, Depends
import httpx
import secrets
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session
import httpx
from database import get_db, User
from itsdangerous import URLSafeTimedSerializer
# Get configuration from environment variables
TWITCH_CLIENT_ID = os.environ.get("TWITCH_CLIENT_ID")
TWITCH_CLIENT_SECRET = os.environ.get("TWITCH_CLIENT_SECRET")
SECRET_KEY = os.environ.get("SECRET_KEY")
REDIRECT_URI = "http://localhost:8000/auth/twitch/callback" # This will need to be updated for production
serializer = URLSafeTimedSerializer(SECRET_KEY)
from config import settings
from database import SessionLocal
import models
import security
router = APIRouter()
# Dependency to get a DB session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.get("/login/twitch")
async def login_with_twitch():
# Scopes required to get user's email and channel info
scopes = "user:read:email"
async def login_with_twitch(request: Request):
"""
Step 1 of OAuth flow: Redirect the user to Twitch's authorization page.
"""
# Generate a random state token for CSRF protection
state = secrets.token_urlsafe(16)
request.session['oauth_state'] = state
# As per RESEARCH_REPORT.md, these are the minimum required scopes
scopes = "chat:read"
# Construct the authorization URL
auth_url = (
f"https://id.twitch.tv/oauth2/authorize"
f"?client_id={TWITCH_CLIENT_ID}"
f"&redirect_uri={REDIRECT_URI}"
f"&response_type=code"
f"?response_type=code"
f"&client_id={settings.TWITCH_CLIENT_ID}"
f"&redirect_uri={settings.APP_BASE_URL}/auth/twitch/callback"
f"&scope={scopes}"
f"&state={state}"
)
return RedirectResponse(url=auth_url)
@router.get("/auth/twitch/callback")
async def auth_twitch_callback(code: str, db: Session = Depends(get_db)):
# Exchange the authorization code for an access token
async def auth_twitch_callback(code: str, state: str, request: Request, db: Session = Depends(get_db)):
"""
Step 2 of OAuth flow: Handle the callback from Twitch after user authorization.
"""
# CSRF Protection: Validate the state
if state != request.session.pop('oauth_state', None):
raise HTTPException(status_code=403, detail="Invalid state parameter. CSRF attack suspected.")
# Step 4: Exchange the authorization code for an access token
token_url = "https://id.twitch.tv/oauth2/token"
token_data = {
"client_id": TWITCH_CLIENT_ID,
"client_secret": TWITCH_CLIENT_SECRET,
"client_id": settings.TWITCH_CLIENT_ID,
"client_secret": settings.TWITCH_CLIENT_SECRET,
"code": code,
"grant_type": "authorization_code",
"redirect_uri": REDIRECT_URI,
"redirect_uri": f"{settings.APP_BASE_URL}/auth/twitch/callback",
}
async with httpx.AsyncClient() as client:
token_response = await client.post(token_url, data=token_data)
if token_response.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to retrieve access token from Twitch.")
token_json = token_response.json()
access_token = token_json.get("access_token")
refresh_token = token_json.get("refresh_token")
access_token = token_json["access_token"]
refresh_token = token_json["refresh_token"]
if not access_token:
return {"error": "Could not fetch access token"}
# Get user info from Twitch API
user_info_url = "https://api.twitch.tv/helix/users"
# Step 5: Validate the user and get their details from Twitch API
users_url = "https://api.twitch.tv/helix/users"
headers = {
"Client-ID": settings.TWITCH_CLIENT_ID,
"Authorization": f"Bearer {access_token}",
"Client-Id": TWITCH_CLIENT_ID,
}
user_response = await client.get(user_info_url, headers=headers)
user_response = await client.get(users_url, headers=headers)
user_data = user_response.json()["data"][0]
twitch_id = user_data["id"]
twitch_username = user_data["login"]
# Encrypt the tokens for storage
encrypted_tokens = security.encrypt_tokens(access_token, refresh_token)
# Check if user exists in the database, otherwise create them
user = db.query(User).filter(User.twitch_id == twitch_id).first()
if not user:
user = User(
twitch_id=twitch_id,
twitch_username=twitch_username,
twitch_access_token=access_token, # TODO: Encrypt this
twitch_refresh_token=refresh_token, # TODO: Encrypt this
)
db.add(user)
db.commit()
db.refresh(user)
else:
# Update tokens for existing user
user.twitch_access_token = access_token # TODO: Encrypt this
user.twitch_refresh_token = refresh_token # TODO: Encrypt this
db.commit()
# --- Database Upsert Logic ---
# Check if the user already exists in our database
user = db.query(models.User).filter(models.User.platform_user_id == user_data['id']).first()
# Create a session cookie
response = RedirectResponse(url="/dashboard")
session_data = {"user_id": user.id}
session_cookie = serializer.dumps(session_data)
response.set_cookie(key="session", value=session_cookie)
return response
if user:
# If user exists, update their details
user.username = user_data['login']
user.encrypted_tokens = encrypted_tokens
else:
# If user does not exist, create a new record
user = models.User(
platform_user_id=user_data['id'],
username=user_data['login'],
platform="twitch",
encrypted_tokens=encrypted_tokens
)
db.add(user)
db.commit()
# Create a session for the user by storing their database ID.
request.session['user_id'] = user.id
# Redirect to a future dashboard page for a better user experience
# This prepares us for Task 1.4 (Session Management) and Task 2.1 (Dashboard UI)
return RedirectResponse(url="/dashboard")

View File

@@ -1,65 +0,0 @@
# chat_listeners.py
import asyncio
from pytchat import LiveChat
from twitchio.ext import commands
# YouTube Chat Listener (Placeholder)
async def listen_youtube_chat(video_id: str, callback):
livechat = LiveChat(video_id=video_id)
while True:
try:
chatdata = await livechat.get().as_dict()
for c in chatdata['items']:
message = {
"platform": "youtube",
"author": c['author']['name'],
"message": c['message'],
"is_moderator": False # Placeholder
}
await callback(message)
except Exception as e:
print(f"YouTube chat error: {e}")
await asyncio.sleep(1) # Don't hammer the API
# Twitch Chat Listener (Placeholder)
class TwitchBot(commands.Bot):
def __init__(self, token: str, channel: str, callback):
super().__init__(token=token, prefix='!', initial_channels=[channel])
self.callback = callback
async def event_ready(self):
print(f'Logged in as | {self.nick}')
print(f'User ID is | {self.user_id}')
async def event_message(self, message):
if message.echo:
return
is_moderator = 'moderator' in message.tags and message.tags['moderator'] == '1'
chat_message = {
"platform": "twitch",
"author": message.author.name,
"message": message.content,
"is_moderator": is_moderator
}
await self.callback(chat_message)
async def listen_twitch_chat(token: str, channel: str, callback):
bot = TwitchBot(token, channel, callback)
await bot.start()
# Example usage (for testing purposes, not part of the main application flow)
async def main():
async def print_message(message):
print(f"[{message['platform']}] {message['author']}: {message['message']} (Mod: {message['is_moderator']})")
# Replace with actual YouTube video ID and Twitch token/channel
# asyncio.create_task(listen_youtube_chat("YOUR_YOUTUBE_VIDEO_ID", print_message))
# asyncio.create_task(listen_twitch_chat("YOUR_TWITCH_OAUTH_TOKEN", "YOUR_TWITCH_CHANNEL", print_message))
while True:
await asyncio.sleep(3600) # Keep main running
if __name__ == "__main__":
asyncio.run(main())

18
config.py Normal file
View File

@@ -0,0 +1,18 @@
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
class Settings:
"""
A simple class to hold all application settings, loaded from environment variables.
"""
ENCRYPTION_KEY: str = os.getenv("ENCRYPTION_KEY")
TWITCH_CLIENT_ID: str = os.getenv("TWITCH_CLIENT_ID")
TWITCH_CLIENT_SECRET: str = os.getenv("TWITCH_CLIENT_SECRET")
# The full URL where our app is running, needed for the redirect_uri
APP_BASE_URL: str = os.getenv("APP_BASE_URL", "http://localhost:8000")
settings = Settings()

View File

@@ -1,22 +1,27 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>MultiChat Overlay Dashboard</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - MultiChatOverlay</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h1>Welcome to your Dashboard</h1>
<p>This is your personalized dashboard. You can manage your connected accounts and configure your overlay here.</p>
<div class="container">
<h1>Dashboard</h1>
<p>Welcome, <strong>{{ user.username }}</strong>! You are successfully logged in.</p>
<h2>Your Overlay URL</h2>
<p>Use this URL as a browser source in your streaming software:</p>
<pre id="overlay-url"></pre>
<div class="overlay-url-container">
<p>Your unique overlay URL:</p>
<code>{{ overlay_url }}</code>
</div>
<script>
// In a real application, we would fetch the user's unique overlay URL
// from an API and display it here. For now, we'll just show a placeholder.
const overlayUrlElement = document.getElementById('overlay-url');
// This would be something like `http://localhost:8000/overlay/USER_ID`
overlayUrlElement.textContent = `http://localhost:8000/overlay/YOUR_USER_ID`;
</script>
<p><a href="/logout">Logout</a></p>
</div>
</body>
</html>

View File

@@ -1,35 +1,19 @@
import os
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = os.environ.get("DATABASE_URL")
# Define the location of our SQLite database file.
# The ./. indicates it will be in the same directory as our project.
SQLALCHEMY_DATABASE_URL = "sqlite:///./multichat_overlay.db"
engine = create_engine(DATABASE_URL)
# Create the SQLAlchemy engine. The `connect_args` is needed only for SQLite
# to allow it to be used by multiple threads, which FastAPI does.
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
# Each instance of SessionLocal will be a database session.
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# This Base will be used by our model classes to inherit from.
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
twitch_id = Column(String, unique=True, index=True)
twitch_username = Column(String)
# We will store encrypted tokens
twitch_access_token = Column(String)
twitch_refresh_token = Column(String)
youtube_id = Column(String, unique=True, index=True, nullable=True)
youtube_username = Column(String, nullable=True)
youtube_access_token = Column(String, nullable=True)
youtube_refresh_token = Column(String, nullable=True)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
def create_tables():
Base.metadata.create_all(bind=engine)

View File

@@ -1,30 +0,0 @@
version: '3.8'
services:
web:
build: .
command: gunicorn -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:8000 main:app
volumes:
- .:/app
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:password@db/mydatabase
- SECRET_KEY=YOUR_SECRET_KEY # This should be a long, random string
- TWITCH_CLIENT_ID=ogxx1fhpxbg8g89rov6oswuxeup2pb
- TWITCH_CLIENT_SECRET=2660uqpk2e1leayhpwcu35a27zidmh
- REDIRECT_URI=https://multichat.ramforth.net/auth/twitch/callback
depends_on:
- db
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydatabase
volumes:
postgres_data:

View File

@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>MultiChat Overlay</title>
</head>
<body>
<h1>Chat Messages</h1>
<ul id="messages"></ul>
<script>
const messages = document.getElementById('messages');
const ws = new WebSocket("ws://localhost:8000/ws");
ws.onmessage = function(event) {
const chatMessage = JSON.parse(event.data);
const messageElement = document.createElement('li');
messageElement.textContent = `[${chatMessage.platform}] ${chatMessage.author}: ${chatMessage.message}`;
messages.appendChild(messageElement);
};
</script>
</body>
</html>

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Login to MultiChat Overlay</title>
</head>
<body>
<h1>Login to MultiChat Overlay</h1>
<p>Connect your streaming accounts to get started.</p>
<a href="/auth/login/twitch">
<button>Login with Twitch</button>
</a>
<!-- YouTube login will be added in a later phase -->
</body>
</html>

110
main.py
View File

@@ -1,67 +1,65 @@
import asyncio
import json
from fastapi import FastAPI, WebSocket, Request, Depends
from fastapi.responses import HTMLResponse, RedirectResponse
from starlette.websockets import WebSocketDisconnect
from starlette.middleware.base import BaseHTTPMiddleware
from sqlalchemy.orm import Session
import os
from fastapi import FastAPI, Request, Depends
from starlette.middleware.sessions import SessionMiddleware
from starlette.staticfiles import StaticFiles
from starlette.responses import FileResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from contextlib import asynccontextmanager
from chat_listeners import listen_youtube_chat, listen_twitch_chat
from auth import router as auth_router, serializer
from database import get_db, User, create_tables
import models
from database import engine
import auth # Import the new auth module
from config import settings # Import settings to get the secret key
app = FastAPI()
# --- Absolute Path Configuration ---
# Get the absolute path of the directory where this file is located
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
STATIC_DIR = os.path.join(BASE_DIR, "static")
TEMPLATES_DIR = os.path.join(BASE_DIR, "templates")
@app.on_event("startup")
async def startup_event():
create_tables()
# The chat listeners will be started dynamically based on user activity
# and not on application startup.
@asynccontextmanager
async def lifespan(app: FastAPI):
# This code runs on startup
print("Application startup: Creating database tables...")
models.Base.metadata.create_all(bind=engine)
print("Application startup: Database tables created.")
yield
# Code below yield runs on shutdown, if needed
class SessionMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
session_cookie = request.cookies.get("session")
if session_cookie:
try:
data = serializer.loads(session_cookie, max_age=3600 * 24 * 7) # 1 week
db = next(get_db())
user = db.query(User).filter(User.id == data["user_id"]).first()
request.state.user = user
except Exception:
request.state.user = None
else:
request.state.user = None
return response
app = FastAPI(lifespan=lifespan)
# Configure Jinja2 templates
templates = Jinja2Templates(directory=TEMPLATES_DIR)
# Mount the 'static' directory using an absolute path for reliability
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
# Add the authentication router
app.include_router(auth.router)
# Add session middleware. A secret key is required for signing the session cookie.
# We can reuse our encryption key for this, but in production you might want a separate key.
app.add_middleware(SessionMiddleware, secret_key=settings.ENCRYPTION_KEY)
@app.get("/")
async def read_root():
return {"Hello": "World"}
return FileResponse(os.path.join(STATIC_DIR, "login.html"))
@app.get("/login", response_class=HTMLResponse)
async def get_login_page():
with open("login.html", "r") as f:
return f.read()
@app.get("/dashboard")
async def read_dashboard(request: Request, db: Session = Depends(auth.get_db)):
# This is our protected route. It checks if a user_id exists in the session.
user_id = request.session.get('user_id')
if not user_id:
# If not, redirect them to the login page.
return RedirectResponse(url="/")
@app.get("/dashboard", response_class=HTMLResponse)
async def get_dashboard(user: User = Depends(get_current_user)):
if not user:
return RedirectResponse(url="/login")
with open("dashboard.html", "r") as f:
return f.read()
user = db.query(models.User).filter(models.User.id == user_id).first()
overlay_url = f"{settings.APP_BASE_URL}/overlay/{user.id}"
@app.get("/overlay", response_class=HTMLResponse)
async def get_overlay():
with open("index.html", "r") as f:
return f.read()
return templates.TemplateResponse("dashboard.html", {"request": request, "user": user, "overlay_url": overlay_url})
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
connected_clients.append(websocket)
try:
while True:
# Keep the connection alive, or handle incoming messages if needed
await websocket.receive_text()
except WebSocketDisconnect:
connected_clients.remove(websocket)
@app.get("/logout")
async def logout(request: Request):
# Clear the session cookie
request.session.clear()
return RedirectResponse(url="/")

27
models.py Normal file
View File

@@ -0,0 +1,27 @@
from sqlalchemy import Column, Integer, String, Text, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
# The user's unique ID from the platform (e.g., Twitch ID, YouTube Channel ID)
platform_user_id = Column(String, unique=True, index=True, nullable=False)
username = Column(String, index=True, nullable=False)
platform = Column(String, nullable=False) # e.g., "twitch", "youtube"
# A JSON string or other format holding the encrypted access and refresh tokens
encrypted_tokens = Column(Text, nullable=False)
settings = relationship("Setting", back_populates="owner", uselist=False)
class Setting(Base):
__tablename__ = "settings"
id = Column(Integer, primary_key=True, index=True)
custom_css = Column(Text, nullable=True)
user_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="settings")

Binary file not shown.

19
php/auth/twitch/login.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
// php/auth/twitch/login.php - Initiates Twitch OAuth flow
require_once '../../config.php';
// Scopes required to get user's email and channel info
$scopes = "user:read:email chat:read"; // Ensure chat:read is included
$auth_url = (
"https://id.twitch.tv/oauth2/authorize" .
"?client_id=" . TWITCH_CLIENT_ID .
"&redirect_uri=" . TWITCH_REDIRECT_URI .
"&response_type=code" .
"&scope=" . urlencode($scopes)
);
header("Location: " . $auth_url);
exit();
?>

View File

@@ -1,37 +1,8 @@
aiohappyeyeballs==2.6.1
aiohttp==3.13.2
aiosignal==1.4.0
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.11.0
attrs==25.4.0
certifi==2025.11.12
click==8.3.0
fastapi==0.121.1
frozenlist==1.8.0
greenlet==3.2.4
gunicorn==23.0.0
h11==0.16.0
h2==4.3.0
hpack==4.1.0
httpcore==1.0.9
httpx==0.28.1
hyperframe==6.1.0
idna==3.11
itsdangerous==2.2.0
multidict==6.7.0
packaging==25.0
propcache==0.4.1
psycopg2-binary==2.9.11
pydantic==2.12.4
pydantic_core==2.41.5
pytchat==0.5.5
sniffio==1.3.1
SQLAlchemy==2.0.44
starlette==0.49.3
twitchio==3.1.0
typing-inspection==0.4.2
typing_extensions==4.15.0
uvicorn==0.38.0
websockets==15.0.1
yarl==1.22.0
fastapi
uvicorn[standard]
sqlalchemy
httpx
cryptography
python-dotenv
itsdangerous
jinja2

47
security.py Normal file
View File

@@ -0,0 +1,47 @@
import os
import json
from cryptography.fernet import Fernet
from dotenv import load_dotenv
# Load environment variables from a .env file for local development
load_dotenv()
def _get_fernet_instance() -> Fernet:
"""
Helper function to get the Fernet instance.
This ensures the key is checked only when encryption/decryption is needed.
"""
# It is CRITICAL that this key is set in your environment and kept secret.
# It should be a 32-url-safe-base64-encoded key.
encryption_key = os.getenv("ENCRYPTION_KEY")
if not encryption_key:
raise ValueError("ENCRYPTION_KEY is not set in the environment. Please generate a key and add it to your .env file.")
# Ensure the key is in bytes for the Fernet instance
return Fernet(encryption_key.encode())
def encrypt_tokens(access_token: str, refresh_token: str) -> str:
"""
Combines access and refresh tokens into a JSON object, then encrypts it.
"""
fernet = _get_fernet_instance()
tokens = {"access_token": access_token, "refresh_token": refresh_token}
tokens_json_string = json.dumps(tokens)
encrypted_data = fernet.encrypt(tokens_json_string.encode())
return encrypted_data.decode()
def decrypt_tokens(encrypted_data_str: str) -> dict:
"""
Decrypts the token string back into a dictionary of tokens.
"""
fernet = _get_fernet_instance()
decrypted_data_bytes = fernet.decrypt(encrypted_data_str.encode())
tokens_json_string = decrypted_data_bytes.decode()
return json.loads(tokens_json_string)
def generate_key():
"""
Utility function to generate a new encryption key. Run this once.
"""
return Fernet.generate_key().decode()

1
static/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# This file ensures the 'static' directory is tracked by Git.

16
static/dashboard.html Normal file
View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - MultiChatOverlay</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<h1>Dashboard</h1>
<p>Welcome! You are successfully logged in.</p>
<p><a href="/logout">Logout</a></p>
</div>
</body>
</html>

21
static/login.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - MultiChatOverlay</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<h1>Welcome to MultiChatOverlay</h1>
<p>Connect your streaming accounts to get started.</p>
<a href="/login/twitch" class="twitch-btn">Login with Twitch</a>
</div>
</body>
</html>

77
static/style.css Normal file
View File

@@ -0,0 +1,77 @@
body {
/* Use a very dark grey background for contrast, making the container pop */
font-family: 'Inter', sans-serif;
/* Suggest a modern font (requires import) */
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
/* Use min-height for responsiveness */
background-color: #0d1117;
/* Dark Mode base color */
background-image: linear-gradient(135deg, #0d1117 0%, #161b22 100%);
/* Subtle gradient */
margin: 0;
color: #e6edf3;
/* Light text color for contrast */
}
.container {
text-align: center;
padding: 50px;
/* Slightly more padding */
background-color: #161b22;
/* Lighter dark-mode color for the box */
border-radius: 12px;
/* Smoother corners */
/* Modern, subtle layered shadows for depth */
box-shadow:
0 4px 15px rgba(0, 0, 0, 0.4),
/* Primary shadow */
0 10px 30px rgba(0, 0, 0, 0.7);
/* Deep, soft shadow */
/* Optional: Small border for definition */
border: 1px solid #30363d;
/* Slightly increase size */
max-width: 380px;
width: 90%;
}
.twitch-btn {
display: inline-flex;
/* Use flex for easy icon alignment if you add one */
align-items: center;
justify-content: center;
/* Use a slightly brighter, but still core Twitch purple */
background-color: #9146FF;
color: white;
padding: 12px 28px;
/* Slightly larger padding */
border-radius: 8px;
/* Smoother corners */
text-decoration: none;
font-weight: 600;
/* Medium bold */
letter-spacing: 0.5px;
/* Better readability */
transition: all 0.3s ease;
/* Enable smooth transitions */
border: none;
cursor: pointer;
/* Subtle inner shadow for 'pressed' look */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}
/* Add a sleek hover effect */
.twitch-btn:hover {
background-color: #772ce8;
/* Slightly darker purple on hover */
transform: translateY(-2px);
/* Lift the button slightly */
box-shadow: 0 6px 12px rgba(145, 70, 255, 0.3);
/* Glow effect on hover */
}