Compare commits
82 Commits
62ee9ad954
...
feature/da
| Author | SHA1 | Date | |
|---|---|---|---|
| ba1d486d07 | |||
| 0967983483 | |||
| 8829228295 | |||
| 8236d5e837 | |||
| f2ebde841d | |||
| cafb39d9df | |||
| 35c2210305 | |||
| 6ed78cf3fb | |||
| 3fb360aa77 | |||
| f15f339de8 | |||
| 603eb84d34 | |||
| 6a7c96a82c | |||
| 9b44c088b8 | |||
| 98c1417bf1 | |||
| c4edd88b71 | |||
| 3827744154 | |||
| 11254095ff | |||
| 264e4c276d | |||
| 60417d4594 | |||
| 8825421335 | |||
| e4df8d0e15 | |||
| 21f175af9a | |||
| aa5c63296a | |||
| 5931a0bdfe | |||
| 407a6447fd | |||
| 643f6f4272 | |||
| dab3fb4bb4 | |||
| 582a12fbe7 | |||
| 55672d4631 | |||
| ecd8518bd9 | |||
| 3f462e34a1 | |||
| a661ab0b9f | |||
| 2b6a4cc3ab | |||
| bd9e801042 | |||
| 761659fffb | |||
| 1a53135050 | |||
| 3d6e78b356 | |||
| 49dcf3b8a8 | |||
| 8c09a9ac82 | |||
| 7ee83501ba | |||
| 48747edb9c | |||
| 78da04fece | |||
| f6e2fedebc | |||
| 1ad2be8dea | |||
| 0475315bab | |||
| 36efa91e1f | |||
| 545ec0ad0f | |||
| fb551af208 | |||
| e31b6701db | |||
| 8678b508b7 | |||
| 9e4f7da306 | |||
| 4a0b08fa25 | |||
| 81338c1ecf | |||
| ce8dd866ec | |||
| f157c8e7df | |||
| 2265d24578 | |||
| b7c7ca6745 | |||
| c4dbb7eca9 | |||
| 7aa6b8a675 | |||
| ca096e8639 | |||
| 99c46f2e47 | |||
| bb70dcfa05 | |||
| be23f184c9 | |||
| d48188cfa7 | |||
| 098dc52248 | |||
| 2e6e605f24 | |||
| 1b9bf938d6 | |||
| 232d24fd10 | |||
| 8ff003e393 | |||
| ac87daec68 | |||
| d09e15ae86 | |||
| 873dc6e736 | |||
| 55c55a7055 | |||
| 2e19fd6f40 | |||
| 6aec15c408 | |||
| d702343db6 | |||
| 0eacb12c73 | |||
| 1186c91dcf | |||
| 56bf044bb6 | |||
| 4a951df300 | |||
| 8f16c2c4df | |||
| 4d2566b48a |
9
.env.example
Normal file
9
.env.example
Normal 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
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -40,3 +40,9 @@ env/
|
||||
.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
189
CONTEXT.md
Normal 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).
|
||||
|
||||
```
|
||||
@@ -80,3 +80,20 @@ This is the process you will follow *every time* you want to add a new feature o
|
||||
* 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.
|
||||
@@ -8,39 +8,35 @@ The goal is to create a service where streamers can log in using their platform
|
||||
|
||||
## 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).
|
||||
* **Database:** MySQL
|
||||
* **Frontend:** HTML, CSS, JavaScript
|
||||
* **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: Basic Setup & Twitch Chat Listener (Python)
|
||||
1. **Project Structure:** Establish a clear directory structure for PHP, Python, Node.js, and static assets.
|
||||
2. **Python Environment:** Set up a Python virtual environment and install `twitchio`.
|
||||
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.
|
||||
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.
|
||||
### 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: MySQL Database & User Management (PHP)
|
||||
1. **MySQL Setup:** Set up a MySQL database and create a `users` table to store user information (Twitch ID, username, access token, refresh token).
|
||||
2. **PHP Web Server:** Configure a basic PHP web server.
|
||||
3. **User Registration/Login (PHP):** Implement PHP scripts for user registration and login, integrating with the MySQL database.
|
||||
4. **Dashboard (PHP/HTML):** Create a basic dashboard where logged-in users can see their Twitch connection status and their unique overlay URL.
|
||||
### 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: WebSocket Server (Node.js) & Overlay (HTML/CSS/JS)
|
||||
1. **Node.js Environment:** Set up a Node.js environment and install `ws` (WebSocket library).
|
||||
2. **WebSocket Server (Node.js):** Create a Node.js WebSocket server that:
|
||||
* Accepts connections from overlay clients.
|
||||
* 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 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. **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.
|
||||
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.
|
||||
3. **Advanced Overlay Customization:** Implement options for users to customize their overlay.
|
||||
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)
|
||||
|
||||
|
||||
23
README.md
23
README.md
@@ -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.
|
||||
* **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)
|
||||
* **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.
|
||||
|
||||
* **Want to contribute?** See our `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.
|
||||
* **Want the full plan?** See the `DEVELOPMENT_PLAN.md` for the complete project roadmap.
|
||||
* **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
|
||||
21
TASKS.md
21
TASKS.md
@@ -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.
|
||||
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
|
||||
@@ -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.
|
||||
|
||||
### 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
|
||||
* `[ ]` **1.0: Project Skeleton** - @ProjectLead
|
||||
* *Task:* Setup `main.py`, `requirements.txt`, and `.gitignore`.
|
||||
|
||||
### 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
|
||||
@@ -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)*
|
||||
|
||||
### 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}`).
|
||||
|
||||
110
auth.py
Normal file
110
auth.py
Normal 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
18
config.py
Normal 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
27
dashboard.html
Normal 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
19
database.py
Normal 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
64
main.py
@@ -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("/")
|
||||
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
27
models.py
Normal 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")
|
||||
@@ -2,3 +2,7 @@ fastapi
|
||||
uvicorn[standard]
|
||||
sqlalchemy
|
||||
httpx
|
||||
cryptography
|
||||
python-dotenv
|
||||
itsdangerous
|
||||
jinja2
|
||||
47
security.py
Normal file
47
security.py
Normal 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
1
static/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file ensures the 'static' directory is tracked by Git.
|
||||
16
static/dashboard.html
Normal file
16
static/dashboard.html
Normal 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
21
static/login.html
Normal 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
77
static/style.css
Normal 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 */
|
||||
}
|
||||
Reference in New Issue
Block a user