mirror of
https://codeberg.org/hyperreal/bin
synced 2024-11-01 16:43:08 +01:00
190 lines
5.8 KiB
Python
Executable File
190 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# DESCRIPTION
|
|
# Search and open Firefox bookmarks from Rofi
|
|
#
|
|
# USAGE
|
|
# - Ensure the imported packages below are available to your Python interpreter.
|
|
# - Must have rofi installed (obviously)
|
|
# - Run script from command line as ./rofifox.py or set to activate with keyboard shortcut
|
|
#
|
|
# TODO
|
|
# - Optimize
|
|
#
|
|
# CHANGELOG
|
|
# 2022-05-05 Jeffrey Serio <hyperreal@fedoraproject.org>
|
|
#
|
|
# Add support for tags.
|
|
#
|
|
# 2022-04-17 Jeffrey Serio <hyperreal@fedoraproject.org>
|
|
#
|
|
# Use parts from https://gist.github.com/iafisher/d624c04940fa46c6d9afb26cb1bf222a
|
|
# Re-use temporary file.
|
|
#
|
|
# 2022-04-13 Jeffrey Serio <hyperreal@fedoraproject.org>
|
|
#
|
|
# Refactor code; no need for query_db() function. Use a dict object for
|
|
# lookups instead of iterating through a list.
|
|
#
|
|
#
|
|
# LICENSE
|
|
# Copyright 2022 Jeffrey Serio <hyperreal@fedoraproject.org>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import configparser
|
|
import os
|
|
import shutil
|
|
import sqlite3
|
|
import subprocess
|
|
import tempfile
|
|
import webbrowser as wb
|
|
from collections import namedtuple
|
|
|
|
|
|
class Rofifox:
|
|
Bookmark = namedtuple("Bookmark", ["title", "url", "tags"])
|
|
|
|
def __init__(self):
|
|
self.bookmarks = self.get_bookmarks()
|
|
self.tags = self.get_tags()
|
|
self.bm_dict = self.get_bm_dict()
|
|
|
|
def get_bookmarks(self) -> list:
|
|
firefox_path = os.path.join(os.environ["HOME"], ".mozilla/firefox/")
|
|
conf_path = os.path.join(firefox_path, "profiles.ini")
|
|
profile = configparser.RawConfigParser()
|
|
profile.read(conf_path)
|
|
prof_path = profile.get("Profile0", "Path")
|
|
sql_path = os.path.join(firefox_path, prof_path, "places.sqlite")
|
|
tmpdir = tempfile.gettempdir()
|
|
shutil.copy(sql_path, tmpdir)
|
|
|
|
conn = sqlite3.connect(os.path.join(tmpdir, "places.sqlite"))
|
|
cursor = conn.cursor()
|
|
cursor.execute(
|
|
"""
|
|
SELECT
|
|
moz_places.id,
|
|
moz_bookmarks.title,
|
|
moz_places.url
|
|
FROM
|
|
moz_bookmarks
|
|
LEFT JOIN
|
|
-- The actual URLs are stored in a separate moz_places table, which is pointed
|
|
-- at by the moz_bookmarks.fk field.
|
|
moz_places
|
|
ON
|
|
moz_bookmarks.fk = moz_places.id
|
|
WHERE
|
|
-- Type 1 is for bookmarks; type 2 is for folders and tags.
|
|
moz_bookmarks.type = 1
|
|
AND
|
|
moz_bookmarks.title IS NOT NULL
|
|
;
|
|
"""
|
|
)
|
|
|
|
rows = cursor.fetchall()
|
|
|
|
bookmark_list = list()
|
|
for place_id, title, url in rows:
|
|
# A tag relationship is established by row in the moz_bookmarks table with NULL
|
|
# title where parent is the tag ID (in moz_bookmarks) and fk is the URL.
|
|
cursor.execute(
|
|
"""
|
|
SELECT
|
|
A.title
|
|
FROM
|
|
moz_bookmarks A, moz_bookmarks B
|
|
WHERE
|
|
A.id <> B.id
|
|
AND
|
|
B.parent = A.id
|
|
AND
|
|
B.title IS NULL
|
|
AND
|
|
B.fk = ?;
|
|
""",
|
|
(place_id,),
|
|
)
|
|
tag_names = [r[0] for r in cursor.fetchall()]
|
|
bookmark_list.append(self.Bookmark(title, url, tag_names))
|
|
|
|
conn.close()
|
|
return bookmark_list
|
|
|
|
def get_tags(self) -> set:
|
|
tags = set()
|
|
for item in self.bookmarks:
|
|
if item.tags:
|
|
for tag in item.tags:
|
|
tags.add(tag)
|
|
return tags
|
|
|
|
def get_bm_dict(self) -> dict:
|
|
bm_dict = dict()
|
|
for item in self.bookmarks:
|
|
bm_dict.setdefault(item.title, []).append(item.url)
|
|
return bm_dict
|
|
|
|
def bookmarks_to_str(self) -> str:
|
|
bookmarks = [item.title for item in self.bookmarks]
|
|
return "\n".join(bookmarks)
|
|
|
|
def tags_to_str(self) -> str:
|
|
tags = [tag for tag in sorted(self.tags)]
|
|
return "\n".join(tags)
|
|
|
|
def tag_items_to_str(self, tag: str) -> str:
|
|
tag_items = list()
|
|
for item in self.bookmarks:
|
|
if tag in item.tags:
|
|
tag_items.append(item.title)
|
|
return "\n".join(tag_items)
|
|
|
|
def open_url(self, item: str):
|
|
url = self.bm_dict.get(item)[0]
|
|
if url:
|
|
wb.open(url, new=2)
|
|
|
|
def run_cmd(self, input: str, option: str) -> subprocess.CompletedProcess:
|
|
return subprocess.run(
|
|
["rofi", "-dmenu", "-i", "-p", "%s" % option],
|
|
input=input,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
|
|
def run_rofifox():
|
|
rofifox = Rofifox()
|
|
|
|
rofi = rofifox.run_cmd("%s\n%s" % ("Bookmarks", "Tags"), "Rofifox")
|
|
if rofi.stdout.strip() == "Bookmarks":
|
|
bookmarks = rofifox.bookmarks_to_str()
|
|
bm = rofifox.run_cmd(bookmarks, "Bookmarks")
|
|
if bm.stdout.strip():
|
|
rofifox.open_url(bm.stdout.strip())
|
|
elif rofi.stdout.strip() == "Tags":
|
|
tags = rofifox.tags_to_str()
|
|
tag = rofifox.run_cmd(tags, "Tags")
|
|
tag_items = rofifox.tag_items_to_str(tag.stdout.strip())
|
|
tag_item = rofifox.run_cmd(tag_items, tag.stdout.strip())
|
|
if tag_item.stdout.strip():
|
|
rofifox.open_url(tag_item.stdout.strip())
|
|
|
|
|
|
run_rofifox()
|