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.')
|