A web browser-independent bookmark manager that uses rofi as an interface to save, open or type out URL bookmarks.
git clone https://mcol.xyz/code/bkmkfi
Log | Files | Refs | README | LICENSE

commit 6ebba8641b285737c0223d90708333a467bfe4ef
Author: mclgn <mlv@posteo.net>
Date:   Sun,  2 Jun 2019 10:57:30 +0100

init git repo with script

Diffstat:
Abkmkfi | 317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 317 insertions(+), 0 deletions(-)

diff --git a/bkmkfi b/bkmkfi @@ -0,0 +1,317 @@ +#!/usr/bin/env python3 +# +# Manage web page bookmarks using rofi +# + + +## Setup +from subprocess import PIPE, run +from json import load, dump +import os +from sys import exit +import argparse +from pykeyboard import PyKeyboard +from re import search + +home = os.path.expanduser('~') +path = home + '/.config/bkmks.json' + + +## Launch rofi instance +def rofi(stdin='', prompt=None): + """ + Execute rofi with specified inputs and prompt + """ + kwargs = {} + kwargs['stdout'] = PIPE + kwargs['universal_newlines'] = True + + args = ['rofi', '-dmenu'] + if prompt: + args.append('-p') + args.append(prompt) + + result = run(args, input=stdin, **kwargs) + + # escape or empty input is cancellation + if result.returncode or not result.stdout: + exit(1) + + # strip newline + result.stdout = result.stdout[0:-1] + return result.stdout + + +## Parse inputs +def parse_args(): + """ Parse command line arguments """ + + parser = argparse.ArgumentParser( + usage = 'bkmkfi [-h | -o [COMMAND] | -d | -c | -t] [-b BOOKMARKS]', + description = 'rofi bookmark manager', + ) + + parser.add_argument('-b', '--bookmarks', nargs=1, + help='bookmarks JSON (default ~/.config/bkmks.json)', + default=[home + '/.config/bkmks.json'], + type=str) + + parser.add_argument('-o', '--open', nargs=1, + help='open command (e.g. "tor-browser --allow-remote %%s")', + default=[''], + type=str) + + parser.add_argument('-d', '--delete', + help='delete bookmark', + action='store_true') + + parser.add_argument('-c', '--copy', + help='copy bookmark URL to clipboard', + action='store_true') + + parser.add_argument('-t', '--type', + help='type bookmark URL with keyboard', + action='store_true') + + args = parser.parse_args() + return args + + +## Manage bookmarks in class +class Bookmarks(object): + """ + Manage Bookmarks + + Bookmarks are saved in a json file with the following structure: + + url1 + [[ folder1 ]] + url2 + url3 + + URLs can be in folders or at the top level + Folders are indicated by the format '[[ folder title ]]' + """ + + def __init__(self, p): + """ Load bookmarks from file """ + path = p.bookmarks[0] + + if os.path.isfile(path): + try: + with open(path, 'r') as bk_file: + bookmarks = load(bk_file) + except: + print("%s could not be loaded." % path) + exit(1) + + else: + print("Bookmarks database %s does not exist" % path) + print("Making a new database") + bookmarks = [] + + self.path = path + self.bookmarks = bookmarks + self.selected = None + self.selected_isnew = False + + +## Select bookmark + def select(self): + """ + Select or add bookmark + This is the first action that selects the URL for further processing + """ + + bookmarks = self.bookmarks + + stdin='' + if bookmarks: + prompt = 'Select or add new bookmark' + for entry in bookmarks: + if isinstance(entry, dict): + folder_name = list(entry.keys())[0] + stdin += folder_name + '\n' + else: + stdin += entry + '\n' + else: + prompt = 'Add a new bookmark' + + selected = [ + rofi( + stdin=stdin, + prompt=prompt + ) + ] + + # continue selecting into folders + if search("^\#\#.*\#\#$", selected[-1]): + while search("^\#\#.*\#\#$", selected[-1]): + # traverse all levels + current_level = bookmarks + for i in range(len(selected)): + for entry in current_level: + if isinstance(entry, dict) and selected[i] in entry: + current_level = entry[selected[i]] + break + else: + current_level = [] + stdin='' + for entry in current_level: + if isinstance(entry, dict): + folder_name = list(entry.keys())[0] + stdin += folder_name + '\n' + else: + stdin += entry + '\n' + selected.append( + rofi( + stdin=stdin, + prompt="> ".join(selected) + "> " + ) + ) + if not selected[-1] in current_level: + self.selected_isnew = True + current_level.append(selected[-1]) + last_level = current_level + current_level = bookmarks + bookmarks[ + + + else: + # selection is at top level + if not selected[-1] in bookmarks: + self.selected_isnew = True + + exit(1) + + self.selected = selected + self.bookmarks = bookmarks + self.get_url() + + +## Get URL from entry + def get_url(self): + """ + Extract URL string from bookmark entry + This will attempt to find a URL, looking for: + http[s]:// www. .co .net + It finds the first match, in the order shown here + """ + http_url = search("(?P<url>https?://[^\s]+)", self.selected) + if http_url: + self.url = http_url.group("url") + return + + www_url = search("(?P<url>www\.[^\s]+)", self.selected) + if www_url: + self.url = www_url.group("url") + return + + co_url = search("(?P<url>\.co[^\s]+)", self.selected) + if co_url: + self.url = co_url.group("url") + return + + net_url = search("(?P<url>\.net[^\s]+)", self.selected) + if net_url: + self.url = net_url.group("url") + return + + self.url = self.selected + + +## Open URL in new tab + def open(self, open_cmd): + """ + Open URL in web browser new tab + Usage: bkmkfi -o "command %s" + %s is replaced with the URL + """ + if os.fork() == 0: + os.system(open_cmd % self.url) + + +## Type URL with keyboard + def type(self): + """ + Type selected URL + This depends on PyKeyboard + """ + k = PyKeyboard() + k.type_string(self.url) + + +## Delete entry + def delete(self): + """ + Delete bookmark entry + """ + self.bookmarks.remove(self.selected) + self.save() + + +## Copy URL to clipboard + def copy(self): + """ + Copy bookmark URL to clipboard + This copies by piping the URL to xclip + """ + + cmd = 'printf ' + self.url + ' | xclip -selection clipboard' + os.system(cmd) + + +## Save bookmarks file + def save(self): + """ Save bookmarks """ + selected = self.selected + current_level = self.bookmarks + for i in range(len(selected)): + if search("^\#\#.*\#\#$", selected[i]): + for entry in current_level + if isinstance(entry, dict) and selected[i] in entry: + current_level = entry[selected[i]] + # TODO HERE + + + else: + if not selected[i] in current_level: + self.bookmarks.append(selected[i]) + + with open(self.path, 'w') as bk_file: + dump(self.bookmarks, bk_file, indent=4) + + +## Main +def main(): + p = parse_args() + + # Create bookmarks class + bkmks = Bookmarks(p) + + # Select entry + bkmks.select() + + # Decide what to do with it + if bkmks.selected_isnew: + # just adding an entry + bkmks.save() + + elif p.open[0]: + # open URL in new tab + bkmks.open(p.open[0]) + + elif p.type: + # type out URL + bkmks.type() + + elif p.delete: + # delete entry + bkmks.delete() + + elif p.copy: + # copy entry to clipboard + bkmks.copy() + +if __name__ == '__main__': + main()