from roku import Roku import os import json import threading ROKU_IP = os.environ.get("ROKU_IP") if not ROKU_IP: print("Roku IP not found in env!") COMMANDS = { # Standard Keys "home": "Home", "reverse": "Rev", "forward": "Fwd", "play": "Play", "select": "Select", "left": "Left", "right": "Right", "down": "Down", "up": "Up", "back": "Back", "replay": "InstantReplay", "info": "Info", "backspace": "Backspace", "search": "Search", "enter": "Enter", "literal": "Lit", # For devices that support "Find Remote" "find_remote": "FindRemote", # For Roku TV "volume_down": "VolumeDown", "volume_up": "VolumeUp", "volume_mute": "VolumeMute", # For Roku TV while on TV tuner channel "channel_up": "ChannelUp", "channel_down": "ChannelDown", # For Roku TV current input "input_tuner": "InputTuner", "input_hdmi1": "InputHDMI1", "input_hdmi2": "InputHDMI2", "input_hdmi3": "InputHDMI3", "input_hdmi4": "InputHDMI4", "input_av1": "InputAV1", # For devices that support being turned on/off "power": "Power", "poweroff": "PowerOff", "poweron": "PowerOn", } class RokuWrapper(): def _send_msg(self, code: int, msg: object): try: content = json.dumps(msg).encode() self.parent._send(code, content) except Exception as e: print(e) def __init__(self, parent): self.parent = parent # eh maybe close the connection? Not really needed self.timer: threading.Timer | None = None try: self.rapp = Roku(ROKU_IP) print(f"Roku client connected to {ROKU_IP}", flush=True) except Exception as e: self.parent.log_error("Failed to create Roku client: %s", e) return self._send_msg(500, {"error": "roku connection failed"}) def run_command(self, command: str): if not ROKU_IP: return self._send_msg(500, {"error": "Roku IP not found in env! Do not attempt further Roku requests"}) if not command: return self._send_msg(404, {"error": "command not given"}) command = command.split('/')[0] # If command is not known, nothing to do if command not in COMMANDS: return self._send_msg(404, {"error": f"unknown command. Available commands: {", ".join(COMMANDS.keys())}"}) if hasattr(self.rapp, command): attr = getattr(self.rapp, command) if callable(attr): attr() return self._send_msg(200, {"result": f"{command} executed"}) # Fallback: send the Roku key name using common API methods key_name = COMMANDS[command] # common method names in Roku libs for send_fn in ("press", "keypress", "send_key", "send_keypress", "button"): if hasattr(self.rapp, send_fn): fn = getattr(self.rapp, send_fn) try: fn(key_name) return self._send_msg(200, {"result": f"{command} sent as {key_name} via {send_fn}"}) except TypeError: continue self._send_msg(404, {"error": "unknown endpoint"})