# Standard Imports import re import sys import html import time import json from datetime import datetime from zoneinfo import ZoneInfo # Package Imports import websocket # Local Imports from helpers.clips import generate_and_post_clip def owncast_on_message(ws, message): """ Handles incoming messages from websocket, filters for commands, and dispatches. Args: ws: The websocket object calling this handler. message: The message received by the websocket. Returns: None """ try: data = json.loads(message) if 'type' in data and data['type'] == 'CHAT': # TODO: add anti-spam and authentication checks current_time = datetime.now(ZoneInfo("America/Chicago")) chat_message = html.unescape( re.sub('<.*?>', '', data['body'])).strip() if chat_message == '!clip' or chat_message.startswith('!clip '): if len(chat_message) == 5: clip_title = f'Clip by {data["user"]["displayName"]} on {current_time.strftime("%Y-%m-%d %H:%M:%S")}' generate_and_post_clip( data['user']['displayName'], clip_title, current_time) elif len(chat_message) > 5 and len(chat_message) < 126: generate_and_post_clip( data['user']['displayName'], chat_message[6:], current_time) else: # TODO: report this error back to the chat return except json.JSONDecodeError as e: print(f'Failed to decode incoming owncast websocket message.', file=sys.stderr) def owncast_on_error(ws, error): print(f'[WS-ERR] {error}', file=sys.stderr) def owncast_on_close(ws, code, message): print(f'Owncast websocket closed (code: {code}): {message}') def owncast_on_open(ws): print('Owncast websocket opened successfully.') def owncast_connect(url: str, retries: int, initial_backoff: float): """ Attempts to connect to the Owncast websocket and auto-reconnect with backoff Args: url (str): The websocket service url to connect to. retries (int): The maximum number of reconnect attemps before failing. initial_backoff (float): The base amount of reconnection backoff time in seconds Returns: None """ attempt = 0 backoff = initial_backoff while attempt < retries: ws = websocket.WebSocketApp(url, on_open=owncast_on_open, on_message=owncast_on_message, on_error=owncast_on_error, on_close=owncast_on_close ) try: ws.run_forever() except KeyboardInterrupt: print('\nInterrupted by user. Exiting.') sys.exit(0) attempt += 1 if attempt < retries: print(f'Reconnecting in {backoff:.1f}s ({attempt} of {retries})') time.sleep(backoff) backoff *= 2 print(f'Max reconnect attempts reached. Exiting.')