123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- ;;; gnus-harvest --- Harvest e-mail address from read/written articles
- ;; Copyright (C) 2011 John Wiegley
- ;; Author: John Wiegley <johnw@gnu.org>
- ;; Created: 15 Aug 2011
- ;; Version: 1.0
- ;; Keywords: gnus email
- ;; X-URL: https://github.com/jwiegley/gnus-harvest
- ;; This program is free software; you can redistribute it and/or
- ;; modify it under the terms of the GNU General Public License as
- ;; published by the Free Software Foundation; either version 2, or (at
- ;; your option) any later version.
- ;; This program is distributed in the hope that it will be useful, but
- ;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;; General Public License for more details.
- ;; You should have received a copy of the GNU General Public License
- ;; along with GNU Emacs; see the file COPYING. If not, write to the
- ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- ;; Boston, MA 02111-1307, USA.
- ;;; Commentary:
- ;; This code requires that SQLite3 be installed. Check to see if the command
- ;; "sqlite3" is already available on your system.
- ;;
- ;; Once you have that, add this to your .emacs:
- ;;
- ;; (eval-after-load "gnus"
- ;; '(progn (require 'gnus-harvest)
- ;; (gnus-harvest-install)))
- ;;
- ;; If you use message-x and ido, you can get TAB completion of harvested
- ;; address in your To:, Cc: and From: fields by using this instead of the
- ;; above:
- ;;
- ;; (eval-after-load "gnus"
- ;; '(progn (require 'gnus-harvest)
- ;; (gnus-harvest-install 'message-x)))
- ;;
- (require 'gnus)
- (require 'mailalias)
- (require 'sendmail)
- (require 'bbdb nil t)
- (require 'bbdb-com nil t)
- (defgroup gnus-harvest nil
- "Harvest addresses from Gnus articles and messages"
- :group 'gnus)
- (defcustom gnus-harvest-sqlite-program (executable-find "sqlite3")
- "Full path to the sqlite3 program"
- :type 'file
- :group 'gnus-harvest)
- (defcustom gnus-harvest-db-path (expand-file-name ".addrs" gnus-home-directory)
- "Path to the addresses database used by Gnus harvest"
- :type 'file
- :group 'gnus-harvest)
- (defcustom gnus-harvest-query-limit 50
- "The maximum number of addresses gnus-harvest will query for"
- :type 'integer
- :group 'gnus-harvest)
- (defcustom gnus-harvest-move-to-subject-after-match t
- "After completing a single address, move to the subject field if empty"
- :type 'boolean
- :group 'gnus-harvest)
- (defcustom gnus-harvest-ignore-email-regexp "@public.gmane.org"
- "A regexps which, if an email matches, that email is ignored."
- :type 'string
- :group 'gnus-harvest)
- (defun gnus-harvest-sqlite-invoke (sql &optional ignore-output-p)
- (let ((tmp-buf (and (not ignore-output-p)
- (generate-new-buffer "*sqlite*"))))
- (if sql
- (call-process gnus-harvest-sqlite-program
- nil tmp-buf nil "-noheader" "-list"
- gnus-harvest-db-path sql)
- (call-process-region (point-min) (point-max)
- gnus-harvest-sqlite-program
- nil tmp-buf nil "-noheader" "-list"
- gnus-harvest-db-path))
- (unless ignore-output-p
- (with-current-buffer tmp-buf
- (prog1
- (buffer-string)
- (kill-buffer (current-buffer)))))))
- (defun gnus-harvest-create-db ()
- (gnus-harvest-sqlite-invoke "
- CREATE TABLE
- addrs
- (
- email TEXT(255) NOT NULL,
- fullname TEXT(255),
- last_seen INTEGER NOT NULL,
- weight INTEGER NOT NULL,
- PRIMARY KEY (email),
- UNIQUE (email)
- )
- " t))
- (defun gnus-harvest-complete-stub (stub &optional prefix-only-p)
- (read (concat "("
- (gnus-harvest-sqlite-invoke
- (format "
- SELECT
- '\"' ||
- CASE
- WHEN fullname IS NOT NULL
- THEN fullname || ' <' || email || '>'
- ELSE email
- END
- || '\"'
- FROM
- (
- SELECT
- email, fullname, last_seen, weight
- FROM
- addrs
- WHERE
- (email LIKE '%s%s%%' OR fullname LIKE '%s%s%%')
- ORDER BY
- weight DESC,
- last_seen DESC
- LIMIT
- %d
- )"
- (if prefix-only-p "" "%") stub
- (if prefix-only-p "" "%") stub
- gnus-harvest-query-limit))
- ")")))
- (defun gnus-harvest-mailalias-complete-stub (stub)
- (sendmail-sync-aliases)
- (if (eq mail-aliases t)
- (progn
- (setq mail-aliases nil)
- (if (file-exists-p mail-personal-alias-file)
- (build-mail-aliases))))
- (let ((entry (assoc stub mail-aliases)))
- (if entry
- (cdr entry)
- (delete nil
- (mapcar (lambda (entry)
- (if (string-prefix-p stub (car entry))
- (cdr entry)))
- mail-aliases)))))
- (defun gnus-harvest-bbdb-complete-stub (stub)
- (catch 'found
- (delete
- nil
- (apply
- 'append
- (mapcar
- (lambda (record)
- (let* ((nets (bbdb-record-net record))
- (name (bbdb-record-name record))
- (aliases
- (bbdb-split (bbdb-record-getprop
- record bbdb-define-all-aliases-field) ","))
- (match (catch 'matches
- (ignore
- (mapc (lambda (alias)
- (if (string-match stub alias)
- (throw 'matches t)))
- aliases)))))
- (when match
- (mapc
- (lambda (alias)
- (if (and (string= alias stub)
- (= 1 (length nets)))
- (throw 'found (format "%s <%s>" name (car nets)))))
- aliases)
- (mapcar (lambda (addr) (format "%s <%s>" name addr)) nets))))
- (let ((target (cons bbdb-define-all-aliases-field ".")))
- (bbdb-search (bbdb-records) nil nil nil target)))))))
- (defun gnus-harvest-insert-address (email fullname moment weight)
- (insert "INSERT OR REPLACE INTO addrs (email, ")
- (if fullname
- (insert "fullname, "))
- (insert "last_seen, weight) VALUES (lower('" email "'), '")
- (if fullname
- (insert fullname "', '"))
- (insert moment "', '")
- (insert (number-to-string weight) "');\n"))
- ;;;###autoload
- (defun gnus-harvest-addresses ()
- "Harvest and remember the addresses in the current article buffer."
- (let ((tmp-buf (generate-new-buffer "*gnus harvest*"))
- (moment (number-to-string (floor (float-time)))))
- (mapc
- (lambda (info)
- (if info
- (let ((field (car info)))
- (mapc (lambda (addr)
- (unless (string-match gnus-harvest-ignore-email-regexp
- (cadr addr))
- (with-current-buffer tmp-buf
- (gnus-harvest-insert-address
- (cadr addr) (car addr) moment
- (if (string= "to" field)
- 10
- 1)))))
- (cdr info)))))
- (mapcar (lambda (field)
- (let ((value (message-field-value field)))
- (and value
- (cons field
- (mail-extract-address-components value t)))))
- '("to" "reply-to" "from" "resent-from" "cc" "bcc")))
- (with-current-buffer tmp-buf
- (gnus-harvest-sqlite-invoke nil t)
- (kill-buffer (current-buffer)))))
- ;;;###autoload
- (defun gnus-harvest-find-address ()
- (interactive)
- (let* ((text-follows (not (looking-at "\\s-*$")))
- (stub
- (let ((here (point)))
- (backward-word 1)
- (prog1
- (buffer-substring-no-properties (point) here)
- (delete-region (point) here))))
- (aliases (if (featurep 'bbdb)
- (gnus-harvest-bbdb-complete-stub stub)
- (gnus-harvest-mailalias-complete-stub stub))))
- (insert
- (if (stringp aliases)
- aliases
- (setq aliases
- (delete-dups (append aliases
- (gnus-harvest-complete-stub stub))))
- (cond
- ((> (length aliases) 1)
- (ido-completing-read "Use address: " aliases nil t stub))
- ((= (length aliases) 1)
- (car aliases))
- (t
- (error "Could not find any matches for '%s'" stub)))))
- (if text-follows
- (insert ", "))
- (if (and gnus-harvest-move-to-subject-after-match
- (null (message-field-value "subject")))
- (message-goto-subject))))
- ;;;###autoload
- (defun gnus-harvest-install (&rest features)
- (unless (file-readable-p gnus-harvest-db-path)
- (gnus-harvest-create-db))
- (add-hook 'gnus-article-prepare-hook 'gnus-harvest-addresses)
- (add-hook 'message-send-hook 'gnus-harvest-addresses)
- (dolist (feature features)
- (cond ((eq 'message-x feature)
- (load "message-x")
- (add-to-list 'message-x-completion-alist
- '("\\([rR]esent-\\|[rR]eply-\\)?[tT]o:\\|[bB]?[cC][cC]:" .
- gnus-harvest-find-address))))))
- (provide 'gnus-harvest)
- ;;; gnus-harvest.el ends here
|