Initial working version

This commit is contained in:
Joey Yakimowich-Payne 2025-05-14 17:45:07 -06:00
commit ec1c5958ce
33 changed files with 4547 additions and 0 deletions

View file

@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""
Helper script to install authentication dependencies
"""
import sys
import subprocess
import platform
def main():
"""Install the dependencies required for Twitch authentication"""
print("Installing dependencies for Twitch authentication...")
# Required packages
packages = [
"requests",
"python-dotenv",
"cryptography"
]
# Use pip to install the packages
try:
subprocess.check_call([sys.executable, "-m", "pip", "install"] + packages)
print("\nSuccessfully installed authentication dependencies!")
print("You can now run: python scripts/setup_twitch_auth.py")
except subprocess.CalledProcessError:
print("\nError: Failed to install dependencies.")
print("Please try installing them manually:")
print(" pip install requests python-dotenv cryptography")
sys.exit(1)
# Show additional information for Windows users
if platform.system() == "Windows":
print("\nNote for Windows users:")
print("This script uses the 'cryptography' library for generating certificates,")
print("which has C dependencies that should be automatically installed.")
print("If you encounter any issues, please refer to the documentation:")
print("https://cryptography.io/en/latest/installation/")
if __name__ == "__main__":
main()

21
scripts/run_consumer.py Normal file
View file

@ -0,0 +1,21 @@
"""
Script to run the queue consumer
"""
import sys
import logging
from src.queue.server import start_consumer
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger('consumer')
if __name__ == "__main__":
logger.info("Starting queue consumer...")
try:
start_consumer()
except KeyboardInterrupt:
logger.info("Consumer stopped by user")
sys.exit(0)
except Exception as e:
logger.error(f"Error in consumer: {e}")
sys.exit(1)

113
scripts/setup_env.py Normal file
View file

@ -0,0 +1,113 @@
"""
Environment setup helper script
"""
import os
import sys
import shutil
from typing import List, Dict, Any, Optional
import subprocess
import platform
def check_python_version() -> bool:
"""Check if Python version is compatible"""
if sys.version_info < (3, 7):
print(f"Error: Python 3.7 or higher is required. You are using {platform.python_version()}")
return False
print(f"Python version OK: {platform.python_version()}")
return True
def create_env_file() -> bool:
"""Create a .env file from .env.example if it doesn't exist"""
example_path = os.path.join("config", ".env.example")
env_path = ".env"
# Check if .env already exists
if os.path.exists(env_path):
print(f"Info: {env_path} file already exists")
return True
# Check if .env.example exists
if not os.path.exists(example_path):
print(f"Creating default .env file (no example found at {example_path})")
with open(env_path, "w") as f:
f.write("# Twitch API credentials\n")
f.write("TWITCH_USERNAME=your_bot_username\n")
f.write("TWITCH_CLIENT_ID=your_client_id\n")
f.write("TWITCH_CLIENT_SECRET=your_client_secret\n")
f.write("TWITCH_CHANNEL=your_channel\n")
print(f"Created default {env_path} file")
return True
# Copy .env.example to .env
try:
shutil.copy(example_path, env_path)
print(f"Created {env_path} file from {example_path}")
print(f"Please edit {env_path} to set your Twitch API credentials")
return True
except Exception as e:
print(f"Error creating .env file: {e}")
return False
def install_dependencies() -> bool:
"""Install Python dependencies from requirements.txt"""
req_path = "requirements.txt"
if not os.path.exists(req_path):
print(f"Error: {req_path} not found")
return False
try:
print(f"Installing dependencies from {req_path}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", req_path])
print("Dependencies installed successfully")
return True
except subprocess.CalledProcessError as e:
print(f"Error installing dependencies: {e}")
return False
def create_directories() -> bool:
"""Create necessary directories if they don't exist"""
dirs = [
"data/cache",
"data/sounds"
]
for directory in dirs:
if not os.path.exists(directory):
try:
os.makedirs(directory, exist_ok=True)
print(f"Created directory: {directory}")
except Exception as e:
print(f"Error creating directory {directory}: {e}")
return False
return True
def setup_environment() -> bool:
"""
Run all setup steps
Returns True if all steps succeeded
"""
steps = [
check_python_version,
create_directories,
create_env_file,
install_dependencies
]
success = True
for step in steps:
if not step():
success = False
return success
if __name__ == "__main__":
print("Setting up environment for stream-interact...")
if setup_environment():
print("\nSetup completed successfully!")
print("\nTo run the bot, use: python main.py")
print("To see available options, use: python main.py --help")
else:
print("\nSetup completed with errors. Please check the messages above.")
sys.exit(1)

