diff libervia/cli/cmd_bookmarks.py @ 4327:554a87ae17a6

plugin XEP-0048, XEP-0402; CLI (bookmarks): implement XEP-0402 (PEP Native Bookmarks): - Former bookmarks implementation is now labeled as "legacy". - XEP-0402 is now used for bookmarks when relevant namespaces are found, and it fallbacks to legacy XEP-0048/XEP-0049 bookmarks otherwise. - CLI legacy bookmark commands have been moved to `bookmarks legacy` - CLI bookmarks commands now use the new XEP-0402 (with fallback to legacy one automatically used if necessary).
author Goffi <goffi@goffi.org>
date Wed, 20 Nov 2024 11:43:27 +0100
parents 47401850dec6
children
line wrap: on
line diff
--- a/libervia/cli/cmd_bookmarks.py	Wed Nov 20 11:38:44 2024 +0100
+++ b/libervia/cli/cmd_bookmarks.py	Wed Nov 20 11:43:27 2024 +0100
@@ -17,86 +17,70 @@
 # 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/>.
 
-from . import base
+from rich.table import Table
+
 from libervia.backend.core.i18n import _
+from libervia.backend.tools.common import data_format
 from libervia.cli.constants import Const as C
 
+from . import base
+from .bookmarks_legacy import BookmarksLegacy
+
 __commands__ = ["Bookmarks"]
 
-STORAGE_LOCATIONS = ("local", "private", "pubsub")
-TYPES = ("muc", "url")
 
