al-gnus.el 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. ;;; al-gnus.el --- Additional functionality for Gnus -*- lexical-binding: t -*-
  2. ;; Copyright © 2013–2017 Alex Kost
  3. ;; This program is free software; you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;;
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Code:
  16. (require 'gnus-sum)
  17. (require 'gnus-art)
  18. (require 'al-buffer)
  19. (require 'al-misc) ; for al/xor
  20. (defun al/gnus-buffer-names ()
  21. "Return a list of names of live gnus buffer."
  22. (mapcar #'buffer-name (gnus-buffers)))
  23. (defun al/gnus-buffer-p ()
  24. "Return nil if current buffer is not a gnus buffer."
  25. (memq (current-buffer) (gnus-buffers)))
  26. ;;;###autoload
  27. (defun al/gnus-switch-to-group-buffer ()
  28. "Switch to gnus group buffer if it exists, otherwise start gnus."
  29. (interactive)
  30. (if (and (fboundp 'gnus-alive-p)
  31. (gnus-alive-p))
  32. (switch-to-buffer gnus-group-buffer)
  33. (gnus)))
  34. ;;;###autoload
  35. (defun al/gnus-switch-buffer ()
  36. "Switch to a gnus buffer."
  37. (interactive)
  38. (let ((buffers (al/gnus-buffer-names)))
  39. (if buffers
  40. (al/switch-buffer "Gnus buffer: "
  41. :buffers buffers)
  42. (user-error "No Gnus buffers"))))
  43. (defvar al/gnus-unbuttonized-mime-types-original
  44. gnus-unbuttonized-mime-types)
  45. ;;;###autoload
  46. (defun al/gnus-summary-toggle-display-buttonized ()
  47. "Toggle the buttonizing of the article buffer."
  48. (interactive)
  49. (setq gnus-unbuttonized-mime-types
  50. (if (setq gnus-inhibit-mime-unbuttonizing
  51. (not gnus-inhibit-mime-unbuttonizing))
  52. al/gnus-unbuttonized-mime-types-original
  53. '(".*/.*")))
  54. (gnus-summary-show-article))
  55. ;;; Switching gnus and non-gnus window configurations
  56. ;; Idea from <http://www.emacswiki.org/emacs/SwitchToGnus>.
  57. (defvar al/gnus-win-config nil
  58. "Window configuration with gnus buffers.")
  59. (defvar al/non-gnus-win-config nil
  60. "Window configuration with non-gnus buffers.")
  61. (defun al/gnus-win-config-variable (&optional revert)
  62. "Return a name of variable with window configuration.
  63. Return `al/gnus-win-config' if current buffer is a gnus buffer,
  64. return `al/non-gnus-win-config' otherwise.
  65. If REVERT is non-nil, do vice versa (return the other variable)."
  66. (if (al/xor (al/gnus-buffer-p) revert)
  67. 'al/gnus-win-config
  68. 'al/non-gnus-win-config))
  69. (defun al/gnus-save-win-config ()
  70. "Save current gnus or non-gnus window configuration."
  71. (interactive)
  72. (set (al/gnus-win-config-variable)
  73. (current-window-configuration)))
  74. ;;;###autoload
  75. (defun al/gnus-switch-win-config ()
  76. "Switch window configuration between gnus and non-gnus buffers.
  77. Start Gnus if needed."
  78. (interactive)
  79. (al/gnus-save-win-config)
  80. (if (gnus-alive-p)
  81. (set-window-configuration
  82. (symbol-value (al/gnus-win-config-variable 'other)))
  83. (gnus)
  84. (al/gnus-save-win-config)))
  85. ;;; Finding URLs in summary and article buffers
  86. (defvar al/gnus-link-re "\\<link\\>"
  87. "Regexp matching a link name.
  88. Used in `al/gnus-summary-find-link-url'.")
  89. (defvar al/gnus-mm-url-re "\\.mp3$"
  90. "Regexp for multimedia links.
  91. Used in `al/gnus-summary-find-mm-url'.")
  92. (defun al/widget-next ()
  93. "Move point to the next field or button.
  94. After the last widget, move point to the end of buffer."
  95. ;; The code is a rework of `widget-move'.
  96. (let ((old (widget-tabable-at))
  97. (move (if widget-use-overlay-change
  98. (lambda () (goto-char (next-overlay-change (point))))
  99. (lambda () (forward-char 1)))))
  100. (funcall move)
  101. (while (let ((new (widget-tabable-at)))
  102. (and (or (null new) (eq new old))
  103. (not (eobp))))
  104. (funcall move))))
  105. (defun al/gnus-article-find-url (predicate)
  106. "Return the first widget URL matching PREDICATE.
  107. Return nil if no matches found."
  108. (save-excursion
  109. (article-goto-body)
  110. (backward-char)
  111. (al/gnus-article-find-url-1 predicate)))
  112. (defun al/gnus-article-find-url-1 (predicate)
  113. (al/widget-next)
  114. (unless (eobp)
  115. (let* ((point (point))
  116. ;; Text property with URL depends on `mm-text-html-renderer'.
  117. (url (or (get-text-property point 'gnus-string)
  118. (get-text-property point 'shr-url))))
  119. (if (and url (funcall predicate url))
  120. url
  121. (al/gnus-article-find-url-1 predicate)))))
  122. (defun al/gnus-article-find-url-by-re (regexp &optional group)
  123. "Return the first widget URL matching REGEXP.
  124. If GROUP is non-nil, it should be a number specifying a
  125. parenthesized expression from REGEXP that should be returned.
  126. Return nil if no matches found."
  127. (let ((url (al/gnus-article-find-url
  128. (lambda (url) (string-match-p regexp url)))))
  129. (if (null group)
  130. url
  131. (string-match regexp url)
  132. (match-string group url))))
  133. (defun al/gnus-article-find-url-by-name (regexp)
  134. "Return the first widget URL with widget name matching REGEXP.
  135. Return nil if no matches found."
  136. (al/gnus-article-find-url
  137. (lambda (_) (looking-at regexp))))
  138. (defmacro al/gnus-summary-eval-in-article (&rest body)
  139. "Display an article buffer and evaluate BODY there."
  140. ;; The code is taken from `gnus-summary-next-page'.
  141. `(let ((article (gnus-summary-article-number)))
  142. (or article
  143. (error "No article to select"))
  144. (gnus-configure-windows 'article)
  145. ;; Selected subject is different from the current article's subject.
  146. (if (or (null gnus-current-article)
  147. (null gnus-article-current)
  148. (/= article (cdr gnus-article-current))
  149. (not (equal (car gnus-article-current) gnus-newsgroup-name)))
  150. (gnus-summary-display-article article))
  151. (gnus-eval-in-buffer-window gnus-article-buffer
  152. ,@body)))
  153. (defun al/gnus-summary-find-url-by-re (regexp &optional group)
  154. "Return the first URL from the gnus article matching REGEXP.
  155. See `al/gnus-article-find-url-by-re' for details."
  156. (al/gnus-summary-eval-in-article
  157. (al/gnus-article-find-url-by-re regexp group)))
  158. (defun al/gnus-summary-find-url-by-name (regexp)
  159. "Return the first URL from the gnus article with name matching REGEXP.
  160. See `al/gnus-article-find-url-by-name' for details."
  161. (al/gnus-summary-eval-in-article
  162. (al/gnus-article-find-url-by-name regexp)))
  163. (defun al/gnus-summary-find-link-url ()
  164. "Return the first \"link\" URL from the gnus article.
  165. Matching url is defined by `al/gnus-link-re'."
  166. (al/gnus-summary-find-url-by-name al/gnus-link-re))
  167. (defun al/gnus-summary-find-mm-url ()
  168. "Return the first multimedia URL from the gnus article.
  169. Matching url is defined by `al/gnus-mm-url-re'."
  170. (al/gnus-summary-find-url-by-re al/gnus-mm-url-re))
  171. ;;;###autoload
  172. (defun al/gnus-summary-browse-link-url ()
  173. "Browse the first \"link\" URL from the gnus article."
  174. (interactive)
  175. (browse-url (al/gnus-summary-find-link-url)))
  176. (declare-function emms-add-url "emms-source-file" (url))
  177. (declare-function emms-play-url "emms-source-file" (url))
  178. ;;;###autoload
  179. (defun al/gnus-summary-emms-add-url ()
  180. "Add the first multimedia URL from gnus article to EMMS playlist."
  181. (interactive)
  182. (emms-add-url (al/gnus-summary-find-mm-url)))
  183. ;;;###autoload
  184. (defun al/gnus-summary-emms-play-url ()
  185. "Play the first multimedia URL from gnus article with EMMS."
  186. (interactive)
  187. (emms-play-url (al/gnus-summary-find-mm-url)))
  188. ;;; Convert Atom to RSS
  189. ;; The code for `al/convert-atom-to-rss' is taken from a defadvice from
  190. ;; <http://www.emacswiki.org/emacs/GnusRss>. The original
  191. ;; "atom2rss.xsl" is taken from <http://atom.geekhood.net/>.
  192. ;; Github private feed (with info from <https://github.com>) is an Atom,
  193. ;; so we need to convert it to use with gnus. There is a little
  194. ;; problem: "atom2rss.xsl" tries to insert a comment with self link to
  195. ;; the resulting rss, but a github private link may contain "--" in it
  196. ;; (for me this link is:
  197. ;; "https://github.com/alezost.private.atom?token=a_lot_of_numbers_and_letters--more_numers_and_letters")
  198. ;; and as it is not allowed in xml comments, xsltproc throws an error.
  199. ;; To fix that, I commented the line:
  200. ;;
  201. ;; <x:template match="atom:feed/atom:link[@rel='self']"> ...
  202. ;;
  203. ;; in "atom2rss.xsl" and now I can check github feed in gnus. Hooray!
  204. (defvar al/atom2rss-file
  205. (expand-file-name "atom2rss.xsl" user-emacs-directory)
  206. "Path to \"atom2rss.xsl\" file for converting Atom to RSS.")
  207. (defun al/convert-atom-to-rss (&rest _)
  208. "Convert Atom to RSS (if needed) by calling xsltproc.
  209. This function is intendend to be used as an 'after' advice for
  210. `mm-url-insert', i.e.:
  211. (advice-add 'mm-url-insert :after #'al/convert-atom-to-rss)"
  212. (when (re-search-forward "xmlns=\"http://www.w3.org/.*/Atom\""
  213. nil t)
  214. (goto-char (point-min))
  215. (message "Converting Atom to RSS... ")
  216. (call-process-region (point-min) (point-max)
  217. "xsltproc"
  218. t t nil
  219. al/atom2rss-file "-")
  220. (goto-char (point-min))
  221. (message "Converting Atom to RSS... done")))
  222. ;;; Agent mode-line string
  223. (defvar al/gnus-plugged " ↔"
  224. "Mode-line string indicating that Gnus is plugged.
  225. Used by `al/change-mode-string' advice for
  226. `gnus-agent-make-mode-line-string'.")
  227. (defvar al/gnus-unplugged " ↮"
  228. "Mode-line string indicating that Gnus is unplugged.
  229. Used by `al/change-mode-string' advice for
  230. `gnus-agent-make-mode-line-string'.")
  231. (defun al/gnus-plugged-status (string)
  232. "Return `al/gnus-plugged' or `al/gnus-unplugged' depending on STRING."
  233. (cond
  234. ((string= string " Plugged") al/gnus-plugged)
  235. ((string= string " Unplugged") al/gnus-unplugged)
  236. (t " unknown")))
  237. (defun al/gnus-agent-mode-line-string (fun string &rest args)
  238. "Modify \"Plugged\"/\"Unplugged\" mode-line string.
  239. This function is intendend to be used as an 'around' advice for
  240. `gnus-agent-make-mode-line-string', i.e.:
  241. (advice-add 'gnus-agent-make-mode-line-string
  242. :around #'al/gnus-agent-mode-line-string)"
  243. (apply fun (al/gnus-plugged-status string) args))
  244. ;;; Miscellaneous
  245. ;;;###autoload
  246. (defun al/gnus-group-next-unread-group (n)
  247. "Go to next N'th unread newsgroup.
  248. This is the same as `gnus-group-next-unread-group' except it
  249. doesn't honor `gnus-group-goto-unread'."
  250. (interactive "p")
  251. (let ((gnus-group-goto-unread t))
  252. (gnus-group-next-unread-group n)))
  253. ;;;###autoload
  254. (defun al/gnus-group-prev-unread-group (n)
  255. "Go to previous N'th unread newsgroup.
  256. This is the same as `gnus-group-prev-unread-group' except it
  257. doesn't honor `gnus-group-goto-unread'."
  258. (interactive "p")
  259. (al/gnus-group-next-unread-group (- n)))
  260. (provide 'al-gnus)
  261. ;;; al-gnus.el ends here