├── .gitignore ├── 142D5735-C1EB-467D-B8F0-DB7B4F50D49F.png ├── Firefox Bookmarks.alfredworkflow ├── LICENSE ├── README.md ├── bookmarks.py ├── icon.png └── info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | # Eclipse 38 | .settings 39 | 40 | # Mac 41 | .DS_Store 42 | -------------------------------------------------------------------------------- /142D5735-C1EB-467D-B8F0-DB7B4F50D49F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikipore/alfred-firefoxbookmarks/d560ceadf64c1aa9934484763391140e7bc24de8/142D5735-C1EB-467D-B8F0-DB7B4F50D49F.png -------------------------------------------------------------------------------- /Firefox Bookmarks.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikipore/alfred-firefoxbookmarks/d560ceadf64c1aa9934484763391140e7bc24de8/Firefox Bookmarks.alfredworkflow -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Dr. Jan Müller 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | alfred-firefoxbookmarks 2 | ======================= 3 | 4 | This is an Alfred 3 workflow which gives you access to your Firefox bookmarks. In fact, in aims to mimick the awesome bar by scanning not only bookmarks, but also the input history, cf. the `places.db` [scheme](http://people.mozilla.org/~dietrich/places-erd.png). 5 | 6 | The `alfred.py` module is a collection of helper functions which ease the communication with Alfred. Everyone is invited to use and improve it for other workflows. 7 | 8 | Installation 9 | ------------ 10 | Download and double-click [Firefox Bookmarks.alfredworkflow](https://github.com/nikipore/alfred-firefoxbookmarks/raw/master/Firefox%20Bookmarks.alfredworkflow). 11 | 12 | Usage 13 | ----- 14 | Just type `ff `. 15 | 16 | Hit `return` on a URL to have Firefox open it. Hit `Cmd + return` on a URL to copy it to the clipboard. 17 | 18 | Note 19 | ---- 20 | This workflow looks for the Firefox `places.db` according to your setting of `PROFILE` in the `ff`script filter (it's a glob expression, so the wildcards `*` and `?` are allowed). By default, it points to your default profile. 21 | -------------------------------------------------------------------------------- /bookmarks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import glob 3 | import os 4 | import re 5 | import sqlite3 6 | import time 7 | from shutil import copyfile 8 | 9 | import alfred 10 | 11 | _MAX_RESULTS = 20 12 | _CACHE_EXPIRY = 24 * 60 * 60 # in seconds 13 | _CACHE = alfred.work(True) 14 | 15 | def combine(operator, iterable): 16 | return u'(%s)' % (u' %s ' % operator).join(iterable) 17 | 18 | def icon(favicons_db, url_hash): 19 | if not url_hash: 20 | return 21 | 22 | result = favicons_db.execute(u"""\ 23 | select moz_icons.id, moz_icons.data from moz_icons 24 | inner join moz_icons_to_pages on moz_icons.id = moz_icons_to_pages.icon_id 25 | inner join moz_pages_w_icons on moz_icons_to_pages.page_id = moz_pages_w_icons.id 26 | where moz_pages_w_icons.page_url_hash = '%s' 27 | order by moz_icons.id asc limit 1""" % url_hash).fetchone() 28 | if not result: 29 | return 30 | (id, data) = result 31 | if not data: 32 | return 33 | icon = os.path.join(_CACHE, 'icon-%d.png' % id) 34 | if (not os.path.exists(icon)) or ((time.time() - os.path.getmtime(icon)) > _CACHE_EXPIRY): 35 | open(icon, 'wb').write(data) 36 | return icon 37 | 38 | def places(profile): 39 | profile = (d for d in glob.glob(os.path.expanduser(profile)) if os.path.isdir(d)).next() 40 | orig = os.path.join(profile, 'places.sqlite') 41 | new = os.path.join(profile, 'places-alfredcopy.sqlite') 42 | copyfile(orig, new) 43 | return new 44 | 45 | def favicons(profile): 46 | profile = (d for d in glob.glob(os.path.expanduser(profile)) if os.path.isdir(d)).next() 47 | orig = os.path.join(profile, 'favicons.sqlite') 48 | new = os.path.join(profile, 'favicons-alfredcopy.sqlite') 49 | copyfile(orig, new) 50 | return new 51 | 52 | def regexp(pattern, item): 53 | return item and bool(re.match(pattern, item, flags=re.IGNORECASE)) 54 | 55 | def results(places_db, favicons_db, query): 56 | places_db.create_function("regexp", 2, regexp) 57 | found = set() 58 | for result in places_db.execute(sql(query)): 59 | if result in found: 60 | continue 61 | found.add(result) 62 | (uid, title, url, url_hash) = result 63 | yield alfred.Item({u'uid': alfred.uid(uid), u'arg': url}, title, url, icon(favicons_db, url_hash)) 64 | 65 | def sql(query): 66 | keywords = u"""\ 67 | select distinct moz_places.id, moz_bookmarks.title, moz_places.url, moz_places.url_hash from moz_places 68 | inner join moz_bookmarks on moz_places.id = moz_bookmarks.fk 69 | inner join moz_keywords on moz_bookmarks.keyword_id = moz_keywords.id 70 | where %s""" % where(query, [u'moz_keywords.keyword']) 71 | 72 | bookmarks = u"""\ 73 | select distinct moz_places.id, moz_bookmarks.title, moz_places.url, moz_places.url_hash from moz_places 74 | inner join moz_bookmarks on moz_places.id = moz_bookmarks.fk 75 | where %s""" % where(query, [u'moz_bookmarks.title', u'moz_places.url']) 76 | 77 | input_history = u"""\ 78 | select distinct moz_places.id, moz_places.title, moz_places.url, moz_places.url_hash from moz_places 79 | inner join moz_inputhistory on moz_places.id = moz_inputhistory.place_id 80 | where %s""" % where(query, [u'moz_inputhistory.input', u'moz_places.title', u'moz_places.url']) 81 | 82 | browsing_history = u"""\ 83 | select distinct moz_places.id, moz_places.title, moz_places.url, moz_places.url_hash from moz_places 84 | inner join moz_historyvisits on moz_places.id = moz_historyvisits.place_id 85 | where %s and moz_places.title notnull""" % where(query, [u'moz_places.title', u'moz_places.url']) 86 | 87 | joinTemplate = u"""\ 88 | inner join %(table)s on moz_places.id = %(table)s.%(field)s 89 | """ 90 | return u'\nunion\n'.join([keywords, bookmarks, input_history, browsing_history]) 91 | 92 | def where(query, fields): 93 | return combine(u'or', ('%s regexp "%s"' % (field, '.*%s' % '.*'.join(re.escape(c) for c in query.split(' '))) for field in fields)) 94 | 95 | (profile, query) = alfred.args() 96 | places_db = sqlite3.connect(places(profile)) 97 | favicons_db = sqlite3.connect(favicons(profile)) 98 | alfred.write(alfred.xml(results(places_db, favicons_db, query), maxresults=_MAX_RESULTS)) 99 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikipore/alfred-firefoxbookmarks/d560ceadf64c1aa9934484763391140e7bc24de8/icon.png -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | nikipore.firefoxbookmarks 7 | category 8 | Internet 9 | connections 10 | 11 | 142D5735-C1EB-467D-B8F0-DB7B4F50D49F 12 | 13 | 14 | destinationuid 15 | D854110A-B838-4E53-9E19-6927E920FFCB 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | vitoclose 21 | 22 | 23 | 24 | destinationuid 25 | 698CD0E9-09B5-424E-8420-38E6384848C5 26 | modifiers 27 | 0 28 | modifiersubtext 29 | 30 | vitoclose 31 | 32 | 33 | 34 | 5DBEA531-326D-4036-8FDE-9353EFE432FB 35 | 36 | 37 | destinationuid 38 | D854110A-B838-4E53-9E19-6927E920FFCB 39 | modifiers 40 | 1048576 41 | modifiersubtext 42 | Copy URL to clipboard 43 | vitoclose 44 | 45 | 46 | 47 | destinationuid 48 | 10BC39C1-3727-4AFE-8837-C01E80B640E8 49 | modifiers 50 | 0 51 | modifiersubtext 52 | 53 | vitoclose 54 | 55 | 56 | 57 | 58 | createdby 59 | Jan Müller 60 | description 61 | Search Mozilla Firefox Bookmarks 62 | disabled 63 | 64 | name 65 | Firefox Bookmarks 66 | objects 67 | 68 | 69 | config 70 | 71 | concurrently 72 | 73 | escaping 74 | 63 75 | script 76 | echo "{query}" | pbcopy 77 | scriptargtype 78 | 0 79 | scriptfile 80 | 81 | type 82 | 0 83 | 84 | type 85 | alfred.workflow.action.script 86 | uid 87 | D854110A-B838-4E53-9E19-6927E920FFCB 88 | version 89 | 2 90 | 91 | 92 | config 93 | 94 | alfredfiltersresults 95 | 96 | alfredfiltersresultsmatchmode 97 | 0 98 | argumenttreatemptyqueryasnil 99 | 100 | argumenttrimmode 101 | 0 102 | argumenttype 103 | 0 104 | escaping 105 | 63 106 | keyword 107 | ff 108 | queuedelaycustom 109 | 3 110 | queuedelayimmediatelyinitially 111 | 112 | queuedelaymode 113 | 1 114 | queuemode 115 | 1 116 | runningsubtext 117 | Scanning… 118 | script 119 | PROFILE="~/Library/Application Support/Firefox/Profiles/*.default*" 120 | python bookmarks.py "${PROFILE}" "{query}" 121 | scriptargtype 122 | 0 123 | scriptfile 124 | 125 | subtext 126 | Search Firefox places 127 | title 128 | Firefox Awesome Bar 129 | type 130 | 0 131 | withspace 132 | 133 | 134 | type 135 | alfred.workflow.input.scriptfilter 136 | uid 137 | 5DBEA531-326D-4036-8FDE-9353EFE432FB 138 | version 139 | 3 140 | 141 | 142 | config 143 | 144 | concurrently 145 | 146 | escaping 147 | 63 148 | script 149 | open -a "/Applications/Firefox.app" "{query}" 150 | scriptargtype 151 | 0 152 | scriptfile 153 | 154 | type 155 | 0 156 | 157 | type 158 | alfred.workflow.action.script 159 | uid 160 | 10BC39C1-3727-4AFE-8837-C01E80B640E8 161 | version 162 | 2 163 | 164 | 165 | config 166 | 167 | alfredfiltersresults 168 | 169 | alfredfiltersresultsmatchmode 170 | 0 171 | argumenttreatemptyqueryasnil 172 | 173 | argumenttrimmode 174 | 0 175 | argumenttype 176 | 0 177 | escaping 178 | 63 179 | keyword 180 | ffd 181 | queuedelaycustom 182 | 3 183 | queuedelayimmediatelyinitially 184 | 185 | queuedelaymode 186 | 1 187 | queuemode 188 | 1 189 | runningsubtext 190 | Scanning … 191 | script 192 | PROFILE="~/Library/Application Support/Firefox/Profiles/*.dev-edition-default*" 193 | python bookmarks.py "${PROFILE}" "{query}" 194 | scriptargtype 195 | 0 196 | scriptfile 197 | 198 | subtext 199 | Search Firefox Bookmarks 200 | title 201 | FirefoxDeveloperEdition Bookmarks 202 | type 203 | 0 204 | withspace 205 | 206 | 207 | type 208 | alfred.workflow.input.scriptfilter 209 | uid 210 | 142D5735-C1EB-467D-B8F0-DB7B4F50D49F 211 | version 212 | 3 213 | 214 | 215 | config 216 | 217 | concurrently 218 | 219 | escaping 220 | 63 221 | script 222 | open -a "/Applications/Firefox Developer Edition.app" "{query}" 223 | scriptargtype 224 | 0 225 | scriptfile 226 | 227 | type 228 | 0 229 | 230 | type 231 | alfred.workflow.action.script 232 | uid 233 | 698CD0E9-09B5-424E-8420-38E6384848C5 234 | version 235 | 2 236 | 237 | 238 | readme 239 | 240 | uidata 241 | 242 | 10BC39C1-3727-4AFE-8837-C01E80B640E8 243 | 244 | xpos 245 | 500 246 | ypos 247 | 130 248 | 249 | 142D5735-C1EB-467D-B8F0-DB7B4F50D49F 250 | 251 | xpos 252 | 300 253 | ypos 254 | 130 255 | 256 | 5DBEA531-326D-4036-8FDE-9353EFE432FB 257 | 258 | xpos 259 | 300 260 | ypos 261 | 10 262 | 263 | 698CD0E9-09B5-424E-8420-38E6384848C5 264 | 265 | xpos 266 | 500 267 | ypos 268 | 250 269 | 270 | D854110A-B838-4E53-9E19-6927E920FFCB 271 | 272 | xpos 273 | 500 274 | ypos 275 | 10 276 | 277 | 278 | variablesdontexport 279 | 280 | version 281 | 282 | webaddress 283 | https://github.com/nikipore 284 | 285 | 286 | --------------------------------------------------------------------------------