Addidng chat_listener function for Twitch, adding custom overlay template for testing.
This commit is contained in:
5
TASKS.md
5
TASKS.md
@@ -54,8 +54,11 @@ If you want to use emojis for visibility, here's some I have used:
|
||||
* **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)*
|
||||
|
||||
### In Progress
|
||||
* `[🧑🔧]` **3.1: Dynamic Listener Manager (The Big One):** Design a system (e.g., background service) to start/stop listener processes for users. @ramforth
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
23
chat_listener.py
Normal file
23
chat_listener.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from twitchio.ext import commands
|
||||
|
||||
class TwitchBot(commands.Bot):
|
||||
def __init__(self, access_token: str, channel_name: str):
|
||||
super().__init__(
|
||||
token=access_token,
|
||||
prefix='!', # A prefix is required but won't be used for reading chat
|
||||
initial_channels=[channel_name]
|
||||
)
|
||||
self.channel_name = channel_name
|
||||
|
||||
async def event_ready(self):
|
||||
"""Called once when the bot goes online."""
|
||||
print(f"Listener ready for #{self.channel_name}")
|
||||
|
||||
async def event_message(self, message):
|
||||
"""Runs every time a message is sent in chat."""
|
||||
if message.echo:
|
||||
return # Ignore messages sent by the bot itself
|
||||
|
||||
# For now, we just print the message to the console.
|
||||
# Later, this will push messages to a queue for the WebSocket.
|
||||
print(f"Message from {message.author.name} in #{self.channel_name}: {message.content}")
|
||||
42
listener_manager.py
Normal file
42
listener_manager.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import asyncio
|
||||
from typing import Dict
|
||||
|
||||
from chat_listener import TwitchBot
|
||||
import security # To decrypt tokens
|
||||
|
||||
class ListenerManager:
|
||||
def __init__(self):
|
||||
# This dictionary will hold our running listener tasks.
|
||||
# The key will be the user_id and the value will be the asyncio.Task.
|
||||
self.active_listeners: Dict[int, asyncio.Task] = {}
|
||||
print("ListenerManager initialized.")
|
||||
|
||||
async def start_listener_for_user(self, user):
|
||||
"""
|
||||
Starts a chat listener for a given user if one isn't already running.
|
||||
"""
|
||||
if user.id in self.active_listeners:
|
||||
print(f"Listener for user {user.id} is already running.")
|
||||
return
|
||||
|
||||
print(f"Starting listener for user {user.id} ({user.username})...")
|
||||
|
||||
tokens = security.decrypt_tokens(user.encrypted_tokens)
|
||||
access_token = tokens['access_token']
|
||||
bot = TwitchBot(access_token=access_token, channel_name=user.username)
|
||||
task = asyncio.create_task(bot.start())
|
||||
self.active_listeners[user.id] = task
|
||||
|
||||
async def stop_listener_for_user(self, user_id: int):
|
||||
"""Stops a chat listener for a given user."""
|
||||
if user_id not in self.active_listeners:
|
||||
print(f"No active listener found for user {user_id}.")
|
||||
return
|
||||
|
||||
print(f"Stopping listener for user {user_id}...")
|
||||
task = self.active_listeners.pop(user_id)
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
print(f"Listener for user {user_id} successfully stopped.")
|
||||
20
main.py
20
main.py
@@ -8,11 +8,12 @@ from contextlib import asynccontextmanager
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import models
|
||||
from database import engine
|
||||
from database import engine, SessionLocal
|
||||
import auth # Import the new auth module
|
||||
import schemas
|
||||
from starlette.responses import Response
|
||||
from config import settings # Import settings to get the secret key
|
||||
from listener_manager import ListenerManager
|
||||
|
||||
# --- Absolute Path Configuration ---
|
||||
# Get the absolute path of the directory where this file is located
|
||||
@@ -24,10 +25,25 @@ TEMPLATES_DIR = os.path.join(BASE_DIR, "templates")
|
||||
async def lifespan(app: FastAPI):
|
||||
# This code runs on startup
|
||||
print("Application startup: Creating database tables...")
|
||||
app.state.listener_manager = ListenerManager()
|
||||
models.Base.metadata.create_all(bind=engine)
|
||||
print("Application startup: Database tables created.")
|
||||
|
||||
# Start listeners for all existing users
|
||||
db = SessionLocal()
|
||||
users = db.query(models.User).all()
|
||||
db.close()
|
||||
for user in users:
|
||||
await app.state.listener_manager.start_listener_for_user(user)
|
||||
|
||||
yield
|
||||
# Code below yield runs on shutdown, if needed
|
||||
|
||||
# This code runs on shutdown
|
||||
print("Application shutdown: Stopping all listeners...")
|
||||
manager = app.state.listener_manager
|
||||
# Create a copy of keys to avoid runtime errors from changing dict size
|
||||
for user_id in list(manager.active_listeners.keys()):
|
||||
await manager.stop_listener_for_user(user_id)
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@ itsdangerous
|
||||
jinja2
|
||||
pydantic
|
||||
python-jose[cryptography]
|
||||
twitchio
|
||||
30
templates/overlay_test.html
Normal file
30
templates/overlay_test.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Live Test Overlay</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Live Test Overlay</h1>
|
||||
<div id="chat-messages">
|
||||
<!-- Messages will appear here -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// JavaScript to connect to WebSocket and display messages will go here
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user