View file

@ -0,0 +1,253 @@
#!/usr/bin/env python3
"""
Setup script for Twitch authentication
This script helps users generate a Twitch user access token with proper IRC scopes.
"""
import os
import sys
import logging
import json
import argparse
import time
from dotenv import load_dotenv
# Load environment variables if available
load_dotenv()
# Add parent directory to the path so we can import our modules
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
# Import the auth module
from src.core.auth import TwitchAuth
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger('twitch-auth-setup')
# Path to default config
DEFAULT_CONFIG_PATH = os.path.join("data", "default_config.json")
def load_default_config():
"""Load default configuration from JSON file"""
if os.path.exists(DEFAULT_CONFIG_PATH):
try:
with open(DEFAULT_CONFIG_PATH, 'r') as f:
return json.load(f)
except json.JSONDecodeError:
logger.warning(f"Invalid JSON in {DEFAULT_CONFIG_PATH}, ignoring")
return {}
def save_default_config(config_data):
"""Save configuration to default config JSON file"""
# Ensure directory exists
os.makedirs(os.path.dirname(DEFAULT_CONFIG_PATH), exist_ok=True)
# If file exists, load current data and update it
existing_data = {}
if os.path.exists(DEFAULT_CONFIG_PATH):
try:
with open(DEFAULT_CONFIG_PATH, 'r') as f:
existing_data = json.load(f)
except json.JSONDecodeError:
logger.warning(f"Invalid JSON in {DEFAULT_CONFIG_PATH}, overwriting")
# Update with new data
existing_data.update(config_data)
# Save back to file
with open(DEFAULT_CONFIG_PATH, 'w') as f:
json.dump(existing_data, f, indent=2)
logger.info(f"Configuration saved to {DEFAULT_CONFIG_PATH}")
def setup_twitch_auth(client_id=None, client_secret=None, scopes=None, manual_mode=False, force_new_token=False, use_cached_token=False):
"""
Setup Twitch authentication by generating a user access token
Args:
client_id: Twitch client ID
client_secret: Twitch client secret
scopes: Space-separated list of scopes
manual_mode: Whether to use manual mode (no browser)
force_new_token: Force generation of a new token even if a cached one exists
use_cached_token: Whether to use a cached token if available (default: False)
Returns:
bool: True if successful, False otherwise
"""
try:
# Load default config
default_config = load_default_config()
# Priority of values:
# 1. Function arguments
# 2. Environment variables
# 3. Default config
# Use provided values, then env vars, then default config
client_id = client_id or os.environ.get('TWITCH_CLIENT_ID') or default_config.get('TWITCH_CLIENT_ID')
client_secret = client_secret or os.environ.get('TWITCH_CLIENT_SECRET') or default_config.get('TWITCH_CLIENT_SECRET')
if not client_id:
client_id = input("Enter your Twitch Client ID: ")
if not client_secret:
client_secret = input("Enter your Twitch Client Secret: ")
if not client_id or not client_secret:
logger.error("Client ID and Client Secret are required")
return False
# Default scopes for IRC chat - prioritize env var, then default config
default_scopes = "chat:read chat:edit"
scopes = scopes or os.environ.get('TWITCH_SCOPES') or default_config.get('TWITCH_SCOPES', default_scopes)
# Define redirect URI
redirect_uri = os.environ.get('TWITCH_REDIRECT_URI') or default_config.get('TWITCH_REDIRECT_URI', "https://localhost:3000")
print("\n===== Twitch Authentication Setup =====")
print(f"Client ID: {client_id}")
print(f"Redirect URI: {redirect_uri} (must match your Twitch app settings)")
print(f"Requested Scopes: {scopes}")
print("=====================================\n")
# Make sure the cache directory exists
cache_dir = os.path.join("data", "cache")
os.makedirs(cache_dir, exist_ok=True)
# Create the token cache file path
token_cache_file = os.path.join(cache_dir, "token_cache.json")
# By default, clear any cached tokens unless explicitly told to use them
if (not use_cached_token or force_new_token) and os.path.exists(token_cache_file):
logger.info("Clearing cached token to generate fresh credentials")
os.remove(token_cache_file)
# Create auth handler
auth = TwitchAuth(
client_id=client_id,
client_secret=client_secret,
token_cache_file=token_cache_file,
redirect_uri=redirect_uri,
scopes=scopes
)
# Check if a valid token is already available
has_valid_token = False
if use_cached_token and auth.access_token and auth.token_expiry and time.time() < (auth.token_expiry - 60):
has_valid_token = True
# Using cached token
if not force_new_token:
print("\nUsing cached token (valid and not expired).")
print("If you want to force a new token, run with --force-new-token")
try:
# Try to validate the token to make sure it's working
token_info = auth.validate_token()
print(f"Token belongs to user: {token_info.get('login', 'unknown')}")
expiry_hours = (auth.token_expiry - time.time()) / 3600
print(f"Token expires in {expiry_hours:.1f} hours")
# Skip browser flow and continue with cached token
oauth_token = f"oauth:{auth.access_token}"
except Exception as e:
logger.warning(f"Error validating cached token: {e}")
logger.warning("Will attempt to get a new token")
has_valid_token = False
# If no valid token, go through the auth flow
if not has_valid_token or force_new_token:
# Show security warning about certificates
if not manual_mode:
print("\n⚠️ IMPORTANT SECURITY NOTE ⚠️")
print("This application uses a self-signed certificate for HTTPS, which is required by Twitch.")
print("When your browser opens, you will see a security warning.")
print("This is expected for local development. Please proceed to the site anyway:")
print(" • In Chrome: Click 'Advanced' and then 'Proceed to localhost (unsafe)'")
print(" • In Firefox: Click 'Advanced', then 'Accept the Risk and Continue'")
print(" • In Edge: Click 'Details' and then 'Go on to the webpage'\n")
print("NOTE: If the browser doesn't open automatically, a URL will be provided for you to copy and paste.")
input("Press Enter to continue...")
# Start OAuth flow
logger.info("Starting OAuth authentication flow...")
oauth_token = auth.get_oauth_token(manual_auth=manual_mode)
# At this point, we should have a valid token
if auth.access_token:
# Get token info
try:
token_info = auth.validate_token()
print("\n===== Authentication Successful =====")
print(f"Access Token: {auth.access_token}")
if auth.refresh_token:
print(f"Refresh Token: {auth.refresh_token}")
print(f"Token Scopes: {token_info.get('scopes', [])}")
print(f"User Name: {token_info.get('login', 'unknown')}")
print("=====================================\n")
except Exception as e:
logger.error(f"Failed to validate token: {e}")
print("\n===== Authentication Status =====")
print(f"Access Token: {auth.access_token}")
if auth.refresh_token:
print(f"Refresh Token: {auth.refresh_token}")
print("Warning: Could not validate token")
print("============================\n")
# Store token info in default_config.json
if input("Would you like to save these credentials to default_config.json? (y/n): ").lower() == 'y':
config_data = {
"TWITCH_CLIENT_ID": client_id,
"TWITCH_CLIENT_SECRET": client_secret,
}
# Try to use the validated user info
if 'token_info' in locals():
username = token_info.get('login', '')
config_data["TWITCH_USERNAME"] = username
# Get channel name
default_channel = username if 'username' in locals() else os.environ.get('TWITCH_CHANNEL', '') or default_config.get('TWITCH_CHANNEL', '')
channel = input(f"Enter Twitch channel to join (default: {default_channel}): ") or default_channel
config_data["TWITCH_CHANNEL"] = channel
# Save access and refresh tokens
config_data["TWITCH_ACCESS_TOKEN"] = auth.access_token
if auth.refresh_token:
config_data["TWITCH_REFRESH_TOKEN"] = auth.refresh_token
# Save to default_config.json
save_default_config(config_data)
return True
else:
logger.error("Failed to get OAuth token")
return False
except Exception as e:
logger.error(f"Error during setup: {e}")
return False
def main():
"""Main function"""
parser = argparse.ArgumentParser(description="Setup Twitch authentication")
parser.add_argument("--client-id", help="Twitch Client ID")
parser.add_argument("--client-secret", help="Twitch Client Secret")
parser.add_argument("--scopes", help="Space-separated list of scopes")
parser.add_argument("--manual", action="store_true", help="Use manual mode (no browser)")
parser.add_argument("--force-new-token", action="store_true", help="Force generation of a new token even if a cached one exists")
parser.add_argument("--use-cached-token", action="store_true", help="Use cached token if available (default is to clear cached tokens)")
args = parser.parse_args()
if setup_twitch_auth(args.client_id, args.client_secret, args.scopes, args.manual, args.force_new_token, args.use_cached_token):
print("Setup completed successfully!")
sys.exit(0)
else:
print("Setup failed!")
sys.exit(1)
if __name__ == "__main__":
main()