My personal .emacs.d folder

gnus-harvest.el 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. ;;; gnus-harvest --- Harvest e-mail address from read/written articles
  2. ;; Copyright (C) 2011 John Wiegley
  3. ;; Author: John Wiegley <johnw@gnu.org>
  4. ;; Created: 15 Aug 2011
  5. ;; Version: 1.0
  6. ;; Keywords: gnus email
  7. ;; X-URL: https://github.com/jwiegley/gnus-harvest
  8. ;; This program is free software; you can redistribute it and/or
  9. ;; modify it under the terms of the GNU General Public License as
  10. ;; published by the Free Software Foundation; either version 2, or (at
  11. ;; your option) any later version.
  12. ;; This program is distributed in the hope that it will be useful, but
  13. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. ;; General Public License for more details.
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  18. ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  19. ;; Boston, MA 02111-1307, USA.
  20. ;;; Commentary:
  21. ;; This code requires that SQLite3 be installed. Check to see if the command
  22. ;; "sqlite3" is already available on your system.
  23. ;;
  24. ;; Once you have that, add this to your .emacs:
  25. ;;
  26. ;; (eval-after-load "gnus"
  27. ;; '(progn (require 'gnus-harvest)
  28. ;; (gnus-harvest-install)))
  29. ;;
  30. ;; If you use message-x and ido, you can get TAB completion of harvested
  31. ;; address in your To:, Cc: and From: fields by using this instead of the
  32. ;; above:
  33. ;;
  34. ;; (eval-after-load "gnus"
  35. ;; '(progn (require 'gnus-harvest)
  36. ;; (gnus-harvest-install 'message-x)))
  37. ;;
  38. (require 'gnus)
  39. (require 'mailalias)
  40. (require 'sendmail)
  41. (require 'bbdb nil t)
  42. (require 'bbdb-com nil t)
  43. (defgroup gnus-harvest nil
  44. "Harvest addresses from Gnus articles and messages"
  45. :group 'gnus)
  46. (defcustom gnus-harvest-sqlite-program (executable-find "sqlite3")
  47. "Full path to the sqlite3 program"
  48. :type 'file
  49. :group 'gnus-harvest)
  50. (defcustom gnus-harvest-db-path (expand-file-name ".addrs" gnus-home-directory)
  51. "Path to the addresses database used by Gnus harvest"
  52. :type 'file
  53. :group 'gnus-harvest)
  54. (defcustom gnus-harvest-query-limit 50
  55. "The maximum number of addresses gnus-harvest will query for"
  56. :type 'integer
  57. :group 'gnus-harvest)
  58. (defcustom gnus-harvest-move-to-subject-after-match t
  59. "After completing a single address, move to the subject field if empty"
  60. :type 'boolean
  61. :group 'gnus-harvest)
  62. (defcustom gnus-harvest-ignore-email-regexp "@public.gmane.org"
  63. "A regexps which, if an email matches, that email is ignored."
  64. :type 'string
  65. :group 'gnus-harvest)
  66. (defun gnus-harvest-sqlite-invoke (sql &optional ignore-output-p)
  67. (let ((tmp-buf (and (not ignore-output-p)
  68. (generate-new-buffer "*sqlite*"))))
  69. (if sql
  70. (call-process gnus-harvest-sqlite-program
  71. nil tmp-buf nil "-noheader" "-list"
  72. gnus-harvest-db-path sql)
  73. (call-process-region (point-min) (point-max)
  74. gnus-harvest-sqlite-program
  75. nil tmp-buf nil "-noheader" "-list"
  76. gnus-harvest-db-path))
  77. (unless ignore-output-p
  78. (with-current-buffer tmp-buf
  79. (prog1
  80. (buffer-string)
  81. (kill-buffer (current-buffer)))))))
  82. (defun gnus-harvest-create-db ()
  83. (gnus-harvest-sqlite-invoke "
  84. CREATE TABLE
  85. addrs
  86. (
  87. email TEXT(255) NOT NULL,
  88. fullname TEXT(255),
  89. last_seen INTEGER NOT NULL,
  90. weight INTEGER NOT NULL,
  91. PRIMARY KEY (email),
  92. UNIQUE (email)
  93. )
  94. " t))
  95. (defun gnus-harvest-complete-stub (stub &optional prefix-only-p)
  96. (read (concat "("
  97. (gnus-harvest-sqlite-invoke
  98. (format "
  99. SELECT
  100. '\"' ||
  101. CASE
  102. WHEN fullname IS NOT NULL
  103. THEN fullname || ' <' || email || '>'
  104. ELSE email
  105. END
  106. || '\"'
  107. FROM
  108. (
  109. SELECT
  110. email, fullname, last_seen, weight
  111. FROM
  112. addrs
  113. WHERE
  114. (email LIKE '%s%s%%' OR fullname LIKE '%s%s%%')
  115. ORDER BY
  116. weight DESC,
  117. last_seen DESC
  118. LIMIT
  119. %d
  120. )"
  121. (if prefix-only-p "" "%") stub
  122. (if prefix-only-p "" "%") stub
  123. gnus-harvest-query-limit))
  124. ")")))
  125. (defun gnus-harvest-mailalias-complete-stub (stub)
  126. (sendmail-sync-aliases)
  127. (if (eq mail-aliases t)
  128. (progn
  129. (setq mail-aliases nil)
  130. (if (file-exists-p mail-personal-alias-file)
  131. (build-mail-aliases))))
  132. (let ((entry (assoc stub mail-aliases)))
  133. (if entry
  134. (cdr entry)
  135. (delete nil
  136. (mapcar (lambda (entry)
  137. (if (string-prefix-p stub (car entry))
  138. (cdr entry)))
  139. mail-aliases)))))
  140. (defun gnus-harvest-bbdb-complete-stub (stub)
  141. (catch 'found
  142. (delete
  143. nil
  144. (apply
  145. 'append
  146. (mapcar
  147. (lambda (record)
  148. (let* ((nets (bbdb-record-net record))
  149. (name (bbdb-record-name record))
  150. (aliases
  151. (bbdb-split (bbdb-record-getprop
  152. record bbdb-define-all-aliases-field) ","))
  153. (match (catch 'matches
  154. (ignore
  155. (mapc (lambda (alias)
  156. (if (string-match stub alias)
  157. (throw 'matches t)))
  158. aliases)))))
  159. (when match
  160. (mapc
  161. (lambda (alias)
  162. (if (and (string= alias stub)
  163. (= 1 (length nets)))
  164. (throw 'found (format "%s <%s>" name (car nets)))))
  165. aliases)
  166. (mapcar (lambda (addr) (format "%s <%s>" name addr)) nets))))
  167. (let ((target (cons bbdb-define-all-aliases-field ".")))
  168. (bbdb-search (bbdb-records) nil nil nil target)))))))
  169. (defun gnus-harvest-insert-address (email fullname moment weight)
  170. (insert "INSERT OR REPLACE INTO addrs (email, ")
  171. (if fullname
  172. (insert "fullname, "))
  173. (insert "last_seen, weight) VALUES (lower('" email "'), '")
  174. (if fullname
  175. (insert fullname "', '"))
  176. (insert moment "', '")
  177. (insert (number-to-string weight) "');\n"))
  178. ;;;###autoload
  179. (defun gnus-harvest-addresses ()
  180. "Harvest and remember the addresses in the current article buffer."
  181. (let ((tmp-buf (generate-new-buffer "*gnus harvest*"))
  182. (moment (number-to-string (floor (float-time)))))
  183. (mapc
  184. (lambda (info)
  185. (if info
  186. (let ((field (car info)))
  187. (mapc (lambda (addr)
  188. (unless (string-match gnus-harvest-ignore-email-regexp
  189. (cadr addr))
  190. (with-current-buffer tmp-buf
  191. (gnus-harvest-insert-address
  192. (cadr addr) (car addr) moment
  193. (if (string= "to" field)
  194. 10
  195. 1)))))
  196. (cdr info)))))
  197. (mapcar (lambda (field)
  198. (let ((value (message-field-value field)))
  199. (and value
  200. (cons field
  201. (mail-extract-address-components value t)))))
  202. '("to" "reply-to" "from" "resent-from" "cc" "bcc")))
  203. (with-current-buffer tmp-buf
  204. (gnus-harvest-sqlite-invoke nil t)
  205. (kill-buffer (current-buffer)))))
  206. ;;;###autoload
  207. (defun gnus-harvest-find-address ()
  208. (interactive)
  209. (let* ((text-follows (not (looking-at "\\s-*$")))
  210. (stub
  211. (let ((here (point)))
  212. (backward-word 1)
  213. (prog1
  214. (buffer-substring-no-properties (point) here)
  215. (delete-region (point) here))))
  216. (aliases (if (featurep 'bbdb)
  217. (gnus-harvest-bbdb-complete-stub stub)
  218. (gnus-harvest-mailalias-complete-stub stub))))
  219. (insert
  220. (if (stringp aliases)
  221. aliases
  222. (setq aliases
  223. (delete-dups (append aliases
  224. (gnus-harvest-complete-stub stub))))
  225. (cond
  226. ((> (length aliases) 1)
  227. (ido-completing-read "Use address: " aliases nil t stub))
  228. ((= (length aliases) 1)
  229. (car aliases))
  230. (t
  231. (error "Could not find any matches for '%s'" stub)))))
  232. (if text-follows
  233. (insert ", "))
  234. (if (and gnus-harvest-move-to-subject-after-match
  235. (null (message-field-value "subject")))
  236. (message-goto-subject))))
  237. ;;;###autoload
  238. (defun gnus-harvest-install (&rest features)
  239. (unless (file-readable-p gnus-harvest-db-path)
  240. (gnus-harvest-create-db))
  241. (add-hook 'gnus-article-prepare-hook 'gnus-harvest-addresses)
  242. (add-hook 'message-send-hook 'gnus-harvest-addresses)
  243. (dolist (feature features)
  244. (cond ((eq 'message-x feature)
  245. (load "message-x")
  246. (add-to-list 'message-x-completion-alist
  247. '("\\([rR]esent-\\|[rR]eply-\\)?[tT]o:\\|[bB]?[cC][cC]:" .
  248. gnus-harvest-find-address))))))
  249. (provide 'gnus-harvest)
  250. ;;; gnus-harvest.el ends here