Chat monitors stream to end

This commit is contained in:
MaddoScientisto 2026-02-11 13:23:14 +01:00
commit 38d51636af
4 changed files with 89 additions and 1 deletions

View file

@ -365,6 +365,7 @@ class ContentDownloader:
max_messages: Optional[int] = None, max_messages: Optional[int] = None,
timeout: Optional[float] = None, timeout: Optional[float] = None,
shutdown_check: Optional[callable] = None, shutdown_check: Optional[callable] = None,
stream_monitor = None,
verbose: bool = False) -> bool: verbose: bool = False) -> bool:
""" """
Download live chat using chat_downloader library as fallback. Download live chat using chat_downloader library as fallback.
@ -376,6 +377,7 @@ class ContentDownloader:
max_messages: Maximum messages to download (None = unlimited) max_messages: Maximum messages to download (None = unlimited)
timeout: Stop after this many seconds (None = until stream ends) timeout: Stop after this many seconds (None = until stream ends)
shutdown_check: Optional callback function that returns True when shutdown requested shutdown_check: Optional callback function that returns True when shutdown requested
stream_monitor: Optional stream monitor to check if stream is still live
verbose: Show chat message previews verbose: Show chat message previews
Returns: Returns:
@ -414,7 +416,10 @@ class ContentDownloader:
# The get_chat with output parameter writes to file automatically # The get_chat with output parameter writes to file automatically
# We just need to iterate to trigger the download # We just need to iterate to trigger the download
message_count = 0 message_count = 0
print(f'{Fore.CYAN}Receiving chat messages (press Ctrl+C to stop)...{Style.RESET_ALL}') last_check_time = time.time()
check_interval = 10.0 # Check if stream is still live every 10 seconds
print(f'{Fore.CYAN}Receiving chat messages (will stop when stream ends)...{Style.RESET_ALL}')
try: try:
for message in chat: for message in chat:
# Check for shutdown request # Check for shutdown request
@ -422,6 +427,19 @@ class ContentDownloader:
print(f'\n{Fore.YELLOW}⚠ Chat download stopped by shutdown request{Style.RESET_ALL}') print(f'\n{Fore.YELLOW}⚠ Chat download stopped by shutdown request{Style.RESET_ALL}')
break break
# Periodically check if stream is still live
current_time = time.time()
if stream_monitor and (current_time - last_check_time) >= check_interval:
last_check_time = current_time
try:
is_live = stream_monitor.is_user_live()
if not is_live:
print(f'\n{Fore.YELLOW}⚠ Stream ended, stopping chat download{Style.RESET_ALL}')
break
except Exception as check_error:
print(f'\n{Fore.YELLOW}⚠ Could not check stream status: {check_error}{Style.RESET_ALL}')
# Continue downloading to avoid false positives from API errors
message_count += 1 message_count += 1
# Show progress every 100 messages # Show progress every 100 messages
@ -467,6 +485,7 @@ class ContentDownloader:
def start_chat_downloader_thread(self, username: str, json_path: str, def start_chat_downloader_thread(self, username: str, json_path: str,
shutdown_check: Optional[callable] = None, shutdown_check: Optional[callable] = None,
stream_monitor = None,
verbose: bool = False) -> threading.Thread: verbose: bool = False) -> threading.Thread:
""" """
Start chat_downloader in a background thread. Start chat_downloader in a background thread.
@ -475,6 +494,7 @@ class ContentDownloader:
username: Twitch username username: Twitch username
json_path: Path to save chat JSON json_path: Path to save chat JSON
shutdown_check: Callback to check for shutdown shutdown_check: Callback to check for shutdown
stream_monitor: Optional stream monitor to check if stream is still live
verbose: Show chat previews verbose: Show chat previews
Returns: Returns:
@ -485,6 +505,7 @@ class ContentDownloader:
self.chat_thread_success = self.download_live_chat_with_chat_downloader( self.chat_thread_success = self.download_live_chat_with_chat_downloader(
username, json_path, username, json_path,
shutdown_check=shutdown_check, shutdown_check=shutdown_check,
stream_monitor=stream_monitor,
verbose=verbose verbose=verbose
) )
except Exception as e: except Exception as e:

View file

@ -115,6 +115,34 @@ class StreamMonitor:
print(f'{Fore.YELLOW} {str(e)}{Style.RESET_ALL}') print(f'{Fore.YELLOW} {str(e)}{Style.RESET_ALL}')
sys.exit(1) 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]]: def get_latest_vod(self) -> Optional[Dict[str, Any]]:
""" """
Get the most recent VOD for the configured user. Get the most recent VOD for the configured user.

37
run_tests.ps1 Normal file
View file

@ -0,0 +1,37 @@
# PowerShell script to run Twitch Archive unit tests
# Run this script to execute all unit tests
Write-Host "======================================================================" -ForegroundColor Cyan
Write-Host "TWITCH ARCHIVE - Running Unit Tests" -ForegroundColor Cyan
Write-Host "======================================================================" -ForegroundColor Cyan
Write-Host ""
# Check if virtual environment exists and activate it
$venvPath = ".\venv314\Scripts\Activate.ps1"
if (Test-Path $venvPath) {
Write-Host "✓ Activating virtual environment..." -ForegroundColor Green
& $venvPath
} else {
Write-Host "⚠ Virtual environment not found at $venvPath" -ForegroundColor Yellow
Write-Host " Continuing with system Python..." -ForegroundColor Yellow
}
Write-Host ""
# Run the tests
Write-Host "Running unit tests..." -ForegroundColor Cyan
python test_twitch_archive_simple.py
# Check exit code
if ($LASTEXITCODE -eq 0) {
Write-Host ""
Write-Host "======================================================================" -ForegroundColor Green
Write-Host "✓ ALL TESTS PASSED" -ForegroundColor Green
Write-Host "======================================================================" -ForegroundColor Green
} else {
Write-Host ""
Write-Host "======================================================================" -ForegroundColor Red
Write-Host "✗ SOME TESTS FAILED" -ForegroundColor Red
Write-Host "======================================================================" -ForegroundColor Red
exit $LASTEXITCODE
}

View file

@ -1088,6 +1088,7 @@ class TwitchArchiveManager:
archiver.downloader.start_chat_downloader_thread( archiver.downloader.start_chat_downloader_thread(
archiver.username, chat_json_path, archiver.username, chat_json_path,
shutdown_check=lambda: self.shutdown_requested or archiver.shutdown_requested, shutdown_check=lambda: self.shutdown_requested or archiver.shutdown_requested,
stream_monitor=archiver.stream_monitor,
verbose=self.verbose verbose=self.verbose
) )
except Exception as e: except Exception as e:
@ -1116,6 +1117,7 @@ class TwitchArchiveManager:
archiver.downloader.start_chat_downloader_thread( archiver.downloader.start_chat_downloader_thread(
archiver.username, chat_json_path, archiver.username, chat_json_path,
shutdown_check=lambda: self.shutdown_requested or archiver.shutdown_requested, shutdown_check=lambda: self.shutdown_requested or archiver.shutdown_requested,
stream_monitor=archiver.stream_monitor,
verbose=self.verbose or self.chat_only verbose=self.verbose or self.chat_only
) )
# Wait for completion # Wait for completion