diff --git a/LGTV/cli.py b/LGTV/cli.py new file mode 100644 index 0000000..ee57f67 --- /dev/null +++ b/LGTV/cli.py @@ -0,0 +1,100 @@ +import json +import logging +import sys +from time import sleep +from typing import Optional + +import click + +from LGTV.conf import read_config, write_config +from LGTV.scan import LGTVScan +from LGTV.auth import LGTVAuth + + +@click.group +@click.option("-d", "--debug", is_flag=True, help="Enable debug output.") +@click.option("-n", "--name", help="Name of the TV to manage.") +@click.pass_context +def cli(ctx: click.Context, debug: bool = False, name: Optional[str] = None) -> None: + """Command line webOS remote for LG TVs.""" + logging.basicConfig(level=logging.DEBUG if debug else logging.INFO) + + config_path, full_config = read_config() + name = name or full_config.get("_default") + + possible_tvs = list(full_config.keys()) + try: + possible_tvs.remove("_default") + except ValueError: + pass + + if name and name not in possible_tvs: + click.secho( + f"No entry with the name '{name}' was found in the configuration at {config_path}. Names found: {', '.join(possible_tvs)}", + fg="red", + err=True, + ) + sys.exit(1) + + tv_config = full_config.get(name, {}) + ctx.obj = { + "config_path": config_path, + "full_config": full_config, + "tv_name": name, + "tv_config": tv_config, + } + + +@cli.command +def scan() -> None: + """Scan the local network for LG TVs.""" + results = LGTVScan() + + if len(results) > 0: + click.echo(json.dumps({"result": "ok", "count": len(results), "list": results})) + sys.exit(0) + else: + click.echo(json.dumps({"result": "failed", "count": len(results)})) + sys.exit(1) + + +@cli.command +@click.argument("host") +@click.argument("name") +@click.option("-s", "--ssl", is_flag=True, help="Connect to TV using SSL.") +@click.pass_context +def auth(ctx: click.Context, host: str, name: str, ssl: bool = False) -> None: + """Connect to a new TV.""" + if name.startswith("_"): + click.secho( + "TV names are not allowed to start with an underscore", fg="red", err=True + ) + sys.exit(1) + + ws = LGTVAuth(name, host, ssl=ssl) + ws.connect() + ws.run_forever() + sleep(1) + config = ctx.obj["full_config"] + config[name] = ws.serialise() + write_config(ctx.obj["config_path"], config) + click.echo(f"Wrote config file: {ctx.obj['config_path']}") + + +@cli.command +@click.argument("name") +@click.pass_context +def set_default(ctx: click.Context, name: str) -> None: + """Change the default TV to interact with.""" + config = ctx.obj["full_config"] + if name == "_default" or name not in config: + click.secho("TV not found in config", fg="red", err=True) + sys.exit(1) + + config["_default"] = name + write_config(ctx.obj["config_path"], config) + click.echo(f"Default TV set to '{name}'") + + +if __name__ == "__main__": + cli() diff --git a/LGTV/conf.py b/LGTV/conf.py new file mode 100644 index 0000000..b369565 --- /dev/null +++ b/LGTV/conf.py @@ -0,0 +1,41 @@ +import json +import sys +from pathlib import Path +from typing import Dict, Tuple + +# TODO: Should this be replaced with click.get_app_dir()? +search_paths = [ + "/etc/lgtv/config.json", + "~/.lgtv/config.json", + "/opt/venvs/lgtv/config/config.json", +] + + +def read_config() -> Tuple[Path, Dict]: + # Check for existing config files + for path in map(Path, search_paths): + path = path.expanduser() + if path.exists(): + with path.open() as fp: + return path, json.load(fp) + + # Attempt to find place to write new config file + for path in map(Path, search_paths): + path = path.expanduser() + + try: + path.parent.mkdir(parents=True, exist_ok=True) + return path, {} + except (FileExistsError, PermissionError): + pass + + print( + "Cannot find suitable config path to write, create one in", + " or ".join(search_paths), + ) + sys.exit(1) + + +def write_config(path: Path, config: Dict) -> None: + with path.open("w") as fp: + json.dump(config, fp) diff --git a/requirements.txt b/requirements.txt index 1fb4e5d..6ba9de9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ wakeonlan==1.1.6 ws4py==0.5.1 requests==2.31.0 getmac==0.9.2 +click==8.1.7 diff --git a/setup.py b/setup.py index 6a581f7..603e559 100755 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ 'ws4py', 'requests', 'getmac', + 'click>=8.1.0', ], data_files=[ ('config', ['data/config.json'])