2020-05-03 09:47:05 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
# helper script for query-bookmarks.sh
|
|
|
|
# currently supports only Firefox
|
|
|
|
# useful links:
|
2021-04-18 15:06:47 +00:00
|
|
|
# <http://kb.mozillazine.org/Profiles.ini_file>
|
|
|
|
# <https://stackoverflow.com/a/740183/12005228>
|
|
|
|
# <https://wiki.mozilla.org/Places:BookmarksComments>
|
2020-05-03 09:47:05 +00:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
from pathlib import Path
|
|
|
|
from configparser import ConfigParser
|
|
|
|
import tempfile
|
|
|
|
import shutil
|
|
|
|
import sqlite3
|
2021-04-18 15:06:47 +00:00
|
|
|
from typing import Optional, Tuple, Generator
|
2020-05-03 09:47:05 +00:00
|
|
|
|
2020-11-22 21:33:44 +00:00
|
|
|
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
|
2021-04-02 18:50:17 +00:00
|
|
|
import common_script_utils
|
2020-05-03 09:47:05 +00:00
|
|
|
|
|
|
|
|
2020-10-04 13:15:27 +00:00
|
|
|
if sys.platform == "darwin":
|
2021-04-18 15:06:47 +00:00
|
|
|
firefox_home: Path = Path.home() / "Library" / "Application Support" / "Firefox"
|
2020-05-03 09:47:05 +00:00
|
|
|
elif os.name == "posix":
|
2021-04-18 15:06:47 +00:00
|
|
|
firefox_home: Path = Path.home() / ".mozilla" / "firefox"
|
2020-05-03 09:47:05 +00:00
|
|
|
else:
|
2020-10-04 13:15:27 +00:00
|
|
|
common_script_utils.platform_not_supported_error()
|
2020-05-03 09:47:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
profiles_config = ConfigParser(interpolation=None)
|
|
|
|
profiles_config.read(firefox_home / "profiles.ini")
|
|
|
|
|
2021-04-18 15:06:47 +00:00
|
|
|
installs_sections: list[str] = [
|
|
|
|
s for s in profiles_config.sections() if s.startswith("Install")
|
|
|
|
]
|
2020-05-03 09:47:05 +00:00
|
|
|
if not installs_sections:
|
|
|
|
raise Exception("no Firefox installations detected!")
|
|
|
|
if len(installs_sections) > 1:
|
|
|
|
raise Exception("multiple Firefox installations are not supported!")
|
2021-04-18 15:06:47 +00:00
|
|
|
profile_dir: Path = firefox_home / profiles_config.get(installs_sections[0], "Default")
|
2020-05-03 09:47:05 +00:00
|
|
|
|
2020-10-12 15:33:10 +00:00
|
|
|
# should places.sqlite be used instead?
|
2021-04-18 15:06:47 +00:00
|
|
|
db_path: Path = profile_dir / "weave" / "bookmarks.sqlite"
|
2020-05-03 09:47:05 +00:00
|
|
|
if not db_path.is_file():
|
|
|
|
raise Exception("'{}' is not a file".format(db_path))
|
|
|
|
|
|
|
|
|
|
|
|
# Firefox holds a lock over the database file, so I can't connect to it even
|
|
|
|
# in the readonly mode: https://stackoverflow.com/a/7857866/12005228
|
|
|
|
# as a workaround I copy the file
|
|
|
|
db_copy_fd, db_copy_path = tempfile.mkstemp(prefix="bookmarks.", suffix=".sqlite")
|
|
|
|
os.close(db_copy_fd)
|
|
|
|
|
2021-04-18 15:06:47 +00:00
|
|
|
chooser_entries: list[Tuple[str, str, Optional[str]]] = []
|
2020-05-03 09:47:05 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
shutil.copyfile(db_path, db_copy_path)
|
|
|
|
db = sqlite3.connect(db_copy_path)
|
|
|
|
|
2021-04-18 15:06:47 +00:00
|
|
|
urls: dict[int, str] = {}
|
|
|
|
url_id: int
|
|
|
|
url: str
|
2020-10-12 15:33:10 +00:00
|
|
|
for url_id, url in db.execute("SELECT id, url FROM urls"):
|
|
|
|
urls[url_id] = url
|
2020-05-03 09:47:05 +00:00
|
|
|
|
2021-04-18 15:06:47 +00:00
|
|
|
folders: dict[str, Tuple[Optional[str], str]] = {}
|
|
|
|
folder_id: str
|
|
|
|
parent_folder_id: str
|
|
|
|
folder_title: str
|
2020-10-12 15:33:10 +00:00
|
|
|
for folder_id, parent_folder_id, folder_title in db.execute(
|
|
|
|
"SELECT guid, parentGuid, title FROM items WHERE kind = 3 AND validity AND NOT isDeleted"
|
2020-05-03 09:47:05 +00:00
|
|
|
):
|
2020-10-12 15:33:10 +00:00
|
|
|
folders[folder_id] = (
|
|
|
|
parent_folder_id if parent_folder_id != folder_id else None,
|
|
|
|
folder_title,
|
|
|
|
)
|
|
|
|
|
2021-04-18 15:06:47 +00:00
|
|
|
url_title: str
|
|
|
|
url_id: int
|
|
|
|
url_keyword: str
|
|
|
|
parent_folder_id: str
|
2020-10-12 15:33:10 +00:00
|
|
|
for url_title, url_id, url_keyword, parent_folder_id in db.execute(
|
|
|
|
"SELECT title, urlId, keyword, parentGuid FROM items WHERE kind = 1 AND validity AND NOT isDeleted"
|
|
|
|
):
|
|
|
|
url = urls[url_id]
|
|
|
|
|
2021-04-18 15:06:47 +00:00
|
|
|
folder_path = list[str]()
|
|
|
|
parent_folder_id_2: Optional[str] = parent_folder_id
|
|
|
|
while parent_folder_id_2 is not None:
|
|
|
|
folder = folders.get(parent_folder_id_2, None)
|
2020-10-12 15:33:10 +00:00
|
|
|
if folder is None:
|
|
|
|
# broken folder structure?
|
|
|
|
folder_path.clear()
|
|
|
|
break
|
2021-04-18 15:06:47 +00:00
|
|
|
parent_folder_id_2, folder_title = folder
|
2020-10-12 15:33:10 +00:00
|
|
|
if folder_title is not None:
|
2021-04-18 15:06:47 +00:00
|
|
|
folder_path.append(folder_title)
|
2020-10-12 15:33:10 +00:00
|
|
|
|
|
|
|
folder_path_str = (
|
2021-04-18 15:06:47 +00:00
|
|
|
("/" + "/".join(reversed(folder_path))) if len(folder_path) > 0 else None
|
2020-10-12 15:33:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
chooser_entries.append((url_title, url, folder_path_str))
|
|
|
|
if url_keyword is not None:
|
|
|
|
chooser_entries.append((url_keyword, url, folder_path_str))
|
2020-05-03 09:47:05 +00:00
|
|
|
|
|
|
|
finally:
|
|
|
|
os.remove(db_copy_path)
|
|
|
|
|
|
|
|
|
2021-04-18 15:06:47 +00:00
|
|
|
def chooser_entries_iter() -> Generator[str, None, None]:
|
2020-10-12 15:33:10 +00:00
|
|
|
for title, url, folder_path_str in chooser_entries:
|
|
|
|
entry_items = [title, url]
|
|
|
|
if folder_path_str is not None:
|
|
|
|
entry_items.append(folder_path_str)
|
|
|
|
entry = " \u2014\u2014 ".join(entry_items)
|
|
|
|
yield entry
|
|
|
|
|
|
|
|
|
2020-10-04 13:15:27 +00:00
|
|
|
chosen_index = common_script_utils.run_chooser(
|
2020-10-12 15:33:10 +00:00
|
|
|
chooser_entries_iter(), prompt="bookmark"
|
2020-05-03 09:47:05 +00:00
|
|
|
)
|
|
|
|
|
2021-04-11 21:53:51 +00:00
|
|
|
if chosen_index >= 0:
|
|
|
|
_title, url, _folder_path_str = chooser_entries[chosen_index]
|
|
|
|
print(url)
|
|
|
|
|
|
|
|
common_script_utils.set_clipboard(url)
|
|
|
|
common_script_utils.send_notification(
|
|
|
|
os.path.basename(__file__), "bookmark URL copied to clipboard!", url
|
|
|
|
)
|