import argparse import base64 import json import socket from .run import MoonlightPairing from .state import add_sunshine_host, gen_pin, get_moonlight_certificate from .uri import parse_moonlight_uri def send_join_request(host: str, port: int, cert: str) -> bool: tries = 0 max_tries = 3 response = False for tries in range(max_tries): response = send_join_request_api(host, port) if response: return response if send_join_request_native(host, port, cert): return True return False # This is the preferred join method, but sunshines pin mechanism # seems to be somewhat brittle in repeated testing, retry then fallback to native def send_join_request_api(host: str, port: int) -> bool: moonlight = MoonlightPairing() # is_paired = moonlight.check(host) is_paired = False if is_paired: print(f"Moonlight is already paired with this host: {host}") return True pin = gen_pin() moonlight.init_pairing(host, pin) moonlight.wait_until_started() with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: s.connect((host, port)) json_body = {"type": "api", "pin": pin} json_body = json.dumps(json_body) request = ( f"POST / HTTP/1.1\r\n" f"Content-Type: application/json\r\n" f"Content-Length: {len(json_body)}\r\n" f"Connection: close\r\n\r\n" f"{json_body}" ) try: s.sendall(request.encode("utf-8")) response = s.recv(16384).decode("utf-8") print(response) body = response.split("\n")[-1] print(body) moonlight.terminate() return True except Exception as e: print(f"An error occurred: {e}") moonlight.terminate() return False def send_join_request_native(host: str, port: int, cert: str) -> bool: # This is the hardcoded UUID for the moonlight client uuid = "123456789ABCD" with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: s.connect((host, port)) encoded_cert = base64.urlsafe_b64encode(cert.encode("utf-8")).decode("utf-8") json_body = {"type": "native", "uuid": uuid, "cert": encoded_cert} json_body = json.dumps(json_body) request = ( f"POST / HTTP/1.1\r\n" f"Content-Type: application/json\r\n" f"Content-Length: {len(json_body)}\r\n" f"Connection: close\r\n\r\n" f"{json_body}" ) try: s.sendall(request.encode("utf-8")) response = s.recv(16384).decode("utf-8") print(response) lines = response.split("\n") body = "\n".join(lines[2:])[2:] print(body) return body except Exception as e: print(f"An error occurred: {e}") # TODO: fix try: print(f"response: {response}") data = json.loads(response) print(f"Data: {data}") print(f"Host uuid: {data['uuid']}") print(f"Host certificate: {data['cert']}") print("Joining sunshine host") cert = data["cert"] cert = base64.urlsafe_b64decode(cert).decode("utf-8") uuid = data["uuid"] hostname = data["hostname"] add_sunshine_host(hostname, host, cert, uuid) except json.JSONDecodeError as e: print(f"Failed to decode JSON: {e}") pos = e.pos print(f"Failed to decode JSON: unexpected character {response[pos]}") def join(args: argparse.Namespace) -> None: if args.url: (host, port) = parse_moonlight_uri(args.url) if port is None: port = 48011 else: port = args.port host = args.host print(f"Host: {host}, port: {port}") # TODO: If cert is not provided parse from config # cert = args.cert cert = get_moonlight_certificate() if send_join_request(host, port, cert): print(f"Successfully joined sunshine host: {host}") else: print(f"Failed to join sunshine host: {host}") def register_join_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument("url", nargs="?") parser.add_argument("--port", type=int, default=48011) parser.add_argument("--host") parser.add_argument("--cert") parser.set_defaults(func=join)