commit db55c04419b8f6c63145282becb1b189f7c929c5 Author: Schimon Jehudah Date: Wed Feb 28 22:54:48 2024 +0000 Slixmark Bookmark Manager diff --git a/slixmark.py b/slixmark.py new file mode 100644 index 0000000..f031cc2 --- /dev/null +++ b/slixmark.py @@ -0,0 +1,1001 @@ +#!/usr/bin/env python3 + +# Slixmark: The XMPP Bookmark Manager +# Copyright (C) 2024 Schimon Zackary +# This file is part of Slixmark. +# See the file LICENSE for copying permission. + +import buku +from getpass import getpass +from argparse import ArgumentParser +import logging +import os +import slixmpp +import sys + +# bookmarks_db = buku.BukuDb(dbfile='temp.db') +# bookmarks_db.get_tag_all +# bookmarks_db.search_keywords_and_filter_by_tags +# bookmarks_db.exclude_results_from_search + +class Actions: + + def init_db(jid_bare): + filename = jid_bare + '.db' + data_dir = Actions.get_db_directory() + pathname = data_dir + '/' + filename + bookmarks_db = buku.BukuDb(dbfile=pathname) + return bookmarks_db + + def get_db_directory(): + if os.environ.get('HOME'): + data_home = os.path.join(os.environ.get('HOME'), '.local', 'share') + return os.path.join(data_home, 'slixmark') + elif sys.platform == 'win32': + data_home = os.environ.get('APPDATA') + if data_home is None: + return os.path.join( + os.path.dirname(__file__) + '/slixmark_data') + else: + return os.path.join(os.path.dirname(__file__) + '/slixmark_data') + + +class Documentation: + + def about(): + return ('Slixmark' + '\n' + 'Jabber/XMPP Bookmark Manager' + '\n\n' + 'Slixmark is an XMPP bot that facilitates accessing and ' + 'managing bookmarks remotely.' + '\n\n' + 'Slixmark is written in Python' + '\n\n' + 'It is utilizing buku to handle bookmarks, and slixmpp to ' + 'communicate in the XMPP telecommunication network.' + '\n\n' + 'https://gitgud.io/sjehuda/slixmark' + '\n\n' + 'Copyright 2024 Schimon Zackary' + '\n\n' + 'Made in Switzerland' + '\n\n' + 'πŸ‡¨πŸ‡­οΈ') + + def commands(): + return ("add URL [tag1,tag2,tag3,...]" + "\n" + " Bookmark URL along with comma-separated tags." + "\n\n" + "mod name " + "\n" + " Modify bookmark title." + "\n" + "mod note " + "\n" + " Modify bookmark description." + "\n" + "tag [+|-] [tag1,tag2,tag3,...]" + "\n" + " Modify bookmark tags. Appends or deletes tags, if flag tag " + "is preceded by \'+\' or \'-\' respectively." + "\n" + "del or " + "\n" + " Delete a bookmark by ID or URL." + "\n" + "\n" + "id " + "\n" + " Print a bookmark by ID." + "\n" + "last" + "\n" + " Print most recently bookmarked item." + "\n" + "tag " + "\n" + " Search bookmarks of given tag." + "\n" + "search " + "\n" + " Search bookmarks by a given search query." + "\n" + "search any " + "\n" + " Search bookmarks by a any given keyword." + # "\n" + # "regex" + # "\n" + # " Search bookmarks using Regular Expression." + "\n") + + def notice(): + return ('Copyright 2024 Schimon Zackary Jehudah' + '\n\n' + 'Permission is hereby granted, free of charge, to any person ' + 'obtaining a copy of this software and associated ' + 'documentation files (the β€œSoftware”), to deal in the ' + 'Software without restriction, including without limitation ' + 'the rights to use, copy, modify, merge, publish, distribute, ' + 'sublicense, and/or sell copies of the Software, and to ' + 'permit persons to whom the Software is furnished to do so, ' + 'subject to the following conditions:' + '\n\n' + 'The above copyright notice and this permission notice shall ' + 'be included in all copies or substantial portions of the ' + 'Software.' + '\n\n' + 'THE SOFTWARE IS PROVIDED β€œAS IS”, WITHOUT WARRANTY OF ANY ' + 'KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE ' + 'WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR ' + 'PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR ' + 'COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ' + 'LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR ' + 'OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ' + 'SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.') + + +class Slixmark(slixmpp.ClientXMPP): + + """ + Slixmark - Bookmark manager bot for Jabber/XMPP. + Slixmark is a bookmark manager bot based on buku and slixmpp. + """ + + def __init__(self, jid, password): + slixmpp.ClientXMPP.__init__(self, jid, password) + + # The session_start event will be triggered when + # the bot establishes its connection with the server + # and the XML streams are ready for use. We want to + # listen for this event so that we we can initialize + # our roster. + self.add_event_handler("session_start", self.start) + + # The message event is triggered whenever a message + # stanza is received. Be aware that that includes + # MUC messages and error messages. + self.add_event_handler("message", self.process_message) + self.add_event_handler("disco_info", self.discovery) + + async def start(self, event): + """ + Process the session_start event. + + Typical actions for the session_start event are + requesting the roster and broadcasting an initial + presence stanza. + + Arguments: + event -- An empty dictionary. The session_start + event does not provide any additional + data. + """ + self.command_list() + self.send_presence() + # await self.get_roster() + await self['xep_0115'].update_caps() + + async def discovery(self, DiscoInfo): + jid = DiscoInfo['from'] + await self['xep_0115'].update_caps(jid=jid) + # jid_bare = DiscoInfo['from'].bare + # self.bookmarks_db = buku.BukuDb(dbfile=jid_bare + '.db') + + def process_message(self, message): + """ + Process incoming message stanzas. Be aware that this also + includes MUC messages and error messages. It is usually + a good idea to check the messages's type before processing + or sending replies. + + Arguments: + message -- The received message stanza. See the documentation + for stanza objects and the Message stanza to see + how it may be used. + """ + jid_bare = message['from'].bare + bookmarks_db = Actions.init_db(jid_bare) + if message['type'] in ('chat', 'normal'): + message_text = " ".join(message['body'].split()) + message_lowercase = message_text.lower() + match message_lowercase: + case 'help': + message_body = '```\n' + Documentation.commands() + '\n```' + case _ if message_lowercase.startswith('add '): + message_lowercase_split = message_lowercase[4:].split(' ') + link = message_lowercase_split[0] + tags = ' '.join(message_lowercase_split[1:]) + tags = tags.replace(' ,', ',') + tags = tags.replace(', ', ',') + idx = bookmarks_db.get_rec_id(link) + if idx: + message_body = '*URL already exists.*' + else: + idx = bookmarks_db.add_rec(url=link, tags_in=tags) + message_body = ('*New bookmark has been added as {}.*' + .format(idx)) + case _ if message_lowercase.startswith('id'): + idx = message_lowercase[2:] + result = bookmarks_db.get_rec_by_id(idx) + message_body = self.format_message(result, True) + case 'last': + idx = bookmarks_db.get_max_id() + result = bookmarks_db.get_rec_by_id(idx) + message_body = self.format_message(result) + case _ if message_lowercase.startswith('search any '): + query = message_lowercase[11:] + query = query.split(' ') + results = bookmarks_db.searchdb(query, + all_keywords=False, + deep=True, + regex=False) + message_body = '*Results for query: {}*\n\n'.format(query) + for result in results: + message_body += self.format_message(result) + '\n\n' + message_body += '*Total of {} results*'.format(len(results)) + case _ if message_lowercase.startswith('search '): + query = message_lowercase[7:] + query = query.split(' ') + results = bookmarks_db.searchdb(query, + all_keywords=True, + deep=True, + regex=False) + message_body = '*Results for query: {}*\n\n'.format(query) + for result in results: + message_body += self.format_message(result) + '\n\n' + message_body += '*Total of {} results*\n\n'.format(len(results)) + # elif message.startswith('regex'): + # message_body = bookmark_regexp(message[7:]) + case _ if message_lowercase.startswith('del '): + val = message_lowercase[4:] + try: + idx = int(val) + except: + idx = bookmarks_db.get_rec_id(val) + bookmark = bookmarks_db.get_rec_by_id(idx) + message_body = self.format_message(bookmark, True) if bookmark else '' + result = bookmarks_db.delete_rec(idx) + if result: + message_body += '\n*Bookmark has been deleted.*' + else: + message_body += '\n*No action has been taken for index {}*'.format(idx) + case _ if message_lowercase.startswith('mod '): + message_lowercase_split = message_lowercase[4:].split(' ') + if len(message_lowercase_split) > 2: + arg = message_lowercase_split[0] + val = message_lowercase_split[1] + new = ' '.join(message_lowercase_split[2:]) + message_body = '' + try: + idx = int(val) + except: + idx = bookmarks_db.get_rec_id(val) + match arg: + case 'name': + result = bookmarks_db.update_rec(idx, title_in=new) + case 'note': + result = bookmarks_db.update_rec(idx, desc=new) + case _: + result = None + message_body = ('*Invalid argument. ' + 'Must be "name" or "note".*\n') + bookmark = bookmarks_db.get_rec_by_id(idx) + message_body += self.format_message(bookmark, True) if bookmark else '' + if result: + message_body += '\n*Bookmark has been deleted.*' + else: + message_body += '\n*No action has been taken for index {}*'.format(idx) + else: + message_body = ('Missing argument. ' + 'Require three arguments: ' + '(1) "name" or "note"; ' + '(2) or ; ' + '(3) .') + case _ if (message_lowercase.startswith('tag +') or + message_lowercase.startswith('tag -')): + message_lowercase_split = message_lowercase[4:].split(' ') + if len(message_lowercase_split) > 2: + arg = message_lowercase_split[0] + val = message_lowercase_split[1] + try: + idx = int(val) + except: + idx = bookmarks_db.get_rec_id(val) + # tag = ',' + ' '.join(message_lowercase_split[2:]) + ',' + # tag = ' '.join(message_lowercase_split[2:]) + tag = arg + ',' + ' '.join(message_lowercase_split[2:]) + tag = tag.replace(' ,', ',') + tag = tag.replace(', ', ',') + result = bookmarks_db.update_rec(idx, tags_in=tag) + bookmark = bookmarks_db.get_rec_by_id(idx) + if result: + message_body = self.format_message(bookmark, True) if bookmark else '' + message_body += '\n*Bookmark has been updated.*' + else: + message_body = '\n*No action has been taken for index {}*'.format(idx) + else: + message_body = ('Missing argument. ' + 'Require three arguments: ' + '(1) + or - sign; ' + '(2) or ; ' + '(3) .') + case _ if message_lowercase.startswith('tag'): + tag = message_lowercase[4:] + results = bookmarks_db.search_by_tag(tag) + message_body = '*Results for tag: {}*\n\n'.format(tag) + for result in results: + message_body += self.format_message(result) + '\n\n' + message_body += '*Total of {} results*'.format(len(results)) + case _: + message_body = ('Unknown command. Send "help" for list ' + 'of commands.') + message.reply(message_body).send() + #message.reply("Thanks for sending\n%(body)s" % message).send() + + def format_message(self, bookmark, extended): + idx = bookmark.id + url = bookmark.url + name = bookmark.title if bookmark.title else 'Untitled' + desc = bookmark.desc if bookmark.desc else 'No comment' + # rec = '\n πŸ”–οΈ {} [{}]\n πŸ”—οΈ {}\n 🏷️ {}'.format(title, index, link, tags) + if extended: + tags = '' if bookmark.tags_raw == ',' else ", ".join(bookmark.tags_raw.split(","))[2:-2] + tags = tags if tags else 'No tags' + message_body = ('{}. {}\n{}\n{}\n{}'.format(idx, name, url, desc, tags)) + else: + message_body = ('{}. {}\n{}'.format(idx, name, url)) + return message_body + + def command_list(self): + self['xep_0050'].add_command(node='add', + name='πŸ”–οΈ Add', + handler=self._handle_add) + self['xep_0050'].add_command(node='random', + name='🎲️ Random', + handler=self._handle_random) + self['xep_0050'].add_command(node='modify', + name='πŸ“‘οΈ Browse', + handler=self._handle_browse) + self['xep_0050'].add_command(node='search', + name='πŸ”οΈ Search', + handler=self._handle_search) + # self['xep_0050'].add_command(node='tag', + # name='🏷️ Tags', + # handler=self._handle_tag) + # self['xep_0050'].add_command(node='statistics', + # name='πŸ“ŠοΈ Statistics', + # handler=self._handle_stats) + self['xep_0050'].add_command(node='help', + name='πŸ“”οΈ Help', + handler=self._handle_help) + self['xep_0050'].add_command(node='license', + name='βœ’οΈ License', + handler=self._handle_license) + self['xep_0050'].add_command(node='about', + name='πŸ“œοΈ About', + handler=self._handle_about) + + def _handle_add(self, iq, session): + # jid = session['from'].bare + # jid_file = jid + # db_file = config.get_pathname_to_database(jid_file) + form = self['xep_0004'].make_form('form', 'Add') + form['instructions'] = 'Add a new bookmark' + form.add_field(desc='URL to bookmark.', + ftype='text-single', + label='URL', + required=True, + var='url') + form.add_field(desc='Title to add manually.', + ftype='text-single', + label='Title', + var='title') + form.add_field(desc='Description of the bookmark.', + ftype='text-multi', + label='Note', + var='note') + form.add_field(desc='Comma-separated tags.', + ftype='text-single', + label='Tags', + var='tag') + form.add_field(desc='Check to disable automatic title fetch.', + ftype='boolean', + label='Immutable', + var='immutable') + session['has_next'] = True + session['next'] = self._handle_edit_single + session['payload'] = form + return session + + def _handle_edit_single(self, payload, session): + jid_bare = session['from'].bare + bookmarks_db = Actions.init_db(jid_bare) + form = self['xep_0004'].make_form('form', 'Edit') + form['instructions'] = 'Edit bookmark' + url = payload['values']['url'] + idx = bookmarks_db.get_rec_id(url) + if not idx: + immu = payload['values']['immutable'] + desc = payload['values']['note'] + tags = payload['values']['tag'] + name = payload['values']['title'] + fetch = True if not name else False + idx = bookmarks_db.add_rec(desc=desc, + fetch=fetch, + immutable=immu, + tags_in=tags, + url=url) + bookmark = bookmarks_db.get_rec_by_id(idx) + form.add_field(ftype='text-single', + label='ID #', + value=str(bookmark.id)) + form.add_field(ftype='text-single', + label='Title', + value=bookmark.title) + form.add_field(ftype='text-single', + label='URL', + value=bookmark.url) + form.add_field(ftype='text-multi', + label='Note', + value=bookmark.desc) + options = form.add_field(ftype='list-multi', + label='Tags') + for tag in bookmark.tags_raw.split(','): + options.addOption(tag, tag) + form.add_field(ftype='boolean', + label='Immutable', + value=str(bookmark.flags)) + session['allow_complete'] = True + session['has_next'] = False + session['next'] = None + session['payload'] = form + return session + + def _handle_browse(self, iq, session): + form = self['xep_0004'].make_form('form', 'Browse') + form['instructions'] = 'Browse bookmark' + options = form.add_field(desc='Items per page.', + label='Items', + ftype='list-single', + value='20', + var='limit') + i = 10 + while i <= 50: + x = str(i) + options.addOption(x, x) + i += 10 + # options['validate']['datatype'] = 'xs:integer' + # options['validate']['range'] = { 'minimum': 10, 'maximum': 50 } + form.add_field(ftype='hidden', + value='0', + var='count') + session['has_next'] = True + session['next'] = self._handle_browse_all + session['payload'] = form + return session + + def _handle_tag(self, iq, session): + jid_bare = session['from'].bare + bookmarks_db = Actions.init_db(jid_bare) + form = self['xep_0004'].make_form('form', 'Tags') + form['instructions'] = ('Select tags to browse') + options = form.add_field(desc='Select tag to list its items.', + ftype='list-single', + label='Tags', + var='tag') + tags = bookmarks_db.get_tag_all() + counter = 0 + for tag in tags[0]: + if counter == 100: break + options.addOption(tag, tag) + counter += 1 + session['has_next'] = True + session['next'] = self._handle_browse_all + session['payload'] = form + return session + + def _handle_browse_all(self, payload, session): + jid_bare = session['from'].bare + bookmarks_db = Actions.init_db(jid_bare) + vals = payload['values'] + if 'url' in vals and vals['url']: + act = vals['action'] + url = vals['url'] + form = self['xep_0004'].make_form('form', 'Edit') + match act: + case 'edit': + if len(url) > 1: + form['instructions'] = 'Modify bookmarks' + idxs = '' + tags = '' + for i in url: + idx = bookmarks_db.get_rec_id(i) + idxs += ',' + str(idx) + bookmark = bookmarks_db.get_rec_by_id(idx) + tags += bookmark.tags_raw + form.add_field(desc='Comma-separated tags.', + ftype='text-single', + label='Tags', + value=bookmark.tags_raw, + var='tags_new') + form.add_field(desc='Check to disable automatic title fetch.', + ftype='boolean', + label='Immutable', + value=True, + var='immutable') + form.add_field(ftype='hidden', + value=idxs, + var='ids') + # session['next'] = self._handle_edit_single(payload, session) + else: + form['instructions'] = 'Modify bookmark' + idx = bookmarks_db.get_rec_id(url[0]) + bookmark = bookmarks_db.get_rec_by_id(idx) + form.add_field(ftype='fixed', + label='ID #', + value=str(bookmark.id), + var='id') + form.add_field(ftype='text-single', + label='Title', + value=bookmark.title, + var='title') + form.add_field(ftype='text-single', + label='URL', + value=bookmark.url, + var='url') + form.add_field(ftype='text-multi', + label='Note', + value=bookmark.desc, + var='description') + form.add_field(ftype='hidden', + value=bookmark.tags_raw, + var='tags_old') + form.add_field(desc='Comma-separated tags.', + ftype='text-single', + label='Tags', + value=bookmark.tags_raw, + var='tags_new') + form.add_field(desc='Check to disable automatic title fetch.', + ftype='boolean', + label='Immutable', + value=str(bookmark.flags), + var='immutable') + case 'remove': + form['instructions'] = ('The following items were deleted from ' + 'bookmarks.\nProceed to finish or ' + 'select items to restore.') + options = form.add_field(desc='Select items to restore', + ftype='list-multi', + label='Deleted items', + var='url') + for i in url: + idx = bookmarks_db.get_rec_id(i) + rec = bookmarks_db.get_rec_by_id(idx) + bookmarks_db.delete_rec(idx) + options.addOption(rec.title, i) + session['cancel'] = self._handle_cancel + # session['allow_complete'] = True + session['has_next'] = True + session['next'] = self._handle_action_result + session['payload'] = form + else: + limit = vals['limit'] if 'limit' in vals else 0 + if isinstance(limit, list): limit = limit[0] + count = vals['count'] if 'count' in vals else 0 + if isinstance(count, list): count = count[0] + form = self['xep_0004'].make_form('form', 'Browse') + form['instructions'] = ('Select bookmarks to modify') + options = form.add_field(desc='Select an action', + ftype='list-single', + label='Action', + required=True, + value='edit', + var='action') + options.addOption('Edit', 'edit') + options.addOption('Remove', 'remove') + options = form.add_field(desc='Selection of several bookmarks will ' + 'only allow to modify tags.', + ftype='list-multi', + label='Bookmark', + var='url') + bookmarks = bookmarks_db.get_rec_all() + # bookmarks = sorted(bookmarks, key=lambda x: x.title) + counter = int(count) + limiter = counter + int(limit) + for bookmark in bookmarks[counter:limiter]: + if counter == limiter: break + options.addOption(bookmark.title, bookmark.url) + counter += 1 + form.add_field(ftype='hidden', + value=str(counter), + var='count') + form.add_field(ftype='hidden', + value=str(limit), + var='limit') + session['has_next'] = True + session['next'] = self._handle_browse_all + session['payload'] = form + return session + + def _handle_action_result(self, payload, session): + jid_bare = session['from'].bare + bookmarks_db = Actions.init_db(jid_bare) + vals = payload['values'] + if 'id' in vals: + idx = vals['id'] + idx = int(idx) + tags_new = vals['tags_new'] + tags_new = tags_new.replace(' ,', ',') + tags_new = tags_new.replace(', ', ',') + tags_old = vals['tags_old'][0] + tags = tags_old.split(',') + tags_to_remove = '-,' + for tag in tags: + if tag not in tags_new: + tags_to_remove += ',' + tag + bookmarks_db.update_rec(idx, + url=vals['url'], + title_in=vals['title'], + tags_in='+,' + tags_new, + desc=vals['description'], + immutable=vals['immutable']) + bookmarks_db.update_rec(idx, + tags_in=tags_to_remove) + form = self['xep_0004'].make_form('result', 'Done') + rec = bookmarks_db.get_rec_by_id(idx) + form.add_field(ftype='text-single', + label='Title', + value=rec.title) + form.add_field(ftype='text-single', + label='URL', + value=rec.url) + form.add_field(ftype='text-single', + label='Note', + value=rec.desc) + form.add_field(ftype='text-single', + label='Tags', + value=rec.tags_raw) + form.add_field(ftype='text-single', + label='Immutable', + value=str(rec.flags)) + elif 'ids' in vals: + immutable = vals['immutable'] + tags_new = vals['tags_new'] + tags_new = tags_new.replace(' ,', ',') + tags_new = tags_new.replace(', ', ',') + idxs = vals['ids'].split(',') + for idx in idxs: + bookmarks_db.update_rec(idx, + tags_in='+,' + tags_new, + immutable=immutable) + form = self['xep_0004'].make_form('result', 'Done') + form.add_field(ftype='text-single', + label='Tags', + value=tags_new) + form.add_field(ftype='text-single', + label='Immutable', + value=immutable) + elif 'url' in vals: + url = vals['url'] + form = self['xep_0004'].make_form('result', 'Add') + form['instructions'] = ('The following items have been added to ' + 'the bookmarks\nNote: You will have to ' + 'manually tag these items, if you would.') + for i in url: + idx = bookmarks_db.add_rec(url=i) + rec = bookmarks_db.get_rec_by_id(idx) + form.add_field(ftype='fixed', + value=str(rec.id)) + form.add_field(ftype='text-single', + value=rec.title) + form.add_field(ftype='text-single', + value=rec.url) + session['allow_complete'] = True + session['has_next'] = False + session['next'] = None + session['payload'] = form + return session + + def _handle_random(self, iq, session): + jid_bare = session['from'].bare + bookmarks_db = Actions.init_db(jid_bare) + bookmarks = bookmarks_db.get_rec_all() + if bookmarks: + import random + bookmark = random.choice(bookmarks) + form = self['xep_0004'].make_form('form', 'Random') + form['instructions'] = 'Bookmark #{}'.format(bookmark.id) + form.add_field(ftype='fixed', + # ftype='text-single', + label='URL', + # required=True, + value=bookmark.url, + var='url') + form.add_field(ftype='text-single', + label='Title', + value=bookmark.title, + var='title') + form.add_field(ftype='text-multi', + label='Note', + value=bookmark.desc, + var='note') + form.add_field(desc='Comma-separated tags.', + ftype='text-single', + label='Tags', + value=bookmark.tags_raw, + var='tag') + form.add_field(desc='Check to disable automatic title fetch.', + ftype='boolean', + label='Immutable', + value=str(bookmark.flags), + var='immutable') + session['has_next'] = True + session['next'] = self._handle_edit_single + session['payload'] = form + else: + text_note = ('There are no bookmarks, yet.') + session['notes'] = [['info', text_note]] + return session + + def _handle_search(self, iq, session): + form = self['xep_0004'].make_form('form', 'Search') + form['instructions'] = 'Search for bookmarks' + form.add_field(desc='Enter a search term to query.', + ftype='text-single', + label='Search', + var='query') + options = form.add_field(desc='Select type of search.', + ftype='list-single', + label='Search by', + value='any', + var='type') + options.addOption('All keywords', 'all') + options.addOption('Any keyword', 'any') + options.addOption('Tag', 'tag') + form.add_field(desc='Search for matching substrings.', + ftype='boolean', + label='Deep', + value=True, + var='deep') + form.add_field(desc='Match a regular expression.', + ftype='boolean', + label='Regular Expression', + var='regex') + session['allow_prev'] = False + session['has_next'] = True + session['next'] = self._handle_search_result + session['payload'] = form + session['prev'] = None + return session + + def _handle_search_result(self, payload, session): + jid_bare = session['from'].bare + bookmarks_db = Actions.init_db(jid_bare) + vals = payload['values'] + if 'url' in vals and vals['url']: + act = vals['action'] + url = vals['url'] + form = self['xep_0004'].make_form('form', 'Edit') + match act: + case 'edit': + if len(url) > 1: + form['instructions'] = 'Modify bookmarks' + idxs = '' + tags = '' + for i in url: + idx = bookmarks_db.get_rec_id(i) + idxs += ',' + str(idx) + bookmark = bookmarks_db.get_rec_by_id(idx) + tags += bookmark.tags_raw + form.add_field(desc='Comma-separated tags.', + ftype='text-single', + label='Tags', + value=bookmark.tags_raw, + var='tags_new') + form.add_field(desc='Check to disable automatic title fetch.', + ftype='boolean', + label='Immutable', + value=True, + var='immutable') + form.add_field(ftype='hidden', + value=idxs, + var='ids') + # session['next'] = self._handle_edit_single(payload, session) + else: + form['instructions'] = 'Modify bookmark' + idx = bookmarks_db.get_rec_id(url[0]) + bookmark = bookmarks_db.get_rec_by_id(idx) + form.add_field(ftype='fixed', + label='ID #', + value=str(bookmark.id), + var='id') + form.add_field(ftype='text-single', + label='Title', + value=bookmark.title, + var='title') + form.add_field(ftype='text-single', + label='URL', + value=bookmark.url, + var='url') + form.add_field(ftype='text-multi', + label='Note', + value=bookmark.desc, + var='description') + form.add_field(ftype='hidden', + value=bookmark.tags_raw, + var='tags_old') + form.add_field(desc='Comma-separated tags.', + ftype='text-single', + label='Tags', + value=bookmark.tags_raw, + var='tags_new') + form.add_field(desc='Check to disable automatic title fetch.', + ftype='boolean', + label='Immutable', + value=str(bookmark.flags), + var='immutable') + case 'remove': + form['instructions'] = ('The following items were deleted from ' + 'bookmarks.\nProceed to finish or ' + 'select items to restore.') + options = form.add_field(desc='Select items to restore', + ftype='list-multi', + label='Deleted items', + var='url') + for i in url: + idx = bookmarks_db.get_rec_id(i) + rec = bookmarks_db.get_rec_by_id(idx) + bookmarks_db.delete_rec(idx) + options.addOption(rec.title, i) + session['cancel'] = self._handle_cancel + # session['allow_complete'] = True + session['has_next'] = True + session['next'] = self._handle_action_result + session['payload'] = form + else: + count = vals['count'] if 'count' in vals else 0 + if isinstance(count, list): count = count[0] + # count = count if count else 0 + query = vals['query'] + if isinstance(query, list): query = query[0] + stype = vals['type'] + if isinstance(stype, list): stype = stype[0] + deep = vals['deep'] + if isinstance(deep, list): deep = deep[0] + deep = True if '1' else False + regex = vals['regex'] + if isinstance(regex, list): regex = regex[0] + regex = True if '1' else False + match stype: + case 'all': + bookmarks = bookmarks_db.searchdb(query, + all_keywords=True, + deep=deep, + regex=regex) + case 'any': + bookmarks = bookmarks_db.searchdb(query, + all_keywords=False, + deep=deep, + regex=regex) + case 'tag': + bookmarks = bookmarks_db.search_by_tag(query) + # bookmarks = sorted(bookmarks, key=lambda x: x.title) + if bookmarks: + form = self['xep_0004'].make_form('form', 'Browse') + form['instructions'] = ('Select bookmarks to modify') + options = form.add_field(desc='Select an action', + ftype='list-single', + label='Action', + required=True, + value='edit', + var='action') + options.addOption('Edit', 'edit') + options.addOption('Remove', 'remove') + options = form.add_field(desc='Selection of several bookmarks ' + 'will only allow to modify tags.', + ftype='list-multi', + label='Bookmark', + var='url') + counter = int(count) + limiter = counter + 10 + for bookmark in bookmarks[counter:limiter]: + if counter == limiter: break + options.addOption(bookmark.title, bookmark.url) + counter += 1 + form.add_field(ftype='hidden', + value=str(counter), + var='count') + form.add_field(ftype='hidden', + value=query, + var='query') + form.add_field(ftype='hidden', + value=stype, + var='type') + deep = '1'if deep else '' + form.add_field(ftype='hidden', + value=deep, + var='deep') + regex = '1'if regex else '' + form.add_field(ftype='hidden', + value=regex, + var='regex') + session['has_next'] = True + session['next'] = self._handle_search_result + session['payload'] = form + else: + text_note = 'No results were yielded for: {}'.format(query) + session['notes'] = [['info', text_note]] + session['allow_prev'] = True + session['prev'] = self._handle_search + return session + + def _handle_cancel(self, payload, session): + text_note = ('Operation has been cancelled.' + '\n\n' + 'No action was taken.') + session['notes'] = [['info', text_note]] + return session + + def _handle_help(self, iq, session): + text_note = Documentation.commands() + session['notes'] = [['info', text_note]] + return session + + def _handle_license(self, iq, session): + text_note = Documentation.notice() + session['notes'] = [['info', text_note]] + return session + + def _handle_about(self, iq, session): + text_note = Documentation.about() + session['notes'] = [['info', text_note]] + return session + + +if __name__ == '__main__': + # Setup the command line arguments. + parser = ArgumentParser(description=Slixmark.__doc__) + + # Output verbosity options. + parser.add_argument("-q", "--quiet", help="set logging to ERROR", + action="store_const", dest="loglevel", + const=logging.ERROR, default=logging.INFO) + parser.add_argument("-d", "--debug", help="set logging to DEBUG", + action="store_const", dest="loglevel", + const=logging.DEBUG, default=logging.INFO) + + # JID and password options. + parser.add_argument("-j", "--jid", dest="jid", + help="JID to use") + parser.add_argument("-p", "--password", dest="password", + help="password to use") + + args = parser.parse_args() + + # Setup logging. + logging.basicConfig(level=args.loglevel, + format='%(levelname)-8s %(message)s') + + if args.jid is None: + args.jid = input("Username: ") + if args.password is None: + args.password = getpass("Password: ") + + # Setup the bot and register plugins. Note that while plugins may + # have interdependencies, the order in which you register them does + # not matter. + xmpp = Slixmark(args.jid, args.password) + xmpp.register_plugin('xep_0030') # Service Discovery + xmpp.register_plugin('xep_0004') # Data Forms + xmpp.register_plugin('xep_0060') # Publish-Subscribe + xmpp.register_plugin('xep_0050') # Ad-Hoc Commands + xmpp.register_plugin('xep_0115') # Entity Capabilities + xmpp.register_plugin('xep_0122') # Data Forms Validation + xmpp.register_plugin('xep_0199') # XMPP Ping + + # Connect to the XMPP server and start processing XMPP stanzas. + xmpp.connect() + xmpp.process()