Compare commits

..

82 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
19 changed files with 706 additions and 46 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

8
.gitignore vendored
View File

@@ -39,4 +39,10 @@ env/
# Environment variables # Environment variables
.env .env
.env.* .env.*
!.env.example !.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).
```

View File

@@ -79,4 +79,21 @@ This is the process you will follow *every time* you want to add a new feature o
* You will see a prompt to "Open a Pull Request" for your new branch. * 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. * 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. 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

@@ -8,44 +8,40 @@ The goal is to create a service where streamers can log in using their platform
## 2. Technology Stack ## 2. Technology Stack
* **Backend (API & Chat Listeners):** Python (for Twitch/YouTube chat listeners), Node.js (for WebSocket server and potentially other APIs), PHP (for user management and web serving). * **Team Communications:** Discord and Nextcloud, primarily. This can change. There's a list of links in the [README.md](README.md)
* **Database:** MySQL * **Backend:** Python 3.13+ (FastAPI)
* **Frontend:** HTML, CSS, JavaScript * **Database:** SQLite (for initial development) with SQLAlchemy ORM
* **Frontend:** Vanilla HTML, CSS, and JavaScript
* **Chat Listeners:** `twitchio` (Twitch), `pytchat` (YouTube)
## 3. Implementation Roadmap ## 3. Implementation Roadmap
### Phase 1: Basic Setup & Twitch Chat Listener (Python) ### Phase 1: User Authentication & Database (FastAPI)
1. **Project Structure:** Establish a clear directory structure for PHP, Python, Node.js, and static assets. 1. **Project Skeleton:** Establish the core FastAPI application structure, dependencies, and version control.
2. **Python Environment:** Set up a Python virtual environment and install `twitchio`. 2. **Database Schema:** Define the data models for users and settings using SQLAlchemy.
3. **Twitch Chat Listener (Python Script):** Create a standalone Python script that connects to Twitch chat, listens for messages, and prints them to standard output. This script will be run as a background process. 3. **Twitch OAuth2:** Implement the server-side OAuth2 flow within FastAPI to authenticate users and securely store encrypted tokens in the database.
4. **Twitch OAuth2 (Python):** Implement a simple Python script or a PHP endpoint to handle Twitch OAuth2 to obtain user access tokens. Store these securely in MySQL. 4. **Session Management:** Create a system to manage logged-in user sessions.
5. **Basic Frontend:** Develop a simple login page.
### Phase 2: MySQL Database & User Management (PHP) ### Phase 2: User Dashboard & Configuration
1. **MySQL Setup:** Set up a MySQL database and create a `users` table to store user information (Twitch ID, username, access token, refresh token). 1. **Dashboard UI:** Create a dashboard page accessible only to authenticated users.
2. **PHP Web Server:** Configure a basic PHP web server. 2. **Settings API:** Build API endpoints for users to save and retrieve their overlay settings (e.g., custom CSS).
3. **User Registration/Login (PHP):** Implement PHP scripts for user registration and login, integrating with the MySQL database. 3. **Overlay URL Generation:** Display a unique, persistent overlay URL for each user on their dashboard.
4. **Dashboard (PHP/HTML):** Create a basic dashboard where logged-in users can see their Twitch connection status and their unique overlay URL.
### Phase 3: WebSocket Server (Node.js) & Overlay (HTML/CSS/JS) ### Phase 3: Dynamic Listeners & Basic Overlay
1. **Node.js Environment:** Set up a Node.js environment and install `ws` (WebSocket library). 1. **Dynamic Listener Manager:** Design and build a background service that starts and stops chat listener processes (`twitchio`, `pytchat`) based on user activity.
2. **WebSocket Server (Node.js):** Create a Node.js WebSocket server that: 2. **Real-time Message Broadcasting:** Implement a WebSocket system within FastAPI to push chat messages to the correct user's overlay in real-time.
* Accepts connections from overlay clients. 3. **Basic Overlay UI:** Create the `overlay.html` page that connects to the WebSocket and renders incoming chat messages.
* Receives chat messages from the Python Twitch listener (via a simple inter-process communication mechanism, e.g., writing to a file or a local socket).
* Broadcasts messages to connected overlay clients.
3. **Overlay Frontend (HTML/CSS/JS):** Create a basic `overlay.html` that:
* Connects to the Node.js WebSocket server.
* Displays incoming chat messages.
4. **Inter-process Communication:** Implement a mechanism for the Python Twitch listener to send messages to the Node.js WebSocket server.
### Phase 4: Integration & Refinement ### Phase 4: Integration & Refinement
1. **Dynamic Listener Management:** Develop a system (e.g., a PHP script or a Node.js API) to start and stop Python Twitch listener processes based on user activity. 1. **YouTube Integration:** Implement the full YouTube OAuth2 flow and integrate the `pytchat` listener into the dynamic listener manager.
2. **YouTube Integration:** Add YouTube chat listening capabilities (Python `pytchat`) and integrate with the existing system. This will be implemented after the core Twitch functionality is stable. 2. **Advanced Overlay Customization:** Add more features for users to customize their overlay's appearance and behavior.
3. **Advanced Overlay Customization:** Implement options for users to customize their overlay. 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) ## 4. Requirements for Completion (Initial Version)
The project will be considered complete for its initial version when Phases 1, 2, and 3 are functional: 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. 1. Users can log in with their Twitch account.
2. Users can see their unique overlay URL on a dashboard. 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. 3. The overlay successfully connects to their Twitch chat and displays messages when opened in a browser source.

View File

@@ -11,7 +11,11 @@ This project is currently in **Phase 1: Initial Development**.
* **Interaction:** Provide "single message focus" and other moderation tools for streamers and their teams. * **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. * **Self-Hosted:** The service is hosted by the project owner (you) and provided to users.
## 💻 Technology Stack ## 🔒 Security & Privacy
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.
## <20> Technology Stack
* **Backend:** Python 3.9+ (FastAPI) * **Backend:** Python 3.9+ (FastAPI)
* **Database:** SQLite (initially, for simplicity) with SQLAlchemy * **Database:** SQLite (initially, for simplicity) with SQLAlchemy
@@ -23,6 +27,17 @@ This project is currently in **Phase 1: Initial Development**.
This project follows a professional development workflow. Gitea is our single source of truth. This project follows a professional development workflow. Gitea is our single source of truth.
* **Want to contribute?** See our `CONTRIBUTING.md` file for the complete setup guide and workflow rules. * **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` file for a list of current jobs, broken down by phase. * **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` for the complete project roadmap. * **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

View File

@@ -9,6 +9,9 @@ This file tracks all active development tasks. It is based on the official `DEVE
3. When you start, move it to "In Progress" and follow the `CONTRIBUTING.md` workflow. 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." 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 ## 🚀 Phase 1: User Authentication & Database
@@ -16,19 +19,18 @@ This file tracks all active development tasks. It is based on the official `DEVE
* **Goal:** Get the basic API, database, and Twitch login flow working. * **Goal:** Get the basic API, database, and Twitch login flow working.
### To Do ### To Do
* `[ ]` **1.1: Database Schema:** Define SQLAlchemy models for `User` (id, username, platform, encrypted_tokens) and `Settings`.
* `[ ]` **1.2: Twitch OAuth API:** Create FastAPI endpoints for `/login/twitch` (redirect) and `/auth/twitch/callback` (handles token exchange).
* `[ ]` **1.3: Secure Token Storage:** Implement helper functions to `encrypt` and `decrypt` OAuth tokens before storing them in the database.
* `[ ]` **1.4: Basic Session Management:** Create a simple session/JWT system to know *who* is logged in.
* `[ ]` **1.5: Login Frontend:** Create a basic `login.html` file with a "Login with Twitch" button.
### In Progress ### In Progress
* `[ ]` **1.0: Project Skeleton** - @ProjectLead
* *Task:* Setup `main.py`, `requirements.txt`, and `.gitignore`.
### Done ### Done
* *(Nothing yet!)* * `[✔️]` **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 ## ⏳ Phase 2: User Dashboard & Configuration
@@ -37,6 +39,7 @@ This file tracks all active development tasks. It is based on the official `DEVE
* *(All tasks for this phase are on hold until Phase 1 is complete)* * *(All tasks for this phase are on hold until Phase 1 is complete)*
### To Do ### 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.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.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}`). * `[ ]` **2.3: Overlay URL:** Generate and display the unique overlay URL for the user (e.g., `/overlay/{user_id}`).

