""" Stream monitoring and API interaction for Twitch Archive. """ import os import sys from typing import Dict, Optional, Any import requests from colorama import Fore, Style from .constants import TWITCH_OAUTH_URL, TWITCH_API_URL, TWITCH_GQL_URL, TWITCH_GQL_CLIENT_ID class StreamMonitor: """Handles Twitch API interactions for monitoring stream status.""" def __init__(self, username: str): """ Initialize the stream monitor. Args: username: Twitch username to monitor """ self.username = username self._oauth_token = None def get_oauth_token(self) -> str: """ Get OAuth token from Twitch API. Uses CLIENT-ID and CLIENT-SECRET from environment variables. Returns: str: OAuth access token Raises: SystemExit: If authentication fails """ if self._oauth_token: return self._oauth_token try: url = f"{TWITCH_OAUTH_URL}?client_id={os.getenv('CLIENT-ID')}&client_secret={os.getenv('CLIENT-SECRET')}&grant_type=client_credentials" response = requests.post(url, timeout=15) response.raise_for_status() self._oauth_token = response.json()['access_token'] return self._oauth_token except requests.exceptions.RequestException as e: print(f'{Fore.RED}✗ ERROR: Failed to authenticate with Twitch API{Style.RESET_ALL}') print(f'{Fore.YELLOW} {str(e)}{Style.RESET_ALL}') print(f'{Fore.CYAN} → Check your CLIENT-ID and CLIENT-SECRET in the .env file{Style.RESET_ALL}') sys.exit(1) except KeyError: print(f'{Fore.RED}✗ ERROR: Invalid response from Twitch API{Style.RESET_ALL}') print(f'{Fore.CYAN} → Verify your CLIENT-ID and CLIENT-SECRET are correct{Style.RESET_ALL}') sys.exit(1) def validate_username(self) -> bool: """ Validate that the configured Twitch username exists. Returns: bool: True if username exists, False otherwise Raises: SystemExit: If username is invalid or doesn't exist """ try: url = f'{TWITCH_API_URL}/users?login={self.username}' headers = { "Authorization": f"Bearer {self.get_oauth_token()}", "Client-ID": os.getenv('CLIENT-ID') } response = requests.get(url, headers=headers, timeout=15) response.raise_for_status() data = response.json() if not data.get('data'): print(f'{Fore.RED}✗ ERROR: Twitch user "{self.username}" not found{Style.RESET_ALL}') print(f'{Fore.CYAN} → Check the username in your config file{Style.RESET_ALL}') sys.exit(1) print(f'{Fore.GREEN}✓ Username "{self.username}" validated{Style.RESET_ALL}') return True except requests.exceptions.RequestException as e: print(f'{Fore.RED}✗ ERROR: Could not validate username{Style.RESET_ALL}') print(f'{Fore.YELLOW} {str(e)}{Style.RESET_ALL}') sys.exit(1) def check_stream_status(self) -> Optional[Dict[str, Any]]: """ Check if the configured user is currently live. Returns: dict: Stream information if live, None if offline Raises: SystemExit: If API request fails """ query = f'query{{user(login: "{self.username}") {{stream{{archiveVideo{{id}}title createdAt}}}}}}' try: response = requests.post( TWITCH_GQL_URL, json={'query': query}, headers={"Client-ID": TWITCH_GQL_CLIENT_ID}, timeout=15 ) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f'{Fore.RED}✗ ERROR: Failed to check stream status{Style.RESET_ALL}') print(f'{Fore.YELLOW} {str(e)}{Style.RESET_ALL}') sys.exit(1) def is_user_live(self) -> bool: """ Check if the configured user is currently live. Returns: bool: True if user is live, False if offline Raises: Exception: If API request fails (caller should handle) """ query = f'query{{user(login: "{self.username}") {{stream{{id title}}}}}}' try: response = requests.post( TWITCH_GQL_URL, json={'query': query}, headers={"Client-ID": TWITCH_GQL_CLIENT_ID}, timeout=15 ) response.raise_for_status() data = response.json() stream_data = data.get('data', {}).get('user', {}).get('stream') return stream_data is not None except requests.exceptions.RequestException as e: # Don't exit, let caller handle this raise Exception(f"Failed to check if user is live: {str(e)}") def get_latest_vod(self) -> Optional[Dict[str, Any]]: """ Get the most recent VOD for the configured user. Returns: dict: VOD information, or None if no VODs found """ query = f'query {{user(login: "{self.username}") {{videos(first: 1) {{edges {{node {{id title description recordedAt lengthSeconds animatedPreviewURL previewThumbnailURL(height: 1280, width: 720) thumbnailURLs(height: 1280, width: 720)}}}}}}}}}}' try: response = requests.post( TWITCH_GQL_URL, json={'query': query}, headers={"Client-ID": TWITCH_GQL_CLIENT_ID}, timeout=15 ) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f'{Fore.YELLOW}⚠ Warning: Could not fetch latest VOD{Style.RESET_ALL}') print(f'{Fore.YELLOW} {str(e)}{Style.RESET_ALL}') return None