firefox.el (6687B)
1 (require 'sqlite) 2 (require 'ini) 3 4 (defvar firefox-app-name "firefox<" 5 "String that firefox window buffers will be prefixed with.") 6 7 (defvar firefox--title-name-regex 8 ".*<\\([^>]*\\)>" 9 "Regex for extracting the title of a Firefox window from its buffer name.") 10 11 (defvar firefox-extract-title-name 12 (lambda (string) 13 (let ((result '()) 14 (start 0)) 15 (while (string-match "<\\([^>]*\\)>" string start) 16 (push (match-string 1 string) result) 17 (setq start (match-end 0))) 18 (string-join (nreverse result) " - "))) 19 "Function used to the title name of a Firefox window from its buffer name.") 20 21 (defvar firefox-mozilla-folder-path 22 (expand-file-name "~/.mozilla") 23 "Location of the .mozilla folder with Firefox profiles info.") 24 25 (defun firefox--profiles-info () 26 (let* ((ini-path (expand-file-name "~/.mozilla/firefox/profiles.ini")) 27 (info (ini-decode ini-path)) 28 (raw-cands 29 (mapcar (lambda (x) (cons (cdr (assoc "Name" (cdr x))) 30 (cdr (assoc "Path" (cdr x))))) 31 info))) 32 (seq-filter 'car raw-cands))) 33 34 (defvar firefox-current-profile 35 (assoc "default-esr" (firefox--profiles-info)) 36 "The name and folder name of the selected Firefox profile.") 37 38 (defun firefox--profile-path () 39 "Get the file path of the the current Firefox profile's directory." 40 (expand-file-name (file-name-concat firefox-mozilla-folder-path 41 "firefox" 42 (cdr firefox-current-profile)))) 43 44 (defvar firefox-command 45 "env MOZ_USE_XINPUT2=1 firefox --new-window" 46 "Shell command to run to launch Firefox.") 47 48 (defvar firefox-search-format 49 "https://www.google.com/search?q=%s&udm=14" 50 "Format string to use for searching non-URL queries.") 51 52 (defvar firefox-title-postprocessor 53 (lambda (x) (car (split-string x " — "))) 54 "Transformation function to run on the title of a page.") 55 56 (defvar firefox-title-width 50 57 "Number of characters used to display the page title.") 58 59 (defvar firefox-url-width 100 60 "Number of characters used to display the page URL.") 61 62 (defvar firefox--known-tlds '("com" "org" "ai" "sh" "bike" "app") 63 "TLDs used for detecting almost-URLS. TODO this is a terrible solution!") 64 65 (defvar firefox--places-temp-filename "places.tmp.sqlite" 66 "Name for temporary copy of the places.sqlite file.") 67 68 (defvar firefox--places-data nil 69 "Cached browsing history completion candidates.") 70 71 ;; (add-to-list 'savehist-additional-variables 'firefox--places-data) 72 73 (defvar firefox-history-sql-where 74 "last_visit_date IS NOT NULL AND (visit_count > 1 or unixepoch() - last_visit < 2628000)" 75 "WHERE expression to use when querying history.") 76 77 (defvar firefox-history-sql-order-by 78 "frecency DESC" 79 "ORDER BY expression to use when querying history.") 80 81 (defun firefox--get-browsing-history () 82 "Copy the places.sqlite file and query to find browsing history sorted by frecency." 83 (let* ((profile-path (firefox--profile-path)) 84 (temp-path (file-name-concat (firefox--profile-path) firefox--places-temp-filename))) 85 (copy-file (file-name-concat (firefox--profile-path) "places.sqlite") temp-path t) 86 (sqlite-execute 87 (sqlite-open temp-path) 88 (format 89 "SELECT url, title, visit_count, frecency, 90 datetime(last_visit_date/1000000, 'unixepoch') as last_visit 91 FROM moz_places 92 WHERE %s ORDER BY %s;" 93 firefox-history-sql-where 94 firefox-history-sql-order-by)))) 95 96 (defun firefox--set-data-cache () 97 "Loads latest browsing data into `firefox--places-data`." 98 (setq firefox--places-data (firefox--get-browsing-history)) 99 (dolist (row firefox--places-data) 100 (if (cadr row) 101 (setf (cadr row) (funcall firefox-title-postprocessor (cadr row))))) 102 (setq firefox--places-data (mapcar 'firefox--make-completion firefox--places-data))) 103 104 (defun firefox-switch-profile () 105 "Switch the selected Firefox profile." 106 (interactive) 107 (let* ((cands (firefox--profiles-info)) 108 (chosen (completing-read "Profile: " cands nil nil))) 109 (setq firefox-current-profile (assoc chosen cands)) 110 (firefox--set-data-cache))) 111 112 (defun firefox--urlify (input) 113 "Coerces INPUT to a URL by either adding a https prefix or converting to a search query." 114 (url-encode-url 115 (let ((url (url-generic-parse-url input))) 116 (cond ((url-type url) input) 117 ((member (car (last (split-string input "\\."))) firefox--known-tlds) (concat "https://" input)) 118 (t (format firefox-search-format (url-hexify-string input))))))) 119 120 (defun firefox--make-completion (x) 121 "Generate a completion string for a given row of the table." 122 ;; 'invisible trick is taken from ready-player-mode. 123 ;; Really we should just use annotation-function properly but I want searchable URLs... 124 (if (cadr x) 125 (concat (truncate-string-to-width (cadr x) firefox-title-width nil ?\s "…") 126 " " 127 (truncate-string-to-width (propertize (car x) 'face 'font-lock-comment-face) 128 firefox-url-width nil ?\s "…") 129 (propertize (concat "firefox-emacs-url:" (car x)) 'invisible t)) 130 (concat (truncate-string-to-width (car x) firefox-url-width nil ?\s "…") 131 (propertize (concat "firefox-emacs-url:" (car x)) 'invisible t)))) 132 133 (defun firefox--get-url-from-completion (x) 134 "Extract the URL from the completion string." 135 (firefox--urlify (car (last (split-string x "firefox-emacs-url:"))))) 136 137 (defun firefox-new-tab () 138 "Open a new firefox tab, prompting with history." 139 (interactive) 140 (when (eq firefox--places-data nil) 141 (firefox--set-data-cache)) 142 (let ((vertico-preselect 'prompt) 143 (vertico-sort-function nil) 144 (cands firefox--places-data)) 145 (call-process-shell-command 146 (format "%s -P %s \"%s\"" 147 firefox-command 148 (car firefox-current-profile) 149 (firefox--get-url-from-completion 150 (completing-read "New tab: " cands nil nil))) 151 nil 0))) 152 153 ;; Personal (less package-y) configuration 154 155 (defvar time-wasting-websites-keywords 156 '("youtube" 157 "lobste.rs" 158 "news.ycombinator.com" 159 "kittensgame.com" 160 "hntoplinks.com" 161 "hn.algolia.com") 162 "Websites I don't want suggested to me.") 163 164 (defvar direct-websites-keywords 165 '("discord.com" 166 "discordapp.com" 167 "google.com/search") 168 "Websites that shouldn't be opened via firefox.el") 169 170 (setq firefox-history-sql-where 171 (seq-reduce 'concat 172 (mapcar (lambda (x) (format " AND INSTR(url, \"%s\") == 0" x)) 173 (append time-wasting-websites-keywords direct-websites-keywords)) 174 firefox-history-sql-where))