110
auth.py Normal file
View File

@@ -0,0 +1,110 @@
import httpx
import secrets
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session
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(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"?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, 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": settings.TWITCH_CLIENT_ID,
"client_secret": settings.TWITCH_CLIENT_SECRET,
"code": code,
"grant_type": "authorization_code",
"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["access_token"]
refresh_token = token_json["refresh_token"]
# 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}",
}
user_response = await client.get(users_url, headers=headers)
user_data = user_response.json()["data"][0]
# Encrypt the tokens for storage
encrypted_tokens = security.encrypt_tokens(access_token, refresh_token)
# --- 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()
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")

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()

27
dashboard.html Normal file
View File

@@ -0,0 +1,27 @@
<!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="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>Dashboard</h1>
<p>Welcome, <strong>{{ user.username }}</strong>! You are successfully logged in.</p>
<div class="overlay-url-container">
<p>Your unique overlay URL:</p>
<code>{{ overlay_url }}</code>
</div>
<p><a href="/logout">Logout</a></p>
</div>
</body>
</html>

19
database.py Normal file
View File

@@ -0,0 +1,19 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# 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"
# 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()

64
main.py
View File

@@ -1,7 +1,65 @@
from fastapi import FastAPI 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
app = FastAPI() import models
from database import engine
import auth # Import the new auth module
from config import settings # Import settings to get the secret key
# --- 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")
@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
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("/") @app.get("/")
async def read_root(): async def read_root():
return {"message": "MultiChatOverlay API"} return FileResponse(os.path.join(STATIC_DIR, "login.html"))
@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="/")
user = db.query(models.User).filter(models.User.id == user_id).first()
overlay_url = f"{settings.APP_BASE_URL}/overlay/{user.id}"
return templates.TemplateResponse("dashboard.html", {"request": request, "user": user, "overlay_url": overlay_url})
@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")

View File

@@ -1,4 +1,8 @@
fastapi fastapi
uvicorn[standard] uvicorn[standard]
sqlalchemy sqlalchemy
httpx 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 */
}