diff --git a/README.txt b/README.txt index e6689a0..eb961de 100644 --- a/README.txt +++ b/README.txt @@ -60,6 +60,7 @@ usage: mxdiscover --help mxpassitem --help mxwhoami --help + mxlogout --help mxtoken mxaccountinfo mxclearaccount diff --git a/smal/pyproject.toml b/smal/pyproject.toml index 4f36bbb..bda16cb 100644 --- a/smal/pyproject.toml +++ b/smal/pyproject.toml @@ -36,6 +36,7 @@ mxdiscover = "pymxutils.mxutils:discoverhs" mxwhoami = "pymxutils.mxutils:whoami" mxtoken = "pymxutils.mxutils:mktoken" mxaccountinfo = "pymxutils.mxutils:accountinfo" +mxlogout = "pymxutils.mxutils:logout" mxclearaccount = "pymxutils.mxutils:clearaccount" mxserverinfo = "pymxutils.mxutils:serverinfo" mxpassitem = "pymxutils.mxutils:passitem" diff --git a/smal/src/pymxutils/mxutils/__init__.py b/smal/src/pymxutils/mxutils/__init__.py index 5d9412e..1fea3dc 100644 --- a/smal/src/pymxutils/mxutils/__init__.py +++ b/smal/src/pymxutils/mxutils/__init__.py @@ -5,3 +5,4 @@ from .accountinfo import accountinfo as accountinfo from .clearaccount import clearaccount as clearaccount from .serverinfo import serverinfo as serverinfo from .passitem import passitem as passitem +from .logout import logout as logout diff --git a/smal/src/pymxutils/mxutils/logout.py b/smal/src/pymxutils/mxutils/logout.py new file mode 100644 index 0000000..22a0896 --- /dev/null +++ b/smal/src/pymxutils/mxutils/logout.py @@ -0,0 +1,129 @@ +# Copyright (C) 2026 saces@c-base.org +# SPDX-License-Identifier: AGPL-3.0-only +import getpass +from datetime import datetime + +import click + +from pygomx import CliV0 + + +@click.command() +@click.option("-u", "--url", "hs_url", metavar="url", help="homeserver url") +@click.option("-t", "--token", "token", metavar="token", help="access token") +@click.option( + "--json", "show_json", is_flag=True, help="show json as returned from server." +) +@click.argument("devices", metavar="DeviceID", nargs=-1) +@click.option( + "-l", + "--logout", + "logout_type", + type=click.Choice(["all", "other", "self"]), + help="logout devices", +) +def logout(hs_url, token, devices, logout_type, show_json): + """List or logout devices. + + \b + mxlogout [--json] + list all devices + mxlogout --all + logout all devices + mxlogout --self + logout this device + mxlogout --other + logout all other devices (requires auth) + mxlogout deviceid [deviceid]... + logout given devices (requires auth) + """ + + if len(devices) > 0 and logout_type is not None: + raise ValueError("you can't get both.") + + if hs_url is None and token is None: + cli = CliV0.from_mxpass(".mxpass", "*", "*", "*") + else: + cli = CliV0(hs_url, token) + + match logout_type: + case "self": + do_logout(cli, all=False) + return + case "all": + do_logout(cli, all=True) + return + + if len(devices) > 0: + device_list = list(devices) + whoami_dict = cli.Whoami() + self_user_id = whoami_dict["user_id"] + do_logout_devices(cli, device_list, self_user_id) + return + + reqData = {"method": "GET", "path": ["_matrix", "client", "v3", "devices"]} + raw_device_dict = cli.Generic(reqData) + + if logout_type == "other": + whoami_dict = cli.Whoami() + self_device_id = whoami_dict["device_id"] + self_user_id = whoami_dict["user_id"] + device_list = [] + for device in raw_device_dict["devices"]: + if device["device_id"] != self_device_id: + device_list += [ + device["device_id"], + ] + if len(device_list) > 0: + do_logout_devices(cli, device_list, self_user_id) + return + + if show_json: + print(raw_device_dict) + return + + max_len = 0 + for device in raw_device_dict["devices"]: + max_len = max(max_len, len(device["device_id"])) + + for device in raw_device_dict["devices"]: + date_object = datetime.fromtimestamp(device["last_seen_ts"] / 1000) + print( + device["device_id"], + " " * (max_len - len(device["device_id"])), + date_object, + device["last_seen_ip"], + device["display_name"], + ) + date_object = datetime.fromtimestamp(device["last_seen_ts"] / 1000) + + +def do_logout(cli, all): + reqData = { + "method": "POST", + "path": ["_matrix", "client", "v3", "logout"], + } + if all: + reqData["path"] += ["all"] + res = cli.Generic(reqData) + print(res) + + +def do_logout_devices(cli, devices, user_id): + reqData = { + "method": "POST", + "path": ["_matrix", "client", "v3", "delete_devices"], + "payload": { + "devices": devices, + "auth": { + "type": "m.login.password", + "password": getpass.getpass(prompt="Password: "), + "identifier": { + "type": "m.id.user", + "user": user_id, + }, + }, + }, + } + res = cli.Generic(reqData) + print(res)