.emacs.d

my emacs configuration directory
Log | Files | Refs | README

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))