-
-class BookmarksCommon(base.CommandBase):
-    """Class used to group common options of bookmarks subcommands"""
-
-    def add_parser_options(self, location_default="all"):
-        self.parser.add_argument(
-            "-l",
-            "--location",
-            type=str,
-            choices=(location_default,) + STORAGE_LOCATIONS,
-            default=location_default,
-            help=_("storage location (default: %(default)s)"),
-        )
-        self.parser.add_argument(
-            "-t",
-            "--type",
-            type=str,
-            choices=TYPES,
-            default=TYPES[0],
-            help=_("bookmarks type (default: %(default)s)"),
+class BookmarksList(base.CommandBase):
+    def __init__(self, host):
+        extra_outputs = {"default": self.default_output}
+        super().__init__(
+            host, "list", help=_("list bookmarks"),
+            use_output=C.OUTPUT_COMPLEX,
+            extra_outputs=extra_outputs
         )
 
+    def add_parser_options(self):
+        pass
 
-class BookmarksList(BookmarksCommon):
-    def __init__(self, host):
-        super(BookmarksList, self).__init__(host, "list", help=_("list bookmarks"))
+    def default_output(self, data: dict) -> None:
+        table = Table(title="📚 " + _("Group Chat Bookmarks"))
+        table.add_column("🌐 JID")
+        table.add_column("📝 " + _("Name"))
+        table.add_column("👤 " + _("Nick"))
+        table.add_column("🔒 " + _("Password"))
+        table.add_column("🚪 " + _("Joined"))
+
+        for jid, conference_data in data.items():
+            table.add_row(
+                str(jid),
+                conference_data.get("name", ""),
+                conference_data.get("nick", ""),
+                conference_data.get("password", ""),
+                "✅" if conference_data.get("autojoin", False) else "❌"
+            )
+
+        self.console.print(table)
 
     async def start(self):
         try:
-            data = await self.host.bridge.bookmarks_list(
-                self.args.type, self.args.location, self.host.profile
-            )
+            data = data_format.deserialise(await self.host.bridge.bookmarks_list(
+                "", self.host.profile
+            ))
         except Exception as e:
             self.disp(f"can't get bookmarks list: {e}", error=True)
             self.host.quit(C.EXIT_BRIDGE_ERRBACK)
+            return
 
-        mess = []
-        for location in STORAGE_LOCATIONS:
-            if not data[location]:
-                continue
-            loc_mess = []
-            loc_mess.append(f"{location}:")
-            book_mess = []
-            for book_link, book_data in list(data[location].items()):
-                name = book_data.get("name")
-                autojoin = book_data.get("autojoin", "false") == "true"
-                nick = book_data.get("nick")
-                book_mess.append(
-                    "\t%s[%s%s]%s"
-                    % (
-                        (name + " ") if name else "",
-                        book_link,
-                        " (%s)" % nick if nick else "",
-                        " (*)" if autojoin else "",
-                    )
-                )
-            loc_mess.append("\n".join(book_mess))
-            mess.append("\n".join(loc_mess))
-
-        print("\n\n".join(mess))
+        await self.output(data)
         self.host.quit()
 
 
-class BookmarksRemove(BookmarksCommon):
+class BookmarksRemove(base.CommandBase):
     def __init__(self, host):
-        super(BookmarksRemove, self).__init__(host, "remove", help=_("remove a bookmark"))
+        super().__init__(host, "remove", help=_("remove a bookmark"))
 
     def add_parser_options(self):
-        super(BookmarksRemove, self).add_parser_options()
         self.parser.add_argument(
-            "bookmark", help=_("jid (for muc bookmark) or url of to remove")
+            "bookmark", help=_("jid of the bookmark to remove")
         )
         self.parser.add_argument(
             "-f",
@@ -107,11 +91,14 @@
 
     async def start(self):
         if not self.args.force:
-            await self.host.confirm_or_quit(_("Are you sure to delete this bookmark?"))
+            await self.host.confirm_or_quit(
+                _("Are you sure to delete the bookmark {bookmark_id!r}?")
+                .format(bookmark_id=self.args.bookmark)
+            )
 
         try:
-            await self.host.bridge.bookmarks_remove(
-                self.args.type, self.args.bookmark, self.args.location, self.host.profile
+            await self.host.bridge.bookmark_remove(
+                self.args.bookmark, self.host.profile
             )
         except Exception as e:
             self.disp(_("can't delete bookmark: {e}").format(e=e), error=True)
@@ -121,55 +108,88 @@
             self.host.quit()
 
 
-class BookmarksAdd(BookmarksCommon):
+class BookmarksSet(base.CommandBase):
     def __init__(self, host):
-        super(BookmarksAdd, self).__init__(host, "add", help=_("add a bookmark"))
+        super().__init__(
+            host, "set", help=_("add or update a bookmark")
+        )
 
     def add_parser_options(self):
-        super(BookmarksAdd, self).add_parser_options(location_default="auto")
+        self.parser.add_argument("bookmark", help=_("jid of the chat room"))
+        self.parser.add_argument("-n", "--name", help=_("bookmark name"), dest="name")
         self.parser.add_argument(
-            "bookmark", help=_("jid (for muc bookmark) or url of to remove")
+            "-j",
+            "--join",
+            nargs="?",
+            # Value use when option is missing.
+            default=None,
+            # Value use when option is used, but value is not specified.
+            const=True,
+            type=base.optional_bool_decoder,
+            # The bookmark attribute is called "autojoin" for historical reason, but it's
+            # now used a "join" flag, so we use ``join`` here for the option.
+            dest="autojoin",
+            metavar="BOOL",
+            help=_("join the conference room"),
         )
-        self.parser.add_argument("-n", "--name", help=_("bookmark name"))
-        muc_group = self.parser.add_argument_group(_("MUC specific options"))
-        muc_group.add_argument("-N", "--nick", help=_("nickname"))
-        muc_group.add_argument(
-            "-a",
-            "--autojoin",
+        self.parser.add_argument(
+            "-N",
+            "--nick", help=_("preferred roomnick for the chatroom")
+        )
+        self.parser.add_argument(
+            "-P",
+            "--password", help=_("password used to access the chatroom")
+        )
+        self.parser.add_argument(
+            "-u",
+            "--update",
             action="store_true",
-            help=_("join room on profile connection"),
+            help=_("update bookmark data instead of replacing")
         )
 
     async def start(self):
-        if self.args.type == "url" and (self.args.autojoin or self.args.nick is not None):
-            self.parser.error(_("You can't use --autojoin or --nick with --type url"))
-        data = {}
-        if self.args.autojoin:
-            data["autojoin"] = "true"
-        if self.args.nick is not None:
-            data["nick"] = self.args.nick
-        if self.args.name is not None:
-            data["name"] = self.args.name
+        conference_data = {
+            "autojoin": self.args.autojoin,
+            "name": self.args.name,
+            "nick": self.args.nick,
+            "password": self.args.password,
+        }
+
+        conference_data = {k: v for k, v in conference_data.items() if v is not None}
+        if self.args.update:
+            try:
+                old_conference_data = data_format.deserialise(
+                    await self.host.bridge.bookmark_get(
+                        self.args.bookmark, self.host.profile
+                    )
+                )
+            except Exception as e:
+                self.disp(
+                    f"Can't find existing bookmark {self.args.bookmark!r}: {e}. We "
+                    "create it.",
+                    error=True
+                )
+            else:
+                old_conference_data.update(conference_data)
+                conference_data = old_conference_data
+
         try:
-            await self.host.bridge.bookmarks_add(
-                self.args.type,
-                self.args.bookmark,
-                data,
-                self.args.location,
+            await self.host.bridge.bookmarks_set(
+                data_format.serialise({self.args.bookmark: conference_data}),
                 self.host.profile,
             )
         except Exception as e:
             self.disp(f"can't add bookmark: {e}", error=True)
             self.host.quit(C.EXIT_BRIDGE_ERRBACK)
         else:
-            self.disp(_("bookmark successfully added"))
+            self.disp(_("bookmark successfully set"))
             self.host.quit()
 
 
 class Bookmarks(base.CommandBase):
-    subcommands = (BookmarksList, BookmarksRemove, BookmarksAdd)
+    subcommands = (BookmarksList, BookmarksSet, BookmarksRemove, BookmarksLegacy)
 
     def __init__(self, host):
-        super(Bookmarks, self).__init__(
+        super().__init__(
             host, "bookmarks", use_profile=False, help=_("manage bookmarks")
         )