#!/usr/bin/env python3 """ # Script ob-bookmarks ## Metadata **Kind**: #obsidian/ob-script **Language**: #python **Parent**:: [[Obsidian Chore Scripts]], [[Executable Markdown File]] ## Synopsis Export Chrome bookmarks into Obsidian. Example:: [[Tools (Bookmarks)]] ## Script ```python # """ from pathlib import Path from urllib.parse import unquote import tempfile import json import textwrap import os import re import shutil import filecmp import subprocess # ruff: noqa: E501 BOOKMARKLET_PREFIX = "javascript:" BOOKMARKLET_PREFIX_LEN = len(BOOKMARKLET_PREFIX) FOLDER_COMMENT_PREFIX = "edge://favorites/?id=" PRETTIER_EXISTS = shutil.which("prettier") is not None DATA_URL_RE = re.compile("data:(text/html;)?(charset=[^,]+,)?") METADATA_RE = re.compile(r"^([a-zA-Z0-9 _-]+):: ", re.M) NEWLINE_RE = re.compile(r" (\\\\)+ ") export_dir = Path.home() / "Dropbox" / "Brain" / "robot" / "Bookmarks Library" prettier_exe = "prettier.cmd" if os.name == "nt" else "prettier" def decode_javascript(encoded_js): unquoted = unquote(encoded_js) if PRETTIER_EXISTS: return subprocess.check_output( [prettier_exe, "--parser", "flow"], input=unquoted, shell=True, text=True ).rstrip() return unquoted.rstrip() def decode_html(encoded_html): unquoted = unquote(encoded_html) if PRETTIER_EXISTS: return ( subprocess.check_output( [ prettier_exe, "--parser", "html", "--html-whitespace-sensitivity", "ignore", ], input=unquoted.encode("utf-8"), ) .decode("utf-8") .rstrip() ) return unquoted.rstrip() def replace_newlines(text): return NEWLINE_RE.sub(lambda m: "\n" * (len(m.group(1)) // 2), text) def format_folder_note(note): if note.startswith("# "): try: note = note.split(" \\\\ ", maxsplit=1)[1] except IndexError: return "" note = METADATA_RE.sub(r"**\1**:: ", replace_newlines(note)) if note.startswith("#"): return "**Tags**:: " + note return note TRANSLATIONS = { "其他收藏夹": "Other Favorites", "收藏夹栏": "Favorites Bar", "Other favorites": "Other Favorites", "Favorites bar": "Favorites Bar", } def translate_name(name): return TRANSLATIONS.get(name, name) def export_bookmarks_folder(dir, folder, parent): dir.mkdir(exist_ok=True) folder["name"] = translate_name(folder["name"]) name = folder["name"] if len(folder["children"]) == 0: return with open(dir / f"{name} (Bookmarks).md", "w", newline="\n") as md_file: first_child = folder["children"][0] has_folder_note = first_child["type"] != "folder" and first_child[ "url" ].startswith(FOLDER_COMMENT_PREFIX) if has_folder_note and "#private" in first_child["name"]: print("---\npublish: false\n---", file=md_file) print(f"# {name} (Bookmarks)\n", file=md_file) print("## Metadata\n", file=md_file) if parent is not None: print( f'**Parent**:: [[{parent["name"]} (Bookmarks)|{parent["name"]}]]', file=md_file, ) print( "**Kind**:: #bookmarks-collection\n**Source**:: #from/browser", file=md_file ) print("**Generated by**:: [[ob-bookmarks]]", file=md_file) print( f'**Chrome URL**:: `edge://favorites/{"" if parent is None else "?id=" + folder["id"]}`', file=md_file, ) if has_folder_note: folder_note = format_folder_note(first_child["name"]) if folder_note != "": print(folder_note, file=md_file) print("", file=md_file) print("## Children\n", file=md_file) for child in folder["children"]: if ( child["name"] == "§ Inbox" or child["name"] == "Ω Archive" or "#private" in child["name"] ): pass elif child["type"] == "folder": child["name"] = translate_name(child["name"]) export_bookmarks_folder(dir / child["name"], child, folder) print( f'- 📁 [[{child["name"]} (Bookmarks)|{child["name"]}]]', file=md_file, ) else: url = child["url"] split_parts = replace_newlines(child["name"]).splitlines(keepends=False) child_name = split_parts[0] description = "\n".join(split_parts[1:]) anchor = child["id"] data_url_match = DATA_URL_RE.match(url) if data_url_match: data_url_syntax = "" data_url_content = url if "html" in data_url_match.group(1): data_url_syntax = "html" data_url_content = decode_html(url[data_url_match.end() :]) print( textwrap.dedent( f"""\ - {child_name} #bookmarklet ^{anchor} ```{data_url_syntax} {{}} ``` """ ).format(textwrap.indent(data_url_content, " ")), file=md_file, ) elif url.startswith(BOOKMARKLET_PREFIX): print( textwrap.dedent( """\ - {} #bookmarklet ^{} ```javascript {} ``` """ ).format( child_name, anchor, textwrap.indent( decode_javascript(url[BOOKMARKLET_PREFIX_LEN:]), " " ), ), file=md_file, ) elif not url.startswith(FOLDER_COMMENT_PREFIX): print( f"- {child_name} [{url.split('://')[1].split('/')[0]}]({url}) ^{anchor}", file=md_file, ) if description != "": if description.startswith("#"): description = description.replace("\n", "\n\n", 1) if not url.startswith(FOLDER_COMMENT_PREFIX): print("", file=md_file) print( textwrap.indent(description, " "), file=md_file, ) print("", file=md_file) tmp_dir = Path(tempfile.mkdtemp()) db_file = ( Path.home() / "AppData/Local/Microsoft/Edge/User Data/Default/Bookmarks" if os.name == "nt" else Path.home() / "Library/Application Support/Microsoft Edge/Default/Bookmarks" ) with (db_file).open() as fd: roots = json.load(fd)["roots"] roots_folder = { "name": "Roots", "type": "folder", "children": [roots["bookmark_bar"], roots["other"]], } export_bookmarks_folder(tmp_dir, roots_folder, None) for root, dirs, files in os.walk(tmp_dir): for f in files: src_file = Path(root) / f relative_path = src_file.relative_to(tmp_dir) target_file = export_dir / relative_path target_file.parent.mkdir(exist_ok=True) if not target_file.exists() or not filecmp.cmp( src_file, target_file, shallow=False ): print("NEW:", relative_path) os.replace(src_file, target_file) else: # print('SKIP:', relative_path) pass shutil.rmtree(tmp_dir) """ # vim: ft=python ``` """