Files

117 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Roku Web Remote - proxy server on port 7777."""
import http.server
import urllib.request
import urllib.error
import urllib.parse
import os
import json
ROKU = "http://10.0.1.93:8060"
HTML_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "roku-remote.html")
class RokuProxyHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/" or self.path == "/roku-remote.html":
self.serve_html()
elif self.path.startswith("/roku/icon/"):
self.proxy_icon()
elif self.path.startswith("/roku/query/"):
self.proxy_get(self.path.replace("/roku", "", 1))
elif self.path.startswith("/roku/"):
self.send_json(400, {"error": "Use POST for commands"})
else:
self.send_json(404, {"error": "Not found"})
def do_POST(self):
if self.path.startswith("/roku/"):
target = self.path.replace("/roku", "", 1)
self.proxy_post(target)
else:
self.send_json(404, {"error": "Not found"})
def serve_html(self):
try:
with open(HTML_PATH, "rb") as f:
content = f.read()
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(content)
except FileNotFoundError:
self.send_json(500, {"error": "HTML file not found"})
def proxy_icon(self):
# /roku/icon/<encoded_full_url>
encoded = self.path[len("/roku/icon/"):]
url = urllib.parse.unquote(encoded)
try:
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=5) as resp:
body = resp.read()
self.send_response(resp.status)
ctype = resp.headers.get("Content-Type", "image/png")
self.send_header("Content-Type", ctype)
self.send_header("Cache-Control", "max-age=3600")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
except Exception as e:
self.send_json(502, {"error": str(e)})
def proxy_get(self, path):
url = f"{ROKU}{path}"
try:
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=5) as resp:
body = resp.read()
self.send_response(resp.status)
self.send_header("Content-Type", resp.headers.get("Content-Type", "application/xml"))
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
except urllib.error.HTTPError as e:
self.send_json(e.code, {"error": str(e.reason)})
except Exception as e:
self.send_json(502, {"error": str(e)})
def proxy_post(self, path):
url = f"{ROKU}{path}"
try:
req = urllib.request.Request(url, method="POST", data=b"")
with urllib.request.urlopen(req, timeout=5) as resp:
body = resp.read()
self.send_response(resp.status)
self.send_header("Content-Type", resp.headers.get("Content-Type", "text/plain"))
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
except urllib.error.HTTPError as e:
self.send_json(e.code, {"error": str(e.reason)})
except Exception as e:
self.send_json(502, {"error": str(e)})
def send_json(self, code, data):
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(json.dumps(data).encode())
def log_message(self, fmt, *args):
msg = fmt % args
if "200" not in msg: # only log non-200
print(f"[roku-proxy] {self.client_address[0]} - {msg}")
if __name__ == "__main__":
port = 7777
server = http.server.HTTPServer(("0.0.0.0", port), RokuProxyHandler)
print(f"[roku-proxy] Serving on 0.0.0.0:{port}")
print(f"[roku-proxy] Access via http://10.10.10.1:{port}/ or http://10.0.1.49:{port}/")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\n[roku-proxy] Shutting down")
server.server_close()