Last active
March 30, 2022 15:42
-
-
Save shollingsworth/9d5806afcf7531ce8fc6e945619a1c93 to your computer and use it in GitHub Desktop.
script to use fzf to quickly open / search for chrome bookmarks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3 | |
| # -*- coding: utf-8 -*- | |
| import os | |
| import datetime | |
| import json | |
| from collections import deque | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Optional | |
| import subprocess | |
| import webbrowser | |
| # from pyfzf.pyfzf import FzfPrompt | |
| _BMFILE = Path("~/.config/google-chrome/Default/Bookmarks").expanduser().resolve() | |
| DATA = json.loads(_BMFILE.read_text()) | |
| WEBKIT_EPOCH = datetime.datetime(1601, 1, 1) | |
| def which(pgm:str, raise_err: bool = True): | |
| path = os.getenv("PATH", "") | |
| if not path: | |
| raise RuntimeError("$PATH environment variable not set") | |
| for p in path.split(os.path.pathsep): | |
| p = os.path.join(p, pgm) | |
| if os.path.exists(p) and os.access(p, os.X_OK): | |
| return p | |
| if raise_err: | |
| raise RuntimeError(f"{pgm} not found, exiting") | |
| return '' | |
| def fzf(content: str): | |
| fzf = which("fzf") | |
| try: | |
| output = subprocess.check_output([fzf], input=content.encode()) | |
| except subprocess.CalledProcessError: | |
| raise SystemExit("fzf aborted...") | |
| val = output.decode("utf-8").strip() | |
| return val | |
| @dataclass | |
| class Bookmark: | |
| """Bookmark.""" | |
| id: Optional[str] = None | |
| date_added: Optional[str] = None | |
| guid: Optional[str] = None | |
| name: Optional[str] = "" | |
| type: Optional[str] = None | |
| url: Optional[str] = "" | |
| children: Optional[list] = None | |
| date_modified: Optional[str] = None | |
| checksum: Optional[str] = None | |
| roots: Optional[dict] = None | |
| sync_metadata: Optional[dict] = None | |
| version: Optional[str] = None | |
| meta_info: Optional[dict] = None | |
| @property | |
| def hash(self) -> str: | |
| """Search string.""" | |
| val = self.name + " " + self.url # type: ignore | |
| return val | |
| # return hashlib.sha256(val.encode("utf-8")).hexdigest() | |
| @property | |
| def date(self): | |
| """Return date.""" | |
| if not self.date_added: | |
| return None | |
| WEBKIT_DELTA = datetime.timedelta(microseconds=int(self.date_added)) # type: ignore | |
| return WEBKIT_EPOCH + WEBKIT_DELTA | |
| def to_dict(self): | |
| """Return dict.""" | |
| return { | |
| "id": self.id, | |
| "date": str(self.date), | |
| "hash": self.hash, | |
| "guid": self.guid, | |
| "name": self.name, | |
| "type": self.type, | |
| "url": self.url, | |
| "meta_info": self.meta_info, | |
| } | |
| CACHE = {} | |
| def _genchildren(): | |
| tree = DATA | |
| queue = deque() | |
| queue.append((tree, None)) | |
| while queue: | |
| node, parent = queue.pop() | |
| try: | |
| if not isinstance(node, Bookmark): | |
| node = Bookmark(**node) | |
| except: | |
| print("Error item", node) | |
| raise | |
| if node.roots: | |
| for root in node.roots.values(): | |
| root = Bookmark(**root) | |
| queue.append((root, node)) | |
| elif node.children: | |
| for child in node.children: | |
| child = Bookmark(**child) | |
| queue.append((child, node)) | |
| else: | |
| key = parent.name + " " + node.hash | |
| CACHE[key] = node | |
| yield parent, node | |
| def iterbookmarks(): | |
| """Iterate bookmarks.""" | |
| for parent, node in sorted( | |
| _genchildren(), key=lambda x: (x[1].date or ""), reverse=True | |
| ): | |
| key = parent.name + " " + node.hash | |
| yield key | |
| def main(): | |
| """Run main function.""" | |
| try: | |
| select = fzf("\n".join(iterbookmarks())) | |
| print(select) | |
| val = CACHE[select] | |
| webbrowser.open(val.url) | |
| except KeyboardInterrupt: | |
| print("Bye") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment