Assorted shell and Python scripts
at main 6.0 kB view raw
1#!/usr/bin/env python 2# 3# DESCRIPTION 4# Search and open Firefox bookmarks from Rofi 5# 6# USAGE 7# - Ensure the imported packages below are available to your Python interpreter. 8# - Must have rofi installed (obviously) 9# - Run script from command line as ./rofifox.py or set to activate with keyboard shortcut 10# 11# TODO 12# - Optimize 13# 14# CHANGELOG 15# 2022-05-05 Jeffrey Serio <hyperreal@fedoraproject.org> 16# 17# Add support for tags. 18# 19# 2022-04-17 Jeffrey Serio <hyperreal@fedoraproject.org> 20# 21# Use parts from https://gist.github.com/iafisher/d624c04940fa46c6d9afb26cb1bf222a 22# Re-use temporary file. 23# 24# 2022-04-13 Jeffrey Serio <hyperreal@fedoraproject.org> 25# 26# Refactor code; no need for query_db() function. Use a dict object for 27# lookups instead of iterating through a list. 28# 29# 30# LICENSE 31# Copyright 2022 Jeffrey Serio <hyperreal@fedoraproject.org> 32# 33# This program is free software: you can redistribute it and/or modify 34# it under the terms of the GNU General Public License as published by 35# the Free Software Foundation, either version 3 of the License, or 36# (at your option) any later version. 37# 38# This program is distributed in the hope that it will be useful, 39# but WITHOUT ANY WARRANTY; without even the implied warranty of 40# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 41# GNU General Public License for more details. 42# 43# You should have received a copy of the GNU General Public License 44# along with this program. If not, see <https://www.gnu.org/licenses/>. 45 46import configparser 47import os 48import shutil 49import sqlite3 50import subprocess 51import tempfile 52import webbrowser as wb 53from collections import namedtuple 54 55 56class Rofifox: 57 Bookmark = namedtuple("Bookmark", ["title", "url", "tags"]) 58 59 def __init__(self): 60 self.bookmarks = self.get_bookmarks() 61 self.tags = self.get_tags() 62 self.bm_dict = self.get_bm_dict() 63 64 def get_bookmarks(self) -> list: 65 firefox_path = os.path.join(os.environ["HOME"], ".mozilla/firefox/") 66 conf_path = os.path.join(firefox_path, "profiles.ini") 67 profile = configparser.RawConfigParser() 68 profile.read(conf_path) 69 prof_path = profile.get("Profile0", "Path") 70 sql_path = os.path.join(firefox_path, prof_path, "places.sqlite") 71 tmpdir = tempfile.gettempdir() 72 shutil.copy(sql_path, tmpdir) 73 74 conn = sqlite3.connect(os.path.join(tmpdir, "places.sqlite")) 75 cursor = conn.cursor() 76 cursor.execute( 77 """ 78 SELECT 79 moz_places.id, 80 moz_bookmarks.title, 81 moz_places.url 82 FROM 83 moz_bookmarks 84 LEFT JOIN 85 -- The actual URLs are stored in a separate moz_places table, which is pointed 86 -- at by the moz_bookmarks.fk field. 87 moz_places 88 ON 89 moz_bookmarks.fk = moz_places.id 90 WHERE 91 -- Type 1 is for bookmarks; type 2 is for folders and tags. 92 moz_bookmarks.type = 1 93 AND 94 moz_bookmarks.title IS NOT NULL 95 ; 96 """ 97 ) 98 99 rows = cursor.fetchall() 100 101 bookmark_list = list() 102 for place_id, title, url in rows: 103 # A tag relationship is established by row in the moz_bookmarks table with NULL 104 # title where parent is the tag ID (in moz_bookmarks) and fk is the URL. 105 cursor.execute( 106 """ 107 SELECT 108 A.title 109 FROM 110 moz_bookmarks A, moz_bookmarks B 111 WHERE 112 A.id <> B.id 113 AND 114 B.parent = A.id 115 AND 116 B.title IS NULL 117 AND 118 B.fk = ?; 119 """, 120 (place_id,), 121 ) 122 tag_names = [r[0] for r in cursor.fetchall()] 123 bookmark_list.append(self.Bookmark(title, url, tag_names)) 124 125 conn.close() 126 return bookmark_list 127 128 def get_tags(self) -> set: 129 tags = set() 130 for item in self.bookmarks: 131 if item.tags: 132 for tag in item.tags: 133 tags.add(tag) 134 return tags 135 136 def get_bm_dict(self) -> dict: 137 bm_dict = dict() 138 for item in self.bookmarks: 139 bm_dict.setdefault(item.title, []).append(item.url) 140 return bm_dict 141 142 def bookmarks_to_str(self) -> str: 143 bookmarks = [item.title for item in self.bookmarks] 144 return "\n".join(bookmarks) 145 146 def tags_to_str(self) -> str: 147 tags = [tag for tag in sorted(self.tags)] 148 return "\n".join(tags) 149 150 def tag_items_to_str(self, tag: str) -> str: 151 tag_items = list() 152 for item in self.bookmarks: 153 if tag in item.tags: 154 tag_items.append(item.title) 155 return "\n".join(tag_items) 156 157 def open_url(self, item: str): 158 url = self.bm_dict.get(item)[0] 159 if url: 160 wb.open(url, new=2) 161 162 def run_cmd(self, input: str, option: str) -> subprocess.CompletedProcess: 163 return subprocess.run( 164 ["rofi", "-dmenu", "-i", "-p", "%s" % option], 165 input=input, 166 capture_output=True, 167 text=True, 168 ) 169 170 171def run_rofifox(): 172 rofifox = Rofifox() 173 174 rofi = rofifox.run_cmd("%s\n%s" % ("Bookmarks", "Tags"), "Rofifox") 175 if rofi.stdout.strip() == "Bookmarks": 176 bookmarks = rofifox.bookmarks_to_str() 177 bm = rofifox.run_cmd(bookmarks, "Bookmarks") 178 if bm.stdout.strip(): 179 rofifox.open_url(bm.stdout.strip()) 180 elif rofi.stdout.strip() == "Tags": 181 tags = rofifox.tags_to_str() 182 tag = rofifox.run_cmd(tags, "Tags") 183 tag_items = rofifox.tag_items_to_str(tag.stdout.strip()) 184 tag_item = rofifox.run_cmd(tag_items, tag.stdout.strip()) 185 if tag_item.stdout.strip(): 186 rofifox.open_url(tag_item.stdout.strip()) 187 188 189run_rofifox()