view libervia/cli/cmd_profile.py @ 4314:6a70fcd93a7a

plugin XEP-0131: Stanza Headers and Internet Metadata implementation: - SHIM is now supported and put in `msg_data["extra"]["headers"]`. - `Keywords` are converted from and to list of string in `msg_data["extra"]["keywords"]` field (if present in headers on message sending, values are merged). - Python minimal version upgraded to 3.11 due to use of `StrEnum`. rel 451
author Goffi <goffi@goffi.org>
date Sat, 28 Sep 2024 15:56:04 +0200
parents 0d7bb4df2343
children
line wrap: on
line source

#!/usr/bin/env python3


# Libervia CLI
# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""This module permits to manage profiles. It can list, create, delete
and retrieve information about a profile."""

from libervia.cli.constants import Const as C
from libervia.backend.core.log import getLogger
from libervia.backend.core.i18n import _
from libervia.cli import base

log = getLogger(__name__)


__commands__ = ["Profile"]

PROFILE_HELP = _("The name of the profile")


class ProfileConnect(base.CommandBase):
    """Dummy command to use profile_session parent, i.e. to be able to connect without doing anything else"""

    def __init__(self, host):
        # it's weird to have a command named "connect" with need_connect=False, but it can be handy to be able
        # to launch just the session, so some paradoxes don't hurt
        super(ProfileConnect, self).__init__(
            host, "connect", need_connect=False, help=("connect a profile")
        )

    def add_parser_options(self):
        pass

    async def start(self):
        # connection is already managed by profile common commands
        # so we just need to check arguments and quit
        if not self.args.connect and not self.args.start_session:
            self.parser.error(_("You need to use either --connect or --start-session"))
        self.host.quit()


class ProfileDisconnect(base.CommandBase):

    def __init__(self, host):
        super(ProfileDisconnect, self).__init__(
            host, "disconnect", need_connect=False, help=("disconnect a profile")
        )

    def add_parser_options(self):
        pass

    async def start(self):
        try:
            await self.host.bridge.disconnect(self.args.profile)
        except Exception as e:
            self.disp(f"can't disconnect profile: {e}", error=True)
            self.host.quit(C.EXIT_BRIDGE_ERRBACK)
        else:
            self.host.quit()


class ProfileCreate(base.CommandBase):
    def __init__(self, host):
        super(ProfileCreate, self).__init__(
            host, "create", use_profile=False, help=("create a new profile")
        )

    def add_parser_options(self):
        self.parser.add_argument("profile", type=str, help=_("the name of the profile"))
        self.parser.add_argument(
            "-p",
            "--password",
            type=str,
            default="",
            help=_("the password of the profile"),
        )
        self.parser.add_argument(
            "-j", "--jid", type=str, help=_("the jid of the profile")
        )
        self.parser.add_argument(
            "-x",
            "--xmpp-password",
            type=str,
            help=_(
                "the password of the XMPP account (use profile password if not specified)"
            ),
            metavar="PASSWORD",
        )
        self.parser.add_argument(
            "-A",
            "--autoconnect",
            choices=[C.BOOL_TRUE, C.BOOL_FALSE],
            nargs="?",
            const=C.BOOL_TRUE,
            help=_("connect this profile automatically when backend starts"),
        )
        self.parser.add_argument(
            "-C",
            "--component",
            default="",
            help=_("set to component import name (entry point) if this is a component"),
        )

    async def start(self):
        """Create a new profile"""
        if self.args.profile in await self.host.bridge.profiles_list_get():
            self.disp(f"Profile {self.args.profile} already exists.", error=True)
            self.host.quit(C.EXIT_BRIDGE_ERROR)
        try:
            await self.host.bridge.profile_create(
                self.args.profile, self.args.password, self.args.component
            )
        except Exception as e:
            self.disp(f"can't create profile: {e}", error=True)
            self.host.quit(C.EXIT_BRIDGE_ERRBACK)

        try:
            await self.host.bridge.profile_start_session(
                self.args.password, self.args.profile
            )
        except Exception as e:
            self.disp(f"can't start profile session: {e}", error=True)
            self.host.quit(C.EXIT_BRIDGE_ERRBACK)

        if self.args.jid:
            await self.host.bridge.param_set(
                "JabberID", self.args.jid, "Connection", profile_key=self.args.profile
            )
        xmpp_pwd = self.args.password or self.args.xmpp_password
        if xmpp_pwd:
            await self.host.bridge.param_set(
                "Password", xmpp_pwd, "Connection", profile_key=self.args.profile
            )

        if self.args.autoconnect is not None:
            await self.host.bridge.param_set(
                "autoconnect_backend",
                self.args.autoconnect,
                "Connection",
                profile_key=self.args.profile,
            )

        self.disp(f"profile {self.args.profile} created successfully", 1)
        self.host.quit()


class ProfileDefault(base.CommandBase):
    def __init__(self, host):
        super(ProfileDefault, self).__init__(
            host, "default", use_profile=False, help=("print default profile")
        )

    def add_parser_options(self):
        pass

    async def start(self):
        print(await self.host.bridge.profile_name_get("@DEFAULT@"))
        self.host.quit()


class ProfileDelete(base.CommandBase):
    def __init__(self, host):
        super(ProfileDelete, self).__init__(
            host, "delete", use_profile=False, help=("delete a profile")
        )

    def add_parser_options(self):
        self.parser.add_argument("profile", type=str, help=PROFILE_HELP)
        self.parser.add_argument(
            "-f",
            "--force",
            action="store_true",
            help=_("delete profile without confirmation"),
        )

    async def start(self):
        if self.args.profile not in await self.host.bridge.profiles_list_get():
            log.error(f"Profile {self.args.profile} doesn't exist.")
            self.host.quit(C.EXIT_NOT_FOUND)
        if not self.args.force:
            message = f"Are you sure to delete profile [{self.args.profile}] ?"
            cancel_message = "Profile deletion cancelled"
            await self.host.confirm_or_quit(message, cancel_message)

        await self.host.bridge.profile_delete_async(self.args.profile)
        self.host.quit()


class ProfileInfo(base.CommandBase):

    def __init__(self, host):
        super(ProfileInfo, self).__init__(
            host,
            "info",
            need_connect=False,
            use_output=C.OUTPUT_DICT,
            help=_("get information about a profile"),
        )
        self.to_show = [
            (_("jid"), "Connection", "JabberID"),
        ]

    def add_parser_options(self):
        self.parser.add_argument(
            "--show-password",
            action="store_true",
            help=_("show the XMPP password IN CLEAR TEXT"),
        )

    async def start(self):
        if self.args.show_password:
            self.to_show.append((_("XMPP password"), "Connection", "Password"))
        self.to_show.append(
            (_("autoconnect (backend)"), "Connection", "autoconnect_backend")
        )
        data = {}
        for label, category, name in self.to_show:
            try:
                value = await self.host.bridge.param_get_a_async(
                    name, category, profile_key=self.host.profile
                )
            except Exception as e:
                self.disp(f"can't get {name}/{category} param: {e}", error=True)
            else:
                data[label] = value

        await self.output(data)
        self.host.quit()


class ProfileList(base.CommandBase):
    def __init__(self, host):
        super(ProfileList, self).__init__(
            host, "list", use_profile=False, use_output="list", help=("list profiles")
        )

    def add_parser_options(self):
        group = self.parser.add_mutually_exclusive_group()
        group.add_argument(
            "-c", "--clients", action="store_true", help=_("get clients profiles only")
        )
        group.add_argument(
            "-C",
            "--components",
            action="store_true",
            help=("get components profiles only"),
        )

    async def start(self):
        if self.args.clients:
            clients, components = True, False
        elif self.args.components:
            clients, components = False, True
        else:
            clients, components = True, True
        await self.output(await self.host.bridge.profiles_list_get(clients, components))
        self.host.quit()


class ProfileModify(base.CommandBase):

    def __init__(self, host):
        super(ProfileModify, self).__init__(
            host, "modify", need_connect=False, help=_("modify an existing profile")
        )

    def add_parser_options(self):
        profile_pwd_group = self.parser.add_mutually_exclusive_group()
        profile_pwd_group.add_argument(
            "-w", "--password", help=_("change the password of the profile")
        )
        profile_pwd_group.add_argument(
            "--disable-password",
            action="store_true",
            help=_("disable profile password (dangerous!)"),
        )
        self.parser.add_argument("-j", "--jid", help=_("the jid of the profile"))
        self.parser.add_argument(
            "-x",
            "--xmpp-password",
            help=_("change the password of the XMPP account"),
            metavar="PASSWORD",
        )
        self.parser.add_argument(
            "-D", "--default", action="store_true", help=_("set as default profile")
        )
        self.parser.add_argument(
            "-A",
            "--autoconnect",
            choices=[C.BOOL_TRUE, C.BOOL_FALSE],
            nargs="?",
            const=C.BOOL_TRUE,
            help=_("connect this profile automatically when backend starts"),
        )

    async def start(self):
        if self.args.disable_password:
            self.args.password = ""
        if self.args.password is not None:
            await self.host.bridge.param_set(
                "Password", self.args.password, "General", profile_key=self.host.profile
            )
        if self.args.jid is not None:
            await self.host.bridge.param_set(
                "JabberID", self.args.jid, "Connection", profile_key=self.host.profile
            )
        if self.args.xmpp_password is not None:
            await self.host.bridge.param_set(
                "Password",
                self.args.xmpp_password,
                "Connection",
                profile_key=self.host.profile,
            )
        if self.args.default:
            await self.host.bridge.profile_set_default(self.host.profile)
        if self.args.autoconnect is not None:
            await self.host.bridge.param_set(
                "autoconnect_backend",
                self.args.autoconnect,
                "Connection",
                profile_key=self.host.profile,
            )

        self.host.quit()


class Profile(base.CommandBase):
    subcommands = (
        ProfileConnect,
        ProfileDisconnect,
        ProfileCreate,
        ProfileDefault,
        ProfileDelete,
        ProfileInfo,
        ProfileList,
        ProfileModify,
    )

    def __init__(self, host):
        super(Profile, self).__init__(
            host, "profile", use_profile=False, help=_("profile commands")
        )