99 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			99 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# 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.')
 |