""" Command line interface for Verisure MyPages """ import inspect import json import re import click import logging from verisure import VariableTypes, Session, ResponseError, LoginError class DeviceLabel(click.ParamType): """Click param for device label""" name = "DeviceLabel" def convert(self, value, param, ctx): if re.match(r"^([A-Z]|[0-9]){4} ([A-Z]|[0-9]){4}$", value): return value self.fail(f"{value!r} is not a device label", param, ctx) class ArmFutureState(click.ParamType): """Click param for arm future state""" name = "FutureState" class LockFutureState(click.ParamType): """Click param for lock future state""" name = "FutureState" class TransactionId(click.ParamType): """Click param for transaction id""" name = "TransactionId" class RequestId(click.ParamType): """Click param for request id""" name = "RequestId" class Code(click.ParamType): """Click param for code""" name = "Code" def convert(self, value, param, ctx): if re.match(r"^[0-9]{4,6}$", value): return value self.fail(f"{value!r} is not a code", param, ctx) VariableTypeMap = { VariableTypes.DeviceLabel: DeviceLabel(), VariableTypes.ArmFutureState: ArmFutureState(), VariableTypes.LockFutureState: LockFutureState(), bool: click.BOOL, VariableTypes.TransactionId: TransactionId(), VariableTypes.RequestId: RequestId(), VariableTypes.Code: Code(), } def options_from_operator_list(): """Get all query operations and build query cli""" def decorator(f): ops = inspect.getmembers(Session, predicate=inspect.isfunction) for name, operation in reversed(ops): if not hasattr(operation, 'is_query'): continue variables = list(operation.__annotations__.values()) # Remove Giid type from variables, not supported by CLI if VariableTypes.Giid in variables: variables.remove(VariableTypes.Giid) dashed_name = name.replace('_', '-') if len(variables) == 0: click.option( '--'+dashed_name, is_flag=True, help=operation.__doc__)(f) elif len(variables) == 1: click.option( '--'+dashed_name, type=VariableTypeMap[variables[0]], help=operation.__doc__)(f) else: types = [VariableTypeMap[variable] for variable in variables] click.option( '--'+dashed_name, type=click.Tuple(types), help=operation.__doc__)(f) return f return decorator def make_query(session, name, arguments): """make query operation""" if arguments is True: return getattr(session, name)() if isinstance(arguments, str): return getattr(session, name)(arguments) return getattr(session, name)(*arguments) @click.command() @click.argument('username') @click.argument('password') @click.option('-i', '--installation', 'installation', help='Installation number', type=int, default=0) # noqa: E501 @click.option('-c', '--cookie', 'cookie', help='File to store cookie in', default='~/.verisure-cookie') # noqa: E501 @click.option('--mfa', 'mfa', help='Login using MFA', default=False, is_flag=True) # noqa: E501 @click.option('--log-level', type=click.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], case_sensitive=False)) # noqa: E501 @options_from_operator_list() def cli(username, password, installation, cookie, mfa, log_level, **kwargs): """Read and change status of verisure devices through verisure app API""" if log_level: logging.basicConfig(level=logging.getLevelName(log_level)) session = Session(username, password, cookie) try: # try using the cookie first installations = session.login_cookie() except LoginError: installations = None try: if mfa and not installations: session.request_mfa() code = input("Enter verification code: ") session.validate_mfa(code) installations = session.login_cookie() elif not installations: installations = session.login() session.set_giid( installations['data']['account'] ['installations'][installation]['giid']) queries = [ make_query(session, name, arguments) for name, arguments in kwargs.items() if arguments] result = session.request(*queries) click.echo(json.dumps(result, indent=4, separators=(',', ': '))) except ResponseError as ex: click.echo(ex,err=True) if __name__ == "__main__": # pylint: disable=no-value-for-parameter cli()