2026-02-15 09:38:58 +01:00
#!/usr/bin/env python3
"""
Start chat downloader standalone for testing without recording video .
Usage :
python run_chat_only . py - - username vinesauce [ - - output path ] [ - - max - messages N ] [ - - timeout S ] [ - - verbose ]
This script uses the project ' s `ConfigManager` and `FileManager` to create
appropriate directories and then starts the chat downloader in a background
thread . Press Ctrl + C to stop .
"""
import argparse
import time
from datetime import datetime
import os
from colorama import Fore , Style
from modules . config import ConfigManager
from modules . file_manager import FileManager
from modules . utils import get_ffmpeg_executable , get_twitch_downloader_executable , detect_operating_system
from modules . downloader import ContentDownloader
2026-02-18 18:11:53 +01:00
from modules . stream_monitor import StreamMonitor
2026-02-15 09:38:58 +01:00
def main ( ) :
parser = argparse . ArgumentParser ( description = ' Run chat downloader standalone for testing ' )
parser . add_argument ( ' --username ' , ' -u ' , required = True , help = ' Twitch username/channel name ' )
parser . add_argument ( ' --output ' , ' -o ' , help = ' Output JSON path (optional) ' )
parser . add_argument ( ' --max-messages ' , type = int , default = None , help = ' Max messages to capture ' )
parser . add_argument ( ' --timeout ' , type = float , default = None , help = ' Timeout in seconds ' )
parser . add_argument ( ' --verbose ' , action = ' store_true ' , help = ' Show verbose/chat previews ' )
parser . add_argument ( ' --foreground ' , action = ' store_true ' , help = ' Run downloader in foreground (blocking) ' )
parser . add_argument ( ' --use-chat-downloader-primary ' , action = ' store_true ' , help = ' Use chat_downloader as primary method ' )
parser . add_argument ( ' --use-chat-downloader-fallback ' , dest = ' use_chat_downloader_fallback ' , action = ' store_true ' , help = ' Allow chat_downloader as fallback (default) ' )
parser . add_argument ( ' --no-chat-downloader-fallback ' , dest = ' use_chat_downloader_fallback ' , action = ' store_false ' , help = ' Disable chat_downloader fallback ' )
args = parser . parse_args ( )
cfg = ConfigManager ( )
config = cfg . load_streamer_config ( args . username )
# Apply overrides from CLI
if args . use_chat_downloader_primary :
config [ ' useChatDownloaderPrimary ' ] = True
if args . use_chat_downloader_fallback is not None :
config [ ' useChatDownloaderFallback ' ] = bool ( args . use_chat_downloader_fallback )
# Ensure directories exist (use configured archive root path)
fm = FileManager ( root_path = config . get ( ' root_path ' , ' archive ' ) , username = args . username , config = config )
fm . initialize_directories ( )
# Build default output path if not provided
if args . output :
json_path = args . output
else :
ts = datetime . now ( ) . strftime ( ' % Y % m %d _ % Hh % Mm % Ss ' )
json_path = str ( fm . chat_json_path / f " CHAT_TEST_ { args . username } _ { ts } .json " )
# Initialize downloader
os_type = detect_operating_system ( )
twitch_downloader_path = get_twitch_downloader_executable ( os_type )
ffmpeg_path = get_ffmpeg_executable ( os_type )
downloader = ContentDownloader ( twitch_downloader_path = twitch_downloader_path ,
ffmpeg_path = ffmpeg_path ,
config = config )
print ( f " { Fore . CYAN } Starting standalone chat downloader for { args . username } { Style . RESET_ALL } " )
print ( f " Output path: { json_path } " )
stop_requested = { ' stop ' : False }
def shutdown_check ( ) :
return stop_requested [ ' stop ' ]
2026-02-18 18:11:53 +01:00
# Prepare stream monitor
stream_monitor = StreamMonitor ( args . username )
# If chat downloads are disabled in config, enter monitoring mode instead
if not downloader . download_live_chat :
print ( f " { Fore . YELLOW } ⚠ downloadLiveCHAT is disabled in config - entering monitoring mode for { args . username } { Style . RESET_ALL } " )
try :
while True :
try :
is_live = stream_monitor . is_user_live ( )
if is_live :
print ( f " { Fore . GREEN } ✓ { args . username } is live! Exiting monitor. Run the archiver to record video. { Style . RESET_ALL } " )
break
else :
print ( f " { Fore . CYAN } { args . username } is offline - checking again in 30s... { Style . RESET_ALL } " )
except Exception as e :
print ( f " { Fore . YELLOW } ⚠ Could not check stream status: { e } { Style . RESET_ALL } " )
time . sleep ( 30 )
except KeyboardInterrupt :
print ( ' \n Keyboard interrupt received; stopping monitor... ' )
return
# If chat download is enabled, but the stream is currently offline, wait until it goes live
try :
try :
if not stream_monitor . is_user_live ( ) :
print ( f " { Fore . CYAN } { args . username } is currently offline - waiting for live stream to start... { Style . RESET_ALL } " )
while not stream_monitor . is_user_live ( ) :
time . sleep ( 10 )
except Exception :
# If we cannot determine live status, proceed to start chat downloader anyway
pass
except KeyboardInterrupt :
print ( ' \n Keyboard interrupt received; exiting... ' )
return
# Start thread (stream_monitor passed so downloader can stop when stream ends)
2026-02-15 09:38:58 +01:00
thread = downloader . start_chat_downloader_thread (
args . username ,
json_path ,
shutdown_check = shutdown_check ,
2026-02-18 18:11:53 +01:00
stream_monitor = stream_monitor ,
2026-02-15 09:38:58 +01:00
verbose = args . verbose
)
try :
if args . foreground :
# Run download directly in foreground
print ( ' Running in foreground; this will block until download completes or interrupted ' )
success = downloader . download_live_chat_with_chat_downloader (
args . username ,
json_path ,
max_messages = args . max_messages ,
timeout = args . timeout ,
shutdown_check = shutdown_check ,
stream_monitor = None ,
verbose = args . verbose
)
print ( ' Done, success= ' + str ( success ) )
else :
# Wait for thread to finish or until interrupted
while thread . is_alive ( ) :
time . sleep ( 0.5 )
except KeyboardInterrupt :
print ( ' \n Keyboard interrupt received; stopping downloader... ' )
stop_requested [ ' stop ' ] = True
thread . join ( timeout = 5 )
if __name__ == ' __main__ ' :
main ( )