Initial working version
This commit is contained in:
commit
ec1c5958ce
33 changed files with 4547 additions and 0 deletions
40
scripts/install_auth_deps.py
Normal file
40
scripts/install_auth_deps.py
Normal 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
21
scripts/run_consumer.py
Normal 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
113
scripts/setup_env.py
Normal 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)
|
||||
253
scripts/setup_twitch_auth.py
Normal file
253
scripts/setup_twitch_auth.py
Normal 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue