* gnus.el: Fix copyright statements.
[gnus] / lisp / gnus-msg.el
1 ;;; gnus-msg.el --- mail and post interface for Gnus
2 ;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
6 ;;      Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; Keywords: news
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25
26 ;;; Commentary:
27
28 ;;; Code:
29
30 (eval-when-compile (require 'cl))
31
32 (require 'gnus)
33 (require 'gnus-ems)
34 (require 'message)
35 (require 'gnus-art)
36
37 (defcustom gnus-post-method 'current
38   "*Preferred method for posting USENET news.
39
40 If this variable is `current', Gnus will use the \"current\" select
41 method when posting.  If it is nil (which is the default), Gnus will
42 use the native select method when posting.
43
44 This method will not be used in mail groups and the like, only in
45 \"real\" newsgroups.
46
47 If not nil nor `native', the value must be a valid method as discussed
48 in the documentation of `gnus-select-method'.  It can also be a list of
49 methods.  If that is the case, the user will be queried for what select
50 method to use when posting."
51   :group 'gnus-group-foreign
52   :type `(choice (const nil)
53                  (const current)
54                  (const native)
55                  (sexp :tag "Methods" ,gnus-select-method)))
56
57 (defvar gnus-outgoing-message-group nil
58   "*All outgoing messages will be put in this group.
59 If you want to store all your outgoing mail and articles in the group
60 \"nnml:archive\", you set this variable to that value.  This variable
61 can also be a list of group names.
62
63 If you want to have greater control over what group to put each
64 message in, you can set this variable to a function that checks the
65 current newsgroup name and then returns a suitable group name (or list
66 of names).")
67
68 (defvar gnus-mailing-list-groups nil
69   "*Regexp matching groups that are really mailing lists.
70 This is useful when you're reading a mailing list that has been
71 gatewayed to a newsgroup, and you want to followup to an article in
72 the group.")
73
74 (defvar gnus-add-to-list nil
75   "*If non-nil, add a `to-list' parameter automatically.")
76
77 (defvar gnus-crosspost-complaint
78   "Hi,
79
80 You posted the article below with the following Newsgroups header:
81
82 Newsgroups: %s
83
84 The %s group, at least, was an inappropriate recipient
85 of this message.  Please trim your Newsgroups header to exclude this
86 group before posting in the future.
87
88 Thank you.
89
90 "
91   "Format string to be inserted when complaining about crossposts.
92 The first %s will be replaced by the Newsgroups header;
93 the second with the current group name.")
94
95 (defvar gnus-message-setup-hook nil
96   "Hook run after setting up a message buffer.")
97
98 (defvar gnus-bug-create-help-buffer t
99   "*Should we create the *Gnus Help Bug* buffer?")
100
101 (defvar gnus-posting-styles nil
102   "*Alist of styles to use when posting.")
103
104 (defcustom gnus-group-posting-charset-alist
105   '(("^\\(no\\|fr\\|dk\\)\\.[^,]*\\(,[ \t\n]*\\(no\\|fr\\|dk\\)\\.[^,]*\\)*$" iso-8859-1 (iso-8859-1))
106     (message-this-is-mail nil nil)
107     (message-this-is-news nil t))
108   "Alist of regexps and permitted unencoded charsets for posting.
109 Each element of the alist has the form (TEST HEADER BODY-LIST), where
110 TEST is either a regular expression matching the newsgroup header or a
111 variable to query,
112 HEADER is the charset which may be left unencoded in the header (nil
113 means encode all charsets),
114 BODY-LIST is a list of charsets which may be encoded using 8bit
115 content-transfer encoding in the body, or one of the special values
116 nil (always encode using quoted-printable) or t (always use 8bit).
117
118 Note that any value other tha nil for HEADER infringes some RFCs, so
119 use this option with care."
120   :type '(repeat (list :tag "Permitted unencoded charsets"
121                   (choice :tag "Where"
122                    (regexp :tag "Group")
123                    (const :tag "Mail message" :value message-this-is-mail)
124                    (const :tag "News article" :value message-this-is-news))
125                   (choice :tag "Header"
126                    (const :tag "None" nil)
127                    (symbol :tag "Charset"))
128                   (choice :tag "Body"
129                           (const :tag "Any" :value t)
130                           (const :tag "None" :value nil)
131                           (repeat :tag "Charsets"
132                                   (symbol :tag "Charset")))))
133   :group 'gnus-charset)
134
135 ;;; Internal variables.
136
137 (defvar gnus-inhibit-posting-styles nil
138   "Inhibit the use of posting styles.")
139
140 (defvar gnus-message-buffer "*Mail Gnus*")
141 (defvar gnus-article-copy nil)
142 (defvar gnus-last-posting-server nil)
143 (defvar gnus-message-group-art nil)
144
145 (defconst gnus-bug-message
146   "Sending a bug report to the Gnus Towers.
147 ========================================
148
149 The buffer below is a mail buffer.  When you press `C-c C-c', it will
150 be sent to the Gnus Bug Exterminators.
151
152 The thing near the bottom of the buffer is how the environment
153 settings will be included in the mail.  Please do not delete that.
154 They will tell the Bug People what your environment is, so that it
155 will be easier to locate the bugs.
156
157 If you have found a bug that makes Emacs go \"beep\", set
158 debug-on-error to t (`M-x set-variable RET debug-on-error RET t RET')
159 and include the backtrace in your bug report.
160
161 Please describe the bug in annoying, painstaking detail.
162
163 Thank you for your help in stamping out bugs.
164 ")
165
166 (eval-and-compile
167   (autoload 'gnus-uu-post-news "gnus-uu" nil t)
168   (autoload 'news-setup "rnewspost")
169   (autoload 'news-reply-mode "rnewspost")
170   (autoload 'rmail-dont-reply-to "mail-utils")
171   (autoload 'rmail-output "rmailout"))
172
173 \f
174 ;;;
175 ;;; Gnus Posting Functions
176 ;;;
177
178 (gnus-define-keys (gnus-summary-send-map "S" gnus-summary-mode-map)
179   "p" gnus-summary-post-news
180   "f" gnus-summary-followup
181   "F" gnus-summary-followup-with-original
182   "c" gnus-summary-cancel-article
183   "s" gnus-summary-supersede-article
184   "r" gnus-summary-reply
185   "y" gnus-summary-yank-message
186   "R" gnus-summary-reply-with-original
187   "w" gnus-summary-wide-reply
188   "W" gnus-summary-wide-reply-with-original
189   "n" gnus-summary-followup-to-mail
190   "N" gnus-summary-followup-to-mail-with-original
191   "m" gnus-summary-mail-other-window
192   "u" gnus-uu-post-news
193   "\M-c" gnus-summary-mail-crosspost-complaint
194   "om" gnus-summary-mail-forward
195   "op" gnus-summary-post-forward
196   "Om" gnus-uu-digest-mail-forward
197   "Op" gnus-uu-digest-post-forward)
198
199 (gnus-define-keys (gnus-send-bounce-map "D" gnus-summary-send-map)
200   "b" gnus-summary-resend-bounced-mail
201   ;; "c" gnus-summary-send-draft
202   "r" gnus-summary-resend-message)
203
204 ;;; Internal functions.
205
206 (defvar gnus-article-reply nil)
207 (defmacro gnus-setup-message (config &rest forms)
208   (let ((winconf (make-symbol "gnus-setup-message-winconf"))
209         (buffer (make-symbol "gnus-setup-message-buffer"))
210         (article (make-symbol "gnus-setup-message-article"))
211         (group (make-symbol "gnus-setup-message-group")))
212     `(let ((,winconf (current-window-configuration))
213            (,buffer (buffer-name (current-buffer)))
214            (,article (and gnus-article-reply (gnus-summary-article-number)))
215            (,group gnus-newsgroup-name)
216            (message-header-setup-hook
217             (copy-sequence message-header-setup-hook))
218            (message-mode-hook (copy-sequence message-mode-hook)))
219        (add-hook 'message-header-setup-hook 'gnus-inews-insert-gcc)
220        (add-hook 'message-header-setup-hook 'gnus-inews-insert-archive-gcc)
221        (add-hook 'message-mode-hook 'gnus-configure-posting-styles)
222        (unwind-protect
223            (progn
224              ,@forms)
225          (gnus-inews-add-send-actions ,winconf ,buffer ,article)
226          (setq gnus-message-buffer (current-buffer))
227          (set (make-local-variable 'gnus-message-group-art)
228               (cons ,group ,article))
229          (set (make-local-variable 'gnus-newsgroup-name) ,group)
230          (gnus-run-hooks 'gnus-message-setup-hook))
231        (gnus-add-buffer)
232        (gnus-configure-windows ,config t)
233        (set-buffer-modified-p nil))))
234
235 (defun gnus-setup-posting-charset (group)
236   (let ((alist gnus-group-posting-charset-alist)
237         (group (or group ""))
238         elem)
239     (when group
240       (catch 'found
241         (while (setq elem (pop alist))
242           (when (or (and (stringp (car elem))
243                          (string-match (car elem) group))
244                     (and (gnus-functionp (car elem))
245                          (funcall (car elem) group))
246                     (and (symbolp (car elem))
247                          (symbol-value (car elem))))
248             (throw 'found (cons (cadr elem) (caddr elem)))))))))
249
250 (defun gnus-inews-add-send-actions (winconf buffer article)
251   (make-local-hook 'message-sent-hook)
252   (add-hook 'message-sent-hook 'gnus-inews-do-gcc nil t)
253   (setq message-post-method
254         `(lambda (arg)
255            (gnus-post-method arg ,gnus-newsgroup-name)))
256   (setq message-newsreader (setq message-mailer (gnus-extended-version)))
257   (message-add-action
258    `(set-window-configuration ,winconf) 'exit 'postpone 'kill)
259   (message-add-action
260    `(when (gnus-buffer-exists-p ,buffer)
261       (save-excursion
262         (set-buffer ,buffer)
263         ,(when article
264            `(gnus-summary-mark-article-as-replied ,article))))
265    'send))
266
267 (put 'gnus-setup-message 'lisp-indent-function 1)
268 (put 'gnus-setup-message 'edebug-form-spec '(form body))
269
270 ;;; Post news commands of Gnus group mode and summary mode
271
272 (defun gnus-group-mail (&optional arg)
273   "Start composing a mail.
274 If ARG, use the group under the point to find a posting style.
275 If ARG is 1, prompt for a group name to find the posting style."
276   (interactive "P")
277   ;; We can't `let' gnus-newsgroup-name here, since that leads
278   ;; to local variables leaking.
279   (let ((group gnus-newsgroup-name)
280         (buffer (current-buffer)))
281     (unwind-protect
282         (progn
283           (setq gnus-newsgroup-name
284                 (if arg
285                     (if (= 1 (prefix-numeric-value arg))
286                         (completing-read "Use posting style of group: "
287                                          gnus-active-hashtb nil
288                                          (gnus-read-active-file-p))
289                       (gnus-group-group-name))
290                   ""))
291           (gnus-setup-message 'message (message-mail)))
292       (save-excursion
293         (set-buffer buffer)
294         (setq gnus-newsgroup-name group)))))
295
296 (defun gnus-group-post-news (&optional arg)
297   "Start composing a news message.
298 If ARG, post to the group under point.
299 If ARG is 1, prompt for a group name."
300   (interactive "P")
301   ;; Bind this variable here to make message mode hooks work ok.
302   (let ((gnus-newsgroup-name
303          (if arg
304              (if (= 1 (prefix-numeric-value arg))
305                  (completing-read "Newsgroup: " gnus-active-hashtb nil
306                                   (gnus-read-active-file-p))
307                (gnus-group-group-name))
308            "")))
309     (gnus-post-news 'post gnus-newsgroup-name)))
310
311 (defun gnus-summary-post-news ()
312   "Start composing a news message."
313   (interactive)
314   (gnus-post-news 'post gnus-newsgroup-name))
315
316 (defun gnus-summary-followup (yank &optional force-news)
317   "Compose a followup to an article.
318 If prefix argument YANK is non-nil, original article is yanked automatically."
319   (interactive
320    (list (and current-prefix-arg
321               (gnus-summary-work-articles 1))))
322   (when yank
323     (gnus-summary-goto-subject (car yank)))
324   (save-window-excursion
325     (gnus-summary-select-article))
326   (let ((headers (gnus-summary-article-header (gnus-summary-article-number)))
327         (gnus-newsgroup-name gnus-newsgroup-name))
328     ;; Send a followup.
329     (gnus-post-news nil gnus-newsgroup-name
330                     headers gnus-article-buffer
331                     yank nil force-news)))
332
333 (defun gnus-summary-followup-with-original (n &optional force-news)
334   "Compose a followup to an article and include the original article."
335   (interactive "P")
336   (gnus-summary-followup (gnus-summary-work-articles n) force-news))
337
338 (defun gnus-summary-followup-to-mail (&optional arg)
339   "Followup to the current mail message via news."
340   (interactive
341    (list (and current-prefix-arg
342               (gnus-summary-work-articles 1))))
343   (gnus-summary-followup arg t))
344
345 (defun gnus-summary-followup-to-mail-with-original (&optional arg)
346   "Followup to the current mail message via news."
347   (interactive "P")
348   (gnus-summary-followup (gnus-summary-work-articles arg) t))
349
350 (defun gnus-inews-yank-articles (articles)
351   (let (beg article)
352     (message-goto-body)
353     (while (setq article (pop articles))
354       (save-window-excursion
355         (set-buffer gnus-summary-buffer)
356         (gnus-summary-select-article nil nil nil article)
357         (gnus-summary-remove-process-mark article))
358       (gnus-copy-article-buffer)
359       (let ((message-reply-buffer gnus-article-copy)
360             (message-reply-headers gnus-current-headers))
361         (message-yank-original)
362         (setq beg (or beg (mark t))))
363       (when articles
364         (insert "\n")))
365     (push-mark)
366     (goto-char beg)))
367
368 (defun gnus-summary-cancel-article (&optional n symp)
369   "Cancel an article you posted.
370 Uses the process-prefix convention.  If given the symbolic
371 prefix `a', cancel using the standard posting method; if not
372 post using the current select method."
373   (interactive (gnus-interactive "P\ny"))
374   (let ((articles (gnus-summary-work-articles n))
375         (message-post-method
376          `(lambda (arg)
377             (gnus-post-method (not (eq symp 'a)) ,gnus-newsgroup-name)))
378         article)
379     (while (setq article (pop articles))
380       (when (gnus-summary-select-article t nil nil article)
381         (when (gnus-eval-in-buffer-window gnus-original-article-buffer
382                 (message-cancel-news))
383           (gnus-summary-mark-as-read article gnus-canceled-mark)
384           (gnus-cache-remove-article 1))
385         (gnus-article-hide-headers-if-wanted))
386       (gnus-summary-remove-process-mark article))))
387
388 (defun gnus-summary-supersede-article ()
389   "Compose an article that will supersede a previous article.
390 This is done simply by taking the old article and adding a Supersedes
391 header line with the old Message-ID."
392   (interactive)
393   (let ((article (gnus-summary-article-number)))
394     (gnus-setup-message 'reply-yank
395       (gnus-summary-select-article t)
396       (set-buffer gnus-original-article-buffer)
397       (message-supersede)
398       (push
399        `((lambda ()
400            (when (gnus-buffer-exists-p ,gnus-summary-buffer)
401              (save-excursion
402                (set-buffer ,gnus-summary-buffer)
403                (gnus-cache-possibly-remove-article ,article nil nil nil t)
404                (gnus-summary-mark-as-read ,article gnus-canceled-mark)))))
405        message-send-actions))))
406
407 \f
408
409 (defun gnus-copy-article-buffer (&optional article-buffer)
410   ;; make a copy of the article buffer with all text properties removed
411   ;; this copy is in the buffer gnus-article-copy.
412   ;; if ARTICLE-BUFFER is nil, gnus-article-buffer is used
413   ;; this buffer should be passed to all mail/news reply/post routines.
414   (setq gnus-article-copy (gnus-get-buffer-create " *gnus article copy*"))
415   (save-excursion
416     (set-buffer gnus-article-copy)
417     (mm-enable-multibyte))
418   (let ((article-buffer (or article-buffer gnus-article-buffer))
419         end beg)
420     (if (not (and (get-buffer article-buffer)
421                   (gnus-buffer-exists-p article-buffer)))
422         (error "Can't find any article buffer")
423       (save-excursion
424         (set-buffer article-buffer)
425         (save-restriction
426           ;; Copy over the (displayed) article buffer, delete
427           ;; hidden text and remove text properties.
428           (widen)
429           (copy-to-buffer gnus-article-copy (point-min) (point-max))
430           (set-buffer gnus-article-copy)
431           (gnus-article-delete-text-of-type 'annotation)
432           (gnus-remove-text-with-property 'gnus-prev)
433           (gnus-remove-text-with-property 'gnus-next)
434           (insert
435            (prog1
436                (format "%s" (buffer-string))
437              (erase-buffer)))
438           ;; Find the original headers.
439           (set-buffer gnus-original-article-buffer)
440           (goto-char (point-min))
441           (while (looking-at message-unix-mail-delimiter)
442             (forward-line 1))
443           (setq beg (point))
444           (setq end (or (search-forward "\n\n" nil t) (point)))
445           ;; Delete the headers from the displayed articles.
446           (set-buffer gnus-article-copy)
447           (delete-region (goto-char (point-min))
448                          (or (search-forward "\n\n" nil t) (point-max)))
449           ;; Insert the original article headers.
450           (insert-buffer-substring gnus-original-article-buffer beg end)
451           (article-decode-encoded-words)))
452       gnus-article-copy)))
453
454 (defun gnus-post-news (post &optional group header article-buffer yank subject
455                             force-news)
456   (when article-buffer
457     (gnus-copy-article-buffer))
458   (let ((gnus-article-reply article-buffer)
459         (add-to-list gnus-add-to-list))
460     (gnus-setup-message (cond (yank 'reply-yank)
461                               (article-buffer 'reply)
462                               (t 'message))
463       (let* ((group (or group gnus-newsgroup-name))
464              (pgroup group)
465              to-address to-group mailing-list to-list
466              newsgroup-p)
467         (when group
468           (setq to-address (gnus-group-find-parameter group 'to-address)
469                 to-group (gnus-group-find-parameter group 'to-group)
470                 to-list (gnus-group-find-parameter group 'to-list)
471                 newsgroup-p (gnus-group-find-parameter group 'newsgroup)
472                 mailing-list (when gnus-mailing-list-groups
473                                (string-match gnus-mailing-list-groups group))
474                 group (gnus-group-real-name group)))
475         (if (or (and to-group
476                      (gnus-news-group-p to-group))
477                 newsgroup-p
478                 force-news
479                 (and (gnus-news-group-p
480                       (or pgroup gnus-newsgroup-name)
481                       (if header (mail-header-number header)
482                         gnus-current-article))
483                      (not mailing-list)
484                      (not to-list)
485                      (not to-address)))
486             ;; This is news.
487             (if post
488                 (message-news (or to-group group))
489               (set-buffer gnus-article-copy)
490               (gnus-msg-treat-broken-reply-to)
491               (message-followup (if (or newsgroup-p force-news) nil to-group)))
492           ;; The is mail.
493           (if post
494               (progn
495                 (message-mail (or to-address to-list))
496                 ;; Arrange for mail groups that have no `to-address' to
497                 ;; get that when the user sends off the mail.
498                 (when (and (not to-list)
499                            (not to-address)
500                            add-to-list)
501                   (push (list 'gnus-inews-add-to-address pgroup)
502                         message-send-actions)))
503             (set-buffer gnus-article-copy)
504             (gnus-msg-treat-broken-reply-to)
505             (message-wide-reply to-address)))
506         (when yank
507           (gnus-inews-yank-articles yank))))))
508
509 (defun gnus-msg-treat-broken-reply-to ()
510   "Remove the Reply-to header iff broken-reply-to."
511   (when (gnus-group-find-parameter
512          gnus-newsgroup-name 'broken-reply-to)
513     (save-restriction
514       (message-narrow-to-head)
515       (message-remove-header "reply-to"))))
516
517 (defun gnus-post-method (arg group &optional silent)
518   "Return the posting method based on GROUP and ARG.
519 If SILENT, don't prompt the user."
520   (let ((group-method (gnus-find-method-for-group group)))
521     (cond
522      ;; If the group-method is nil (which shouldn't happen) we use
523      ;; the default method.
524      ((null group-method)
525       (or (and (null (eq gnus-post-method 'active)) gnus-post-method)
526           gnus-select-method message-post-method))
527      ;; We want the inverse of the default
528      ((and arg (not (eq arg 0)))
529       (if (eq gnus-post-method 'active)
530           gnus-select-method
531         group-method))
532      ;; We query the user for a post method.
533      ((or arg
534           (and gnus-post-method
535                (not (eq gnus-post-method 'current))
536                (listp (car gnus-post-method))))
537       (let* ((methods
538               ;; Collect all methods we know about.
539               (append
540                (when (and gnus-post-method
541                           (not (eq gnus-post-method 'current)))
542                  (if (listp (car gnus-post-method))
543                      gnus-post-method
544                    (list gnus-post-method)))
545                gnus-secondary-select-methods
546                (mapcar 'cdr gnus-server-alist)
547                (mapcar 'car gnus-opened-servers)
548                (list gnus-select-method)
549                (list group-method)))
550              method-alist post-methods method)
551         ;; Weed out all mail methods.
552         (while methods
553           (setq method (gnus-server-get-method "" (pop methods)))
554           (when (and (or (gnus-method-option-p method 'post)
555                          (gnus-method-option-p method 'post-mail))
556                      (not (member method post-methods)))
557             (push method post-methods)))
558         ;; Create a name-method alist.
559         (setq method-alist
560               (mapcar
561                (lambda (m)
562                  (list (concat (cadr m) " (" (symbol-name (car m)) ")") m))
563                post-methods))
564         ;; Query the user.
565         (cadr
566          (assoc
567           (setq gnus-last-posting-server
568                 (if (and silent
569                          gnus-last-posting-server)
570                     ;; Just use the last value.
571                     gnus-last-posting-server
572                   (completing-read
573                    "Posting method: " method-alist nil t
574                    (cons (or gnus-last-posting-server "") 0))))
575           method-alist))))
576      ;; Override normal method.
577      ((and (eq gnus-post-method 'current)
578            (not (eq (car group-method) 'nndraft))
579            (gnus-get-function group-method 'request-post t)
580            (not arg))
581       group-method)
582      ((and gnus-post-method
583            (not (eq gnus-post-method 'current)))
584       gnus-post-method)
585      ;; Use the normal select method.
586      (t gnus-select-method))))
587
588 \f
589
590 ;; Dummies to avoid byte-compile warning.
591 (defvar nnspool-rejected-article-hook)
592 (defvar xemacs-codename)
593
594 (defun gnus-extended-version ()
595   "Stringified Gnus version and Emacs version."
596   (interactive)
597   (concat
598    "Gnus/" (prin1-to-string (gnus-continuum-version gnus-version) t)
599    " (" gnus-version ")"
600    " "
601    (cond
602     ((string-match "^\\(\\([.0-9]+\\)*\\)\\.[0-9]+$" emacs-version)
603      (concat "Emacs/" (match-string 1 emacs-version)))
604     ((string-match "\\([A-Z]*[Mm][Aa][Cc][Ss]\\)[^(]*\\(\\((beta.*)\\|'\\)\\)?"
605                    emacs-version)
606      (concat (match-string 1 emacs-version)
607              (format "/%d.%d" emacs-major-version emacs-minor-version)
608              (if (match-beginning 3)
609                  (match-string 3 emacs-version)
610                "")
611              (if (boundp 'xemacs-codename)
612                  (concat " (" xemacs-codename ")")
613                "")))
614     (t emacs-version))))
615
616 \f
617 ;;;
618 ;;; Gnus Mail Functions
619 ;;;
620
621 ;;; Mail reply commands of Gnus summary mode
622
623 (defun gnus-summary-reply (&optional yank wide)
624   "Start composing a reply mail to the current message.
625 If prefix argument YANK is non-nil, the original article is yanked
626 automatically."
627   (interactive
628    (list (and current-prefix-arg
629               (gnus-summary-work-articles 1))))
630   ;; Stripping headers should be specified with mail-yank-ignored-headers.
631   (when yank
632     (gnus-summary-goto-subject (car yank)))
633   (let ((gnus-article-reply t))
634     (gnus-setup-message (if yank 'reply-yank 'reply)
635       (gnus-summary-select-article)
636       (set-buffer (gnus-copy-article-buffer))
637       (gnus-msg-treat-broken-reply-to)
638       (message-reply nil wide)
639       (when yank
640         (gnus-inews-yank-articles yank)))))
641
642 (defun gnus-summary-reply-with-original (n &optional wide)
643   "Start composing a reply mail to the current message.
644 The original article will be yanked."
645   (interactive "P")
646   (gnus-summary-reply (gnus-summary-work-articles n) wide))
647
648 (defun gnus-summary-wide-reply (&optional yank)
649   "Start composing a wide reply mail to the current message.
650 If prefix argument YANK is non-nil, the original article is yanked
651 automatically."
652   (interactive
653    (list (and current-prefix-arg
654               (gnus-summary-work-articles 1))))
655   (gnus-summary-reply yank t))
656
657 (defun gnus-summary-wide-reply-with-original (n)
658   "Start composing a wide reply mail to the current message.
659 The original article will be yanked."
660   (interactive "P")
661   (gnus-summary-reply-with-original n t))
662
663 (defun gnus-summary-mail-forward (&optional not-used post)
664   "Forward the current message to another user.
665 If POST, post instead of mail."
666   (interactive "P")
667   (gnus-setup-message 'forward
668     (gnus-summary-select-article)
669     (let (text)
670       (save-excursion
671         (set-buffer gnus-original-article-buffer)
672         (setq text (buffer-string)))
673       (set-buffer (gnus-get-buffer-create
674                    (generate-new-buffer-name " *Gnus forward*")))
675       (erase-buffer)
676       (insert text)
677       (goto-char (point-min))
678       (when (looking-at "From ")
679         (replace-match "X-From-Line: ") )
680       (run-hooks 'gnus-article-decode-hook)
681       (message-forward post))))
682
683 (defun gnus-summary-resend-message (address n)
684   "Resend the current article to ADDRESS."
685   (interactive "sResend message(s) to: \nP")
686   (let ((articles (gnus-summary-work-articles n))
687         article)
688     (while (setq article (pop articles))
689       (gnus-summary-select-article nil nil nil article)
690       (save-excursion
691         (set-buffer gnus-original-article-buffer)
692         (message-resend address)))))
693
694 (defun gnus-summary-post-forward (&optional full-headers)
695   "Forward the current article to a newsgroup.
696 If FULL-HEADERS (the prefix), include full headers when forwarding."
697   (interactive "P")
698   (gnus-summary-mail-forward full-headers t))
699
700 (defvar gnus-nastygram-message
701   "The following article was inappropriately posted to %s.\n\n"
702   "Format string to insert in nastygrams.
703 The current group name will be inserted at \"%s\".")
704
705 (defun gnus-summary-mail-nastygram (n)
706   "Send a nastygram to the author of the current article."
707   (interactive "P")
708   (when (or gnus-expert-user
709             (gnus-y-or-n-p
710              "Really send a nastygram to the author of the current article? "))
711     (let ((group gnus-newsgroup-name))
712       (gnus-summary-reply-with-original n)
713       (set-buffer gnus-message-buffer)
714       (message-goto-body)
715       (insert (format gnus-nastygram-message group))
716       (message-send-and-exit))))
717
718 (defun gnus-summary-mail-crosspost-complaint (n)
719   "Send a complaint about crossposting to the current article(s)."
720   (interactive "P")
721   (let ((articles (gnus-summary-work-articles n))
722         article)
723     (while (setq article (pop articles))
724       (set-buffer gnus-summary-buffer)
725       (gnus-summary-goto-subject article)
726       (let ((group (gnus-group-real-name gnus-newsgroup-name))
727             newsgroups followup-to)
728         (gnus-summary-select-article)
729         (set-buffer gnus-original-article-buffer)
730         (if (and (<= (length (message-tokenize-header
731                               (setq newsgroups
732                                     (mail-fetch-field "newsgroups"))
733                               ", "))
734                      1)
735                  (or (not (setq followup-to (mail-fetch-field "followup-to")))
736                      (not (member group (message-tokenize-header
737                                          followup-to ", ")))))
738             (if followup-to
739                 (gnus-message 1 "Followup-to restricted")
740               (gnus-message 1 "Not a crossposted article"))
741           (set-buffer gnus-summary-buffer)
742           (gnus-summary-reply-with-original 1)
743           (set-buffer gnus-message-buffer)
744           (message-goto-body)
745           (insert (format gnus-crosspost-complaint newsgroups group))
746           (message-goto-subject)
747           (re-search-forward " *$")
748           (replace-match " (crosspost notification)" t t)
749           (gnus-deactivate-mark)
750           (when (gnus-y-or-n-p "Send this complaint? ")
751             (message-send-and-exit)))))))
752
753 (defun gnus-summary-mail-other-window ()
754   "Compose mail in other window."
755   (interactive)
756   (gnus-setup-message 'message
757     (message-mail)))
758
759 (defun gnus-mail-parse-comma-list ()
760   (let (accumulated
761         beg)
762     (skip-chars-forward " ")
763     (while (not (eobp))
764       (setq beg (point))
765       (skip-chars-forward "^,")
766       (while (zerop
767               (save-excursion
768                 (save-restriction
769                   (let ((i 0))
770                     (narrow-to-region beg (point))
771                     (goto-char beg)
772                     (logand (progn
773                               (while (search-forward "\"" nil t)
774                                 (incf i))
775                               (if (zerop i) 2 i))
776                             2)))))
777         (skip-chars-forward ",")
778         (skip-chars-forward "^,"))
779       (skip-chars-backward " ")
780       (push (buffer-substring beg (point))
781             accumulated)
782       (skip-chars-forward "^,")
783       (skip-chars-forward ", "))
784     accumulated))
785
786 (defun gnus-inews-add-to-address (group)
787   (let ((to-address (mail-fetch-field "to")))
788     (when (and to-address
789                (gnus-alive-p))
790       ;; This mail group doesn't have a `to-list', so we add one
791       ;; here.  Magic!
792       (when (gnus-y-or-n-p
793              (format "Do you want to add this as `to-list': %s " to-address))
794         (gnus-group-add-parameter group (cons 'to-list to-address))))))
795
796 (defun gnus-put-message ()
797   "Put the current message in some group and return to Gnus."
798   (interactive)
799   (let ((reply gnus-article-reply)
800         (winconf gnus-prev-winconf)
801         (group gnus-newsgroup-name))
802
803     (or (and group (not (gnus-group-read-only-p group)))
804         (setq group (read-string "Put in group: " nil
805                                  (gnus-writable-groups))))
806     (when (gnus-gethash group gnus-newsrc-hashtb)
807       (error "No such group: %s" group))
808
809     (save-excursion
810       (save-restriction
811         (widen)
812         (message-narrow-to-headers)
813         (let (gnus-deletable-headers)
814           (if (message-news-p)
815               (message-generate-headers message-required-news-headers)
816             (message-generate-headers message-required-mail-headers)))
817         (goto-char (point-max))
818         (insert "Gcc: " group "\n")
819         (widen)))
820
821     (gnus-inews-do-gcc)
822
823     (when (get-buffer gnus-group-buffer)
824       (when (gnus-buffer-exists-p (car-safe reply))
825         (set-buffer (car reply))
826         (and (cdr reply)
827              (gnus-summary-mark-article-as-replied
828               (cdr reply))))
829       (when winconf
830         (set-window-configuration winconf)))))
831
832 (defun gnus-article-mail (yank)
833   "Send a reply to the address near point.
834 If YANK is non-nil, include the original article."
835   (interactive "P")
836   (let ((address
837          (buffer-substring
838           (save-excursion (re-search-backward "[ \t\n]" nil t) (1+ (point)))
839           (save-excursion (re-search-forward "[ \t\n]" nil t) (1- (point))))))
840     (when address
841       (message-reply address)
842       (when yank
843         (gnus-inews-yank-articles (list (cdr gnus-article-current)))))))
844
845 (defvar nntp-server-type)
846 (defun gnus-bug ()
847   "Send a bug report to the Gnus maintainers."
848   (interactive)
849   (unless (gnus-alive-p)
850     (error "Gnus has been shut down"))
851   (gnus-setup-message 'bug
852     (delete-other-windows)
853     (when gnus-bug-create-help-buffer
854       (switch-to-buffer "*Gnus Help Bug*")
855       (erase-buffer)
856       (insert gnus-bug-message)
857       (goto-char (point-min)))
858     (message-pop-to-buffer "*Gnus Bug*")
859     (message-setup `((To . ,gnus-maintainer) (Subject . "")))
860     (when gnus-bug-create-help-buffer
861       (push `(gnus-bug-kill-buffer) message-send-actions))
862     (goto-char (point-min))
863     (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$"))
864     (forward-line 1)
865     (insert (gnus-version) "\n"
866             (emacs-version) "\n")
867     (when (and (boundp 'nntp-server-type)
868                (stringp nntp-server-type))
869       (insert nntp-server-type))
870     (insert "\n\n\n\n\n")
871     (save-excursion
872       (set-buffer (gnus-get-buffer-create " *gnus environment info*"))
873       (gnus-debug))
874     (insert "<#part type=application/x-emacs-lisp buffer=\" *gnus environment info*\" disposition=inline description=\"User settings\"><#/part>")
875     (goto-char (point-min))
876     (search-forward "Subject: " nil t)
877     (message "")))
878
879 (defun gnus-bug-kill-buffer ()
880   (when (get-buffer "*Gnus Help Bug*")
881     (kill-buffer "*Gnus Help Bug*")))
882
883 (defun gnus-summary-yank-message (buffer n)
884   "Yank the current article into a composed message."
885   (interactive
886    (list (completing-read "Buffer: " (mapcar 'list (message-buffers)) nil t)
887          current-prefix-arg))
888   (gnus-summary-iterate n
889     (let ((gnus-display-mime-function nil)
890           (gnus-inhibit-treatment t))
891       (gnus-summary-select-article))
892     (save-excursion
893       (set-buffer buffer)
894       (message-yank-buffer gnus-article-buffer))))
895
896 (defun gnus-debug ()
897   "Attempts to go through the Gnus source file and report what variables have been changed.
898 The source file has to be in the Emacs load path."
899   (interactive)
900   (let ((files '("gnus.el" "gnus-sum.el" "gnus-group.el"
901                  "gnus-art.el" "gnus-start.el" "gnus-async.el"
902                  "gnus-msg.el" "gnus-score.el" "gnus-win.el" "gnus-topic.el"
903                  "nnmail.el" "message.el"))
904         (point (point))
905         file expr olist sym)
906     (gnus-message 4 "Please wait while we snoop your variables...")
907     (sit-for 0)
908     ;; Go through all the files looking for non-default values for variables.
909     (save-excursion
910       (set-buffer (gnus-get-buffer-create " *gnus bug info*"))
911       (while files
912         (erase-buffer)
913         (when (and (setq file (locate-library (pop files)))
914                    (file-exists-p file))
915           (insert-file-contents file)
916           (goto-char (point-min))
917           (if (not (re-search-forward "^;;* *Internal variables" nil t))
918               (gnus-message 4 "Malformed sources in file %s" file)
919             (narrow-to-region (point-min) (point))
920             (goto-char (point-min))
921             (while (setq expr (ignore-errors (read (current-buffer))))
922               (ignore-errors
923                 (and (or (eq (car expr) 'defvar)
924                          (eq (car expr) 'defcustom))
925                      (stringp (nth 3 expr))
926                      (or (not (boundp (nth 1 expr)))
927                          (not (equal (eval (nth 2 expr))
928                                      (symbol-value (nth 1 expr)))))
929                      (push (nth 1 expr) olist)))))))
930       (kill-buffer (current-buffer)))
931     (when (setq olist (nreverse olist))
932       (insert "------------------ Environment follows ------------------\n\n"))
933     (while olist
934       (if (boundp (car olist))
935           (condition-case ()
936               (pp `(setq ,(car olist)
937                          ,(if (or (consp (setq sym (symbol-value (car olist))))
938                                   (and (symbolp sym)
939                                        (not (or (eq sym nil)
940                                                 (eq sym t)))))
941                               (list 'quote (symbol-value (car olist)))
942                             (symbol-value (car olist))))
943                   (current-buffer))
944             (error
945              (format "(setq %s 'whatever)\n" (car olist))))
946         (insert ";; (makeunbound '" (symbol-name (car olist)) ")\n"))
947       (setq olist (cdr olist)))
948     (insert "\n\n")
949     ;; Remove any control chars - they seem to cause trouble for some
950     ;; mailers.  (Byte-compiled output from the stuff above.)
951     (goto-char point)
952     (while (re-search-forward "[\000-\010\013-\037\200-\237]" nil t)
953       (replace-match (format "\\%03o" (string-to-char (match-string 0)))
954                      t t))))
955
956 ;;; Treatment of rejected articles.
957 ;;; Bounced mail.
958
959 (defun gnus-summary-resend-bounced-mail (&optional fetch)
960   "Re-mail the current message.
961 This only makes sense if the current message is a bounce message than
962 contains some mail you have written which has been bounced back to
963 you.
964 If FETCH, try to fetch the article that this is a reply to, if indeed
965 this is a reply."
966   (interactive "P")
967   (gnus-summary-select-article t)
968   (set-buffer gnus-original-article-buffer)
969   (gnus-setup-message 'compose-bounce
970     (let* ((references (mail-fetch-field "references"))
971            (parent (and references (gnus-parent-id references))))
972       (message-bounce)
973       ;; If there are references, we fetch the article we answered to.
974       (and fetch parent
975            (gnus-summary-refer-article parent)
976            (gnus-summary-show-all-headers)))))
977
978 ;;; Gcc handling.
979
980 ;; Do Gcc handling, which copied the message over to some group.
981 (defun gnus-inews-do-gcc (&optional gcc)
982   (interactive)
983   (when (gnus-alive-p)
984     (save-excursion
985       (save-restriction
986         (message-narrow-to-headers)
987         (let ((gcc (or gcc (mail-fetch-field "gcc" nil t)))
988               (cur (current-buffer))
989               groups group method)
990           (when gcc
991             (message-remove-header "gcc")
992             (widen)
993             (setq groups (message-tokenize-header gcc " ,"))
994             ;; Copy the article over to some group(s).
995             (while (setq group (pop groups))
996               (gnus-check-server
997                (setq method
998                      (cond ((and (null (gnus-get-info group))
999                                  (eq (car gnus-message-archive-method)
1000                                      (car
1001                                       (gnus-server-to-method
1002                                        (gnus-group-method group)))))
1003                             ;; If the group doesn't exist, we assume
1004                             ;; it's an archive group...
1005                             gnus-message-archive-method)
1006                            ;; Use the method.
1007                            ((gnus-info-method (gnus-get-info group))
1008                             (gnus-info-method (gnus-get-info group)))
1009                            ;; Find the method.
1010                            (t (gnus-group-method group)))))
1011               (gnus-check-server method)
1012               (unless (gnus-request-group group t method)
1013                 (gnus-request-create-group group method))
1014               (save-excursion
1015                 (nnheader-set-temp-buffer " *acc*")
1016                 (insert-buffer-substring cur)
1017                 (message-encode-message-body)
1018                 (save-restriction
1019                   (message-narrow-to-headers)
1020                   (mail-encode-encoded-word-buffer))
1021                 (goto-char (point-min))
1022                 (when (re-search-forward
1023                        (concat "^" (regexp-quote mail-header-separator) "$")
1024                        nil t)
1025                   (replace-match "" t t ))
1026                 (unless (gnus-request-accept-article group method t t)
1027                   (gnus-message 1 "Couldn't store article in group %s: %s"
1028                                 group (gnus-status-message method))
1029                   (sit-for 2))
1030                 (kill-buffer (current-buffer))))))))))
1031
1032 (defun gnus-inews-insert-gcc ()
1033   "Insert Gcc headers based on `gnus-outgoing-message-group'."
1034   (save-excursion
1035     (save-restriction
1036       (message-narrow-to-headers)
1037       (let* ((group gnus-outgoing-message-group)
1038              (gcc (cond
1039                    ((gnus-functionp group)
1040                     (funcall group))
1041                    ((or (stringp group) (list group))
1042                     group))))
1043         (when gcc
1044           (insert "Gcc: "
1045                   (if (stringp gcc) gcc
1046                     (mapconcat 'identity gcc " "))
1047                   "\n"))))))
1048
1049 (defun gnus-inews-insert-archive-gcc (&optional group)
1050   "Insert the Gcc to say where the article is to be archived."
1051   (let* ((var gnus-message-archive-group)
1052          (group (or group gnus-newsgroup-name ""))
1053          (gcc-self-val
1054           (and gnus-newsgroup-name
1055                (not (equal gnus-newsgroup-name ""))
1056                (gnus-group-find-parameter
1057                 gnus-newsgroup-name 'gcc-self)))
1058          result
1059          (groups
1060           (cond
1061            ((null gnus-message-archive-method)
1062             ;; Ignore.
1063             nil)
1064            ((stringp var)
1065             ;; Just a single group.
1066             (list var))
1067            ((null var)
1068             ;; We don't want this.
1069             nil)
1070            ((and (listp var) (stringp (car var)))
1071             ;; A list of groups.
1072             var)
1073            ((gnus-functionp var)
1074             ;; A function.
1075             (funcall var group))
1076            (t
1077             ;; An alist of regexps/functions/forms.
1078             (while (and var
1079                         (not
1080                          (setq result
1081                                (cond
1082                                 ((stringp (caar var))
1083                                  ;; Regexp.
1084                                  (when (string-match (caar var) group)
1085                                    (cdar var)))
1086                                 ((gnus-functionp (car var))
1087                                  ;; Function.
1088                                  (funcall (car var) group))
1089                                 (t
1090                                  (eval (car var)))))))
1091               (setq var (cdr var)))
1092             result)))
1093          name)
1094     (when (or groups gcc-self-val)
1095       (when (stringp groups)
1096         (setq groups (list groups)))
1097       (save-excursion
1098         (save-restriction
1099           (message-narrow-to-headers)
1100           (goto-char (point-max))
1101           (insert "Gcc: ")
1102           (if gcc-self-val
1103               ;; Use the `gcc-self' param value instead.
1104               (progn
1105                 (insert
1106                  (if (stringp gcc-self-val)
1107                      gcc-self-val
1108                    group))
1109                 (if (not (eq gcc-self-val 'none))
1110                     (insert "\n")
1111                   (progn
1112                     (beginning-of-line)
1113                     (kill-line))))
1114             ;; Use the list of groups.
1115             (while (setq name (pop groups))
1116               (insert (if (string-match ":" name)
1117                           name
1118                         (gnus-group-prefixed-name
1119                          name gnus-message-archive-method)))
1120               (when groups
1121                 (insert " ")))
1122             (insert "\n")))))))
1123
1124 ;;; Posting styles.
1125
1126 (defun gnus-configure-posting-styles ()
1127   "Configure posting styles according to `gnus-posting-styles'."
1128   (unless gnus-inhibit-posting-styles
1129     (let ((group (or gnus-newsgroup-name ""))
1130           (styles gnus-posting-styles)
1131           style match variable attribute value v results
1132           filep name address element)
1133       ;; If the group has a posting-style parameter, add it at the end with a
1134       ;; regexp matching everything, to be sure it takes precedence over all
1135       ;; the others.
1136       (when gnus-newsgroup-name
1137         (let ((tmp-style (gnus-group-find-parameter group 'posting-style t)))
1138           (when tmp-style
1139             (setq styles (append styles (list (cons ".*" tmp-style)))))))
1140       ;; Go through all styles and look for matches.
1141       (dolist (style styles)
1142         (setq match (pop style))
1143         (goto-char (point-min))
1144         (when (cond
1145                ((stringp match)
1146                 ;; Regexp string match on the group name.
1147                 (string-match match group))
1148                ((eq match 'header)
1149                 (let ((header (message-fetch-field (pop style))))
1150                   (and header
1151                        (string-match (pop style) header))))
1152                ((or (symbolp match)
1153                     (gnus-functionp match))
1154                 (cond
1155                  ((gnus-functionp match)
1156                   ;; Function to be called.
1157                   (funcall match))
1158                  ((boundp match)
1159                   ;; Variable to be checked.
1160                   (symbol-value match))))
1161                ((listp match)
1162                 ;; This is a form to be evaled.
1163                 (eval match)))
1164           ;; We have a match, so we set the variables.
1165           (dolist (attribute style)
1166             (setq element (pop attribute)
1167                   variable nil
1168                   filep nil)
1169             (setq value
1170                   (cond
1171                    ((eq (car attribute) :file)
1172                     (setq filep t)
1173                     (cadr attribute))
1174                    ((eq (car attribute) :value)
1175                     (cadr attribute))
1176                    (t
1177                     (car attribute))))
1178             ;; We get the value.
1179             (setq v
1180                   (cond
1181                    ((stringp value)
1182                     value)
1183                    ((or (symbolp value)
1184                         (gnus-functionp value))
1185                     (cond ((gnus-functionp value)
1186                            (funcall value))
1187                           ((boundp value)
1188                            (symbol-value value))))
1189                    ((listp value)
1190                     (eval value))))
1191             ;; Translate obsolescent value.
1192             (when (eq element 'signature-file)
1193               (setq element 'signature
1194                     filep t))
1195             ;; Get the contents of file elems.
1196             (when (and filep v)
1197               (setq v (with-temp-buffer
1198                         (insert-file-contents v)
1199                         (buffer-string))))
1200             (setq results (delq (assoc element results) results))
1201             (push (cons element v) results))))
1202       ;; Now we have all the styles, so we insert them.
1203       (setq name (assq 'name results)
1204             address (assq 'address results))
1205       (setq results (delq name (delq address results)))
1206       (make-local-variable 'message-setup-hook)
1207       (dolist (result results)
1208         (add-hook 'message-setup-hook
1209                   (cond
1210                    ((eq 'eval (car result))
1211                     'ignore)
1212                    ((eq 'body (car result))
1213                     `(lambda ()
1214                        (save-excursion
1215                          (message-goto-body)
1216                          (insert ,(cdr result)))))
1217                    ((eq 'signature (car result))
1218                     (set (make-local-variable 'message-signature) nil)
1219                     (set (make-local-variable 'message-signature-file) nil)
1220                     (if (not (cdr result))
1221                         'ignore
1222                       `(lambda ()
1223                          (save-excursion
1224                            (let ((message-signature ,(cdr result)))
1225                              (when message-signature
1226                                (message-insert-signature)))))))
1227                    (t
1228                     (let ((header
1229                            (if (symbolp (car result))
1230                                (capitalize (symbol-name (car result)))
1231                              (car result))))
1232                       `(lambda ()
1233                          (save-excursion
1234                            (message-remove-header ,header)
1235                            (message-goto-eoh)
1236                            (insert ,header ": " ,(cdr result) "\n"))))))))
1237       (when (or name address)
1238         (add-hook 'message-setup-hook
1239                   `(lambda ()
1240                      (set (make-local-variable 'user-mail-address)
1241                           ,(or (cdr address) user-mail-address))
1242                      (let ((user-full-name ,(or (cdr name) (user-full-name)))
1243                            (user-mail-address
1244                             ,(or (cdr address) user-mail-address)))
1245                        (save-excursion
1246                          (message-remove-header "From")
1247                          (message-goto-eoh)
1248                          (insert "From: " (message-make-from) "\n")))))))))
1249
1250 ;;; Allow redefinition of functions.
1251
1252 (gnus-ems-redefine)
1253
1254 (provide 'gnus-msg)
1255
1256 ;;; gnus-msg.el ends here