Refactor code structure for improved readability and maintainability
This commit is contained in:
parent
efb320eb05
commit
e078cada3b
11 changed files with 1640 additions and 1184 deletions
140
modules/stream_monitor.py
Normal file
140
modules/stream_monitor.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
"""
|
||||
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 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue