使用 Helm 搜索 Chrome 历史记录

Chrome 把历史记录保存在一个 SQLite 数据库中,用户也可以访问(Chrome 开着时,需要先拷贝一份):

~ $ cp '/Users/xcy/Library/Application Support/Google/Chrome/Profile 1/History' .
~ $ sqlite3 History 'select url, title from urls limit 2'
http://emacs-china.org/|Emacs China
https://emacs-china.org/|关于“Chrome 历史”的搜索结果 - Emacs China
~ $

我写了个 Helm 界面,用来搜索 Chrome 历史记录:

(defvar helm-chrome-history-db
  "~/Library/Application Support/Google/Chrome/Profile 1/History"
  "Chrome History SQLite Database.")

(defun helm-chrome-history-init ()
  ;; 缓存到 " *helm candidates:Chrome History*",和缓存到变量相比,省去了插入的
  ;; 时间。我的有 600 万字符,应该会一直占用 6 M 内存
  (unless (helm-candidate-buffer)
    (helm-init-candidates-in-buffer 'global
      (with-temp-buffer
        (let ((db "/tmp/History"))
          (copy-file helm-chrome-history-db db t)
          (unless (zerop (call-process "sqlite3" nil t nil db "select url, title from urls"))
            (error "sqlite3 failed: %s" (buffer-string)))
          (buffer-string))))))

(defvar helm-chrome-history-source
  (helm-build-in-buffer-source "Chrome History"
    :init #'helm-chrome-history-init
    :action (helm-make-actions
             "Browse URL"
             (lambda (x) (browse-url (car (split-string x "|"))))
             "EWW URL"
             (lambda (x) (eww (car (split-string x "|")))))))

(defun helm-chrome-history ()
  (interactive)
  (helm :sources helm-chrome-history-source
        :buffer "*Helm Chrome History*"
        :full-frame t))
8 个赞
(require 'cl-lib)
(require 'subr-x)
(require 'pcase)

(defvar cm/counsel-ff-db-path
  (car (file-expand-wildcards "~/.mozilla/firefox/*/places.sqlite")))

(defvar cm/counsel-ff-bookmarks-cache nil)

(defun cm/counsel-ensure-ff-db ()
  (let* ((name (make-temp-name "ffdb"))
         (path (expand-file-name name temporary-file-directory)))
    (condition-case e
        (copy-file cm/counsel-ff-db-path path)
      (error "Failed to ensure firefox database: %s" e))
    path))

(defun cm/counsel-prepare-bookmarks-candidates (&optional force-update?)
  (counsel-require-program "sqlite3")
  (or
   (if force-update? nil cm/counsel-ff-bookmarks-cache)
   (let* ((db-path (cm/counsel-ensure-ff-db)))
     (with-temp-buffer
       (let ((errno (call-process
                     "sqlite3" nil (current-buffer) nil "--ascii" db-path
                     (string-join '("SELECT bm.title, p.url"
                                    "FROM moz_bookmarks AS bm INNER JOIN moz_places AS p"
                                    "WHERE bm.fk = p.id;") " ")))
             splitted)
         (if (= errno 0)
             (setq splitted
                   (mapcar
                    (lambda (it) (split-string it "\037")) ; ^_
                    (split-string
                     (string-trim
                      (buffer-substring-no-properties (point-min) (point-max)))
                     "\036"             ; ^^
                     )))
           (error "SQLite exited with error code %d" errno))
         (setq cm/counsel-ff-bookmarks-cache
               (cl-remove "" (mapcar (pcase-lambda (`(,title ,url))
                                       (when (null url) (setq url title))
                                       (list (format "%s %s"
                                                     title
                                                     (propertize url 'face 'link))
                                             url))
                                     splitted)
                          :test #'equal :key #'cadr)))))))

(defun cm/counsel-firefox-bookmarks (&optional force-update?)
  (interactive "P")
  (ivy-read "Firefox Bookmarks: " (cm/counsel-prepare-bookmarks-candidates
                                   force-update?)
            :history 'cm/counsel-firefox-bookmarks-history
            :action (lambda (it) (browse-url (cadr it)))
            :caller 'cm/counsel-firefox-bookmarks
            :require-match t))

用ivy查询火狐的历史记录。

火狐数据库里的书签,url和title居然不是放在一起的。title在moz_bookmarks表里,这个表的fk列标记了对应URL在moz_places表里的id列。

3 个赞

花时间重构了一下,现在历史记录和书签都可以查询了。

依赖emacsql,主要是用了他的sql语句生成器

3 个赞

Lisp 民科警告 :smiley:

2 个赞

自 El Captain,Safari 把历史存在 ~/Library/Safari 下的 SQLite 数据库,不过在 Mojave 现在这个路径被做了特殊处理不能访问和复制。比较有趣的是通过 Finder 反而可以移动出历史文件,终端下所有对这个路径的操作都被禁止了。

看见listary 了,暴露了你是windows用户的事实 :joy:

我觉得listary比everything用户体验好多了,可惜不能和emacs很好的配合,另外现在不收钱各种阉割功能基本没法用 :cry:

还不来用自由的浏览器? :sunglasses:

1 个赞

Mac下还是习惯用launchbar搜

Chrome 有个很像 helm 的插件,也是做类似的事

2 个赞

已加入melpa豪华午餐

欢迎搜索counsel-ffdata 体验使用

可以在 README 上加个 MELPA 徽章,别人看到就知道了。

1 个赞

有没有可能支持查询打开的tab页面呢?

使用主题上的脚本,放在config.el ,启动emacs,出现以下问题:

–debug-init 日志:

Debugger entered--Lisp error: (void-function helm-build-in-buffer-source)
  (helm-build-in-buffer-source "Chrome History" :init #'helm-chrome-history-init :action (helm-make-actions "Browse URL" #'(lambda (x) (browse-url (car (split-string x "|")))) "EWW URL" #'(lambda (x) (eww (car (split-string x "|"))))))
  (defvar helm-chrome-history-source (helm-build-in-buffer-source "Chrome History" :init #'helm-chrome-history-init :action (helm-make-actions "Browse URL" #'(lambda (x) (browse-url (car (split-string x "|")))) "EWW URL" #'(lambda (x) (eww (car (split-string x "|")))))))
  eval-buffer(#<buffer  *load*-591807> nil "/Users/boyer/hsg/hugo/iDoom.d/modules/boyer/eww/co..." nil t)  ; Reading at buffer position 4653
  load-with-code-conversion("/Users/boyer/hsg/hugo/iDoom.d/modules/boyer/eww/co..." "/Users/boyer/hsg/hugo/iDoom.d/modules/boyer/eww/co..." t t)
  load("/Users/boyer/hsg/hugo/iDoom.d/modules/boyer/eww/co..." t nomessage)
  (let (file-name-handler-alist) (load (expand-file-name file (plist-get plist :path)) t 'nomessage))
  (condition-case e (let (file-name-handler-alist) (load (expand-file-name file (plist-get plist :path)) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name file (plist-get plist :path)) (plist-get plist :path))))
  (let ((doom--current-module module) (doom--current-flags (plist-get plist :flags)) (inhibit-redisplay t)) (condition-case e (let (file-name-handler-alist) (load (expand-file-name file (plist-get plist :path)) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name file (plist-get plist :path)) (plist-get plist :path)))))
  (closure ((file . "config") t) (module plist) (let ((doom--current-module module) (doom--current-flags (plist-get plist :flags)) (inhibit-redisplay t)) (condition-case e (let (file-name-handler-alist) (load (expand-file-name file (plist-get plist :path)) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name file (plist-get plist :path)) (plist-get plist :path))))))((:boyer . eww) (:flags nil :path "/Users/boyer/hsg/hugo/iDoom.d/modules/boyer/eww/"))
  maphash((closure ((file . "config") t) (module plist) (let ((doom--current-module module) (doom--current-flags (plist-get plist :flags)) (inhibit-redisplay t)) (condition-case e (let (file-name-handler-alist) (load (expand-file-name file (plist-get plist :path)) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name file (plist-get plist :path)) (plist-get plist :path)))))) #<hash-table equal 85/97 0x1ff7e50571e5>)
  (if no-config-p nil (maphash (doom-module-loader doom-module-config-file) doom-modules) (doom-run-hooks 'doom-init-modules-hook) (condition-case e (let (file-name-handler-alist) (load (expand-file-name "config" doom-private-dir) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name "config" doom-private-dir) doom-private-dir))) (if custom-file (progn (load custom-file 'noerror (not doom-debug-mode)))))
  (progn (if doom-debug-p (progn (let ((inhibit-message (active-minibuffer-window))) (message #("DOOM Initializing user config" 0 5 (face font-lock-comment-face)))))) (maphash (doom-module-loader doom-module-init-file) doom-modules) (doom-run-hooks 'doom-before-init-modules-hook) (if no-config-p nil (maphash (doom-module-loader doom-module-config-file) doom-modules) (doom-run-hooks 'doom-init-modules-hook) (condition-case e (let (file-name-handler-alist) (load (expand-file-name "config" doom-private-dir) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name "config" doom-private-dir) doom-private-dir))) (if custom-file (progn (load custom-file 'noerror (not doom-debug-mode))))))
  (if init-p (progn (if doom-debug-p (progn (let ((inhibit-message (active-minibuffer-window))) (message #("DOOM Initializing user config" 0 5 (face font-lock-comment-face)))))) (maphash (doom-module-loader doom-module-init-file) doom-modules) (doom-run-hooks 'doom-before-init-modules-hook) (if no-config-p nil (maphash (doom-module-loader doom-module-config-file) doom-modules) (doom-run-hooks 'doom-init-modules-hook) (condition-case e (let (file-name-handler-alist) (load (expand-file-name "config" doom-private-dir) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name "config" doom-private-dir) doom-private-dir))) (if custom-file (progn (load custom-file 'noerror (not doom-debug-mode)))))) nil)
  (let* ((init-p (and t (condition-case e (let (file-name-handler-alist) (load (expand-file-name doom-module-init-file doom-private-dir) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name doom-module-init-file doom-private-dir) doom-private-dir)))))) (if init-p (progn (if doom-debug-p (progn (let ((inhibit-message ...)) (message #("DOOM Initializing user config" 0 5 ...))))) (maphash (doom-module-loader doom-module-init-file) doom-modules) (doom-run-hooks 'doom-before-init-modules-hook) (if no-config-p nil (maphash (doom-module-loader doom-module-config-file) doom-modules) (doom-run-hooks 'doom-init-modules-hook) (condition-case e (let (file-name-handler-alist) (load (expand-file-name "config" doom-private-dir) t 'nomessage)) ((debug doom-error) (signal (car e) (cdr e))) ((debug error) (doom--handle-load-error e (expand-file-name "config" doom-private-dir) doom-private-dir))) (if custom-file (progn (load custom-file 'noerror (not doom-debug-mode)))))) nil))
  (progn (setq doom-init-modules-p t) (if no-config-p nil (if doom-debug-p (progn (let ((inhibit-message (active-minibuffer-window))) (message #("DOOM Initializing core modules" 0 5 (face font-lock-comment-face)))))) (doom-initialize-core-modules)) (let* ((init-p (and t (condition-case e (let (file-name-handler-alist) (load ... t ...)) ((debug doom-error) (signal ... ...)) ((debug error) (doom--handle-load-error e ... doom-private-dir)))))) (if init-p (progn (if doom-debug-p (progn (let (...) (message ...)))) (maphash (doom-module-loader doom-module-init-file) doom-modules) (doom-run-hooks 'doom-before-init-modules-hook) (if no-config-p nil (maphash (doom-module-loader doom-module-config-file) doom-modules) (doom-run-hooks 'doom-init-modules-hook) (condition-case e (let (file-name-handler-alist) (load ... t ...)) ((debug doom-error) (signal ... ...)) ((debug error) (doom--handle-load-error e ... doom-private-dir))) (if custom-file (progn (load custom-file ... ...))))) nil)))
  (if (or force-p (not doom-init-modules-p)) (progn (setq doom-init-modules-p t) (if no-config-p nil (if doom-debug-p (progn (let ((inhibit-message ...)) (message #("DOOM Initializing core modules" 0 5 ...))))) (doom-initialize-core-modules)) (let* ((init-p (and t (condition-case e (let ... ...) (... ...) (... ...))))) (if init-p (progn (if doom-debug-p (progn (let ... ...))) (maphash (doom-module-loader doom-module-init-file) doom-modules) (doom-run-hooks 'doom-before-init-modules-hook) (if no-config-p nil (maphash (doom-module-loader doom-module-config-file) doom-modules) (doom-run-hooks 'doom-init-modules-hook) (condition-case e (let ... ...) (... ...) (... ...)) (if custom-file (progn ...)))) nil))))
  doom-initialize-modules()
  eval-buffer(#<buffer  *load*> nil "/Users/boyer/.emacs.d/init.el" nil t)  ; Reading at buffer position 2803
  load-with-code-conversion("/Users/boyer/.emacs.d/init.el" "/Users/boyer/.emacs.d/init.el" t t)
  load("/Users/boyer/.emacs.d/init" noerror nomessage)
  #f(compiled-function (filename-function &optional alternate-filename-function load-defaults) "Load a user init-file.\nFILENAME-FUNCTION is called with no arguments and should return\nthe name of the init-file to load.  If this file cannot be\nloaded, and ALTERNATE-FILENAME-FUNCTION is non-nil, then it is\ncalled with no arguments and should return the name of an\nalternate init-file to load.  If LOAD-DEFAULTS is non-nil, then\nload default.el after the init-file, unless `inhibit-default-init'\nis non-nil.\n\nThis function sets `user-init-file' to the name of the loaded\ninit-file, or to a default value if loading is not possible." #<bytecode 0x175e3ee983650d4c>)(#f(compiled-function () #<bytecode -0x1493560aa6ec0a7>) #f(compiled-function () #<bytecode -0x1f3c6eaddc0e6db5>) t)
  apply(#f(compiled-function (filename-function &optional alternate-filename-function load-defaults) "Load a user init-file.\nFILENAME-FUNCTION is called with no arguments and should return\nthe name of the init-file to load.  If this file cannot be\nloaded, and ALTERNATE-FILENAME-FUNCTION is non-nil, then it is\ncalled with no arguments and should return the name of an\nalternate init-file to load.  If LOAD-DEFAULTS is non-nil, then\nload default.el after the init-file, unless `inhibit-default-init'\nis non-nil.\n\nThis function sets `user-init-file' to the name of the loaded\ninit-file, or to a default value if loading is not possible." #<bytecode 0x175e3ee983650d4c>) (#f(compiled-function () #<bytecode -0x1493560aa6ec0a7>) #f(compiled-function () #<bytecode -0x1f3c6eaddc0e6db5>) t))
  startup--load-user-init-file(#f(compiled-function () #<bytecode -0x1493560aa6ec0a7>) #f(compiled-function () #<bytecode -0x1f3c6eaddc0e6db5>) t)
  command-line()
  normal-top-level()

helm 没来得及定义,想办法提前加在下

谢谢,发现doom配置下的init.el 没有开启helm。

历史记录包括网页内容吗?

不包括网页内容

要是能用Emacs管理浏览器tab就好了, 可以用org-mode分组, 写笔记, 关闭, 同步, 搜索, 备份 etc

这个可以的,用 native host messaging 插件,然后用 dbus 传消息。