comparison 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
comparison
equal deleted inserted replaced
4326:5fd6a4dc2122 4327:554a87ae17a6
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from rich.table import Table
21
22 from libervia.backend.core.i18n import _
23 from libervia.backend.tools.common import data_format
24 from libervia.cli.constants import Const as C
25
20 from . import base 26 from . import base
21 from libervia.backend.core.i18n import _ 27 from .bookmarks_legacy import BookmarksLegacy
22 from libervia.cli.constants import Const as C
23 28
24 __commands__ = ["Bookmarks"] 29 __commands__ = ["Bookmarks"]
25 30
26 STORAGE_LOCATIONS = ("local", "private", "pubsub")
27 TYPES = ("muc", "url")
28 31
29 32 class BookmarksList(base.CommandBase):
30 class BookmarksCommon(base.CommandBase): 33 def __init__(self, host):
31 """Class used to group common options of bookmarks subcommands""" 34 extra_outputs = {"default": self.default_output}
32 35 super().__init__(
33 def add_parser_options(self, location_default="all"): 36 host, "list", help=_("list bookmarks"),
34 self.parser.add_argument( 37 use_output=C.OUTPUT_COMPLEX,
35 "-l", 38 extra_outputs=extra_outputs
36 "--location",
37 type=str,
38 choices=(location_default,) + STORAGE_LOCATIONS,
39 default=location_default,
40 help=_("storage location (default: %(default)s)"),
41 )
42 self.parser.add_argument(
43 "-t",
44 "--type",
45 type=str,
46 choices=TYPES,
47 default=TYPES[0],
48 help=_("bookmarks type (default: %(default)s)"),
49 ) 39 )
50 40
41 def add_parser_options(self):
42 pass
51 43
52 class BookmarksList(BookmarksCommon): 44 def default_output(self, data: dict) -> None:
53 def __init__(self, host): 45 table = Table(title="📚 " + _("Group Chat Bookmarks"))
54 super(BookmarksList, self).__init__(host, "list", help=_("list bookmarks")) 46 table.add_column("🌐 JID")
47 table.add_column("📝 " + _("Name"))
48 table.add_column("👤 " + _("Nick"))
49 table.add_column("🔒 " + _("Password"))
50 table.add_column("🚪 " + _("Joined"))
51
52 for jid, conference_data in data.items():
53 table.add_row(
54 str(jid),
55 conference_data.get("name", ""),
56 conference_data.get("nick", ""),
57 conference_data.get("password", ""),
58 "✅" if conference_data.get("autojoin", False) else "❌"
59 )
60
61 self.console.print(table)
55 62
56 async def start(self): 63 async def start(self):
57 try: 64 try:
58 data = await self.host.bridge.bookmarks_list( 65 data = data_format.deserialise(await self.host.bridge.bookmarks_list(
59 self.args.type, self.args.location, self.host.profile 66 "", self.host.profile
60 ) 67 ))
61 except Exception as e: 68 except Exception as e:
62 self.disp(f"can't get bookmarks list: {e}", error=True) 69 self.disp(f"can't get bookmarks list: {e}", error=True)
63 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 70 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
71 return
64 72
65 mess = [] 73 await self.output(data)
66 for location in STORAGE_LOCATIONS:
67 if not data[location]:
68 continue
69 loc_mess = []
70 loc_mess.append(f"{location}:")
71 book_mess = []
72 for book_link, book_data in list(data[location].items()):
73 name = book_data.get("name")
74 autojoin = book_data.get("autojoin", "false") == "true"
75 nick = book_data.get("nick")
76 book_mess.append(
77 "\t%s[%s%s]%s"
78 % (
79 (name + " ") if name else "",
80 book_link,
81 " (%s)" % nick if nick else "",
82 " (*)" if autojoin else "",
83 )
84 )
85 loc_mess.append("\n".join(book_mess))
86 mess.append("\n".join(loc_mess))
87
88 print("\n\n".join(mess))
89 self.host.quit() 74 self.host.quit()
90 75
91 76
92 class BookmarksRemove(BookmarksCommon): 77 class BookmarksRemove(base.CommandBase):
93 def __init__(self, host): 78 def __init__(self, host):
94 super(BookmarksRemove, self).__init__(host, "remove", help=_("remove a bookmark")) 79 super().__init__(host, "remove", help=_("remove a bookmark"))
95 80
96 def add_parser_options(self): 81 def add_parser_options(self):
97 super(BookmarksRemove, self).add_parser_options()
98 self.parser.add_argument( 82 self.parser.add_argument(
99 "bookmark", help=_("jid (for muc bookmark) or url of to remove") 83 "bookmark", help=_("jid of the bookmark to remove")
100 ) 84 )
101 self.parser.add_argument( 85 self.parser.add_argument(
102 "-f", 86 "-f",
103 "--force", 87 "--force",
104 action="store_true", 88 action="store_true",
105 help=_("delete bookmark without confirmation"), 89 help=_("delete bookmark without confirmation"),
106 ) 90 )
107 91
108 async def start(self): 92 async def start(self):
109 if not self.args.force: 93 if not self.args.force:
110 await self.host.confirm_or_quit(_("Are you sure to delete this bookmark?")) 94 await self.host.confirm_or_quit(
95 _("Are you sure to delete the bookmark {bookmark_id!r}?")
96 .format(bookmark_id=self.args.bookmark)
97 )
111 98
112 try: 99 try:
113 await self.host.bridge.bookmarks_remove( 100 await self.host.bridge.bookmark_remove(
114 self.args.type, self.args.bookmark, self.args.location, self.host.profile 101 self.args.bookmark, self.host.profile
115 ) 102 )
116 except Exception as e: 103 except Exception as e:
117 self.disp(_("can't delete bookmark: {e}").format(e=e), error=True) 104 self.disp(_("can't delete bookmark: {e}").format(e=e), error=True)
118 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 105 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
119 else: 106 else:
120 self.disp(_("bookmark deleted")) 107 self.disp(_("bookmark deleted"))
121 self.host.quit() 108 self.host.quit()
122 109
123 110
124 class BookmarksAdd(BookmarksCommon): 111 class BookmarksSet(base.CommandBase):
125 def __init__(self, host): 112 def __init__(self, host):
126 super(BookmarksAdd, self).__init__(host, "add", help=_("add a bookmark")) 113 super().__init__(
114 host, "set", help=_("add or update a bookmark")
115 )
127 116
128 def add_parser_options(self): 117 def add_parser_options(self):
129 super(BookmarksAdd, self).add_parser_options(location_default="auto") 118 self.parser.add_argument("bookmark", help=_("jid of the chat room"))
119 self.parser.add_argument("-n", "--name", help=_("bookmark name"), dest="name")
130 self.parser.add_argument( 120 self.parser.add_argument(
131 "bookmark", help=_("jid (for muc bookmark) or url of to remove") 121 "-j",
122 "--join",
123 nargs="?",
124 # Value use when option is missing.
125 default=None,
126 # Value use when option is used, but value is not specified.
127 const=True,
128 type=base.optional_bool_decoder,
129 # The bookmark attribute is called "autojoin" for historical reason, but it's
130 # now used a "join" flag, so we use ``join`` here for the option.
131 dest="autojoin",
132 metavar="BOOL",
133 help=_("join the conference room"),
132 ) 134 )
133 self.parser.add_argument("-n", "--name", help=_("bookmark name")) 135 self.parser.add_argument(
134 muc_group = self.parser.add_argument_group(_("MUC specific options")) 136 "-N",
135 muc_group.add_argument("-N", "--nick", help=_("nickname")) 137 "--nick", help=_("preferred roomnick for the chatroom")
136 muc_group.add_argument( 138 )
137 "-a", 139 self.parser.add_argument(
138 "--autojoin", 140 "-P",
141 "--password", help=_("password used to access the chatroom")
142 )
143 self.parser.add_argument(
144 "-u",
145 "--update",
139 action="store_true", 146 action="store_true",
140 help=_("join room on profile connection"), 147 help=_("update bookmark data instead of replacing")
141 ) 148 )
142 149
143 async def start(self): 150 async def start(self):
144 if self.args.type == "url" and (self.args.autojoin or self.args.nick is not None): 151 conference_data = {
145 self.parser.error(_("You can't use --autojoin or --nick with --type url")) 152 "autojoin": self.args.autojoin,
146 data = {} 153 "name": self.args.name,
147 if self.args.autojoin: 154 "nick": self.args.nick,
148 data["autojoin"] = "true" 155 "password": self.args.password,
149 if self.args.nick is not None: 156 }
150 data["nick"] = self.args.nick 157
151 if self.args.name is not None: 158 conference_data = {k: v for k, v in conference_data.items() if v is not None}
152 data["name"] = self.args.name 159 if self.args.update:
160 try:
161 old_conference_data = data_format.deserialise(
162 await self.host.bridge.bookmark_get(
163 self.args.bookmark, self.host.profile
164 )
165 )
166 except Exception as e:
167 self.disp(
168 f"Can't find existing bookmark {self.args.bookmark!r}: {e}. We "
169 "create it.",
170 error=True
171 )
172 else:
173 old_conference_data.update(conference_data)
174 conference_data = old_conference_data
175
153 try: 176 try:
154 await self.host.bridge.bookmarks_add( 177 await self.host.bridge.bookmarks_set(
155 self.args.type, 178 data_format.serialise({self.args.bookmark: conference_data}),
156 self.args.bookmark,
157 data,
158 self.args.location,
159 self.host.profile, 179 self.host.profile,
160 ) 180 )
161 except Exception as e: 181 except Exception as e:
162 self.disp(f"can't add bookmark: {e}", error=True) 182 self.disp(f"can't add bookmark: {e}", error=True)
163 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 183 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
164 else: 184 else:
165 self.disp(_("bookmark successfully added")) 185 self.disp(_("bookmark successfully set"))
166 self.host.quit() 186 self.host.quit()
167 187
168 188
169 class Bookmarks(base.CommandBase): 189 class Bookmarks(base.CommandBase):
170 subcommands = (BookmarksList, BookmarksRemove, BookmarksAdd) 190 subcommands = (BookmarksList, BookmarksSet, BookmarksRemove, BookmarksLegacy)
171 191
172 def __init__(self, host): 192 def __init__(self, host):
173 super(Bookmarks, self).__init__( 193 super().__init__(
174 host, "bookmarks", use_profile=False, help=_("manage bookmarks") 194 host, "bookmarks", use_profile=False, help=_("manage bookmarks")
175 ) 195 )