Mercurial > emacs
diff lisp/gnus-msg.el @ 13401:178d730efae2
entered into RCS
author | Lars Magne Ingebrigtsen <larsi@gnus.org> |
---|---|
date | Sat, 04 Nov 1995 03:54:42 +0000 |
parents | |
children | d4841ac1505f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/gnus-msg.el Sat Nov 04 03:54:42 1995 +0000 @@ -0,0 +1,1803 @@ +;;; gnus-msg.el --- mail and post interface for Gnus +;; Copyright (C) 1995 Free Software Foundation, Inc. + +;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet> +;; Lars Magne Ingebrigtsen <larsi@ifi.uio.no> +;; Keywords: news + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: + +;;; Code: + +(require 'gnus) +(require 'sendmail) +(require 'gnus-ems) + +(defvar gnus-organization-file "/usr/lib/news/organization" + "*Local news organization file.") + +(defvar gnus-prepare-article-hook (list 'gnus-inews-insert-signature) + "*A hook called after preparing body, but before preparing header headers. +The default hook (`gnus-inews-insert-signature') inserts a signature +file specified by the variable `gnus-signature-file'.") + +(defvar gnus-post-prepare-function nil + "*Function that is run after a post buffer has been prepared. +It is called with the name of the newsgroup that is posted to. It +might be used, for instance, for inserting signatures based on the +newsgroup name. (In that case, `gnus-signature-file' and +`mail-signature' should both be set to nil).") + +(defvar gnus-post-prepare-hook nil + "*Hook that is run after a post buffer has been prepared. +If you want to insert the signature, you might put +`gnus-inews-insert-signature' in this hook.") + +(defvar gnus-use-followup-to t + "*Specifies what to do with Followup-To header. +If nil, ignore the header. If it is t, use its value, but ignore +`poster'. If it is the symbol `ask', query the user before posting. +If it is the symbol `use', always use the value.") + +(defvar gnus-followup-to-function nil + "*A variable that contains a function that returns a followup address. +The function will be called in the buffer of the article that is being +followed up. The buffer will be narrowed to the headers of the +article. To pick header headers, one might use `mail-fetch-field'. The +function will be called with the name of the current newsgroup as the +argument. + +Here's an example `gnus-followup-to-function': + +(setq gnus-followup-to-function + (lambda (group) + (cond ((string= group \"mail.list\") + (or (mail-fetch-field \"sender\") + (mail-fetch-field \"from\"))) + (t + (or (mail-fetch-field \"reply-to\") + (mail-fetch-field \"from\"))))))") + +(defvar gnus-reply-to-function nil + "*A variable that contains a function that returns a reply address. +See the `gnus-followup-to-function' variable for an explanation of how +this variable is used. + +This function should return a string that will be used to fill in the +header. This function may also return a list. In that case, every +list element should be a cons where the first car should be a string +with the header name, and the cdr should be a string with the header +value.") + +(defvar gnus-author-copy (getenv "AUTHORCOPY") + "*Save outgoing articles in this file. +Initialized from the AUTHORCOPY environment variable. + +If this variable begins with the character \"|\", outgoing articles +will be piped to the named program. It is possible to save an article +in an MH folder as follows: + +\(setq gnus-author-copy \"|/usr/local/lib/mh/rcvstore +Article\") + +If the first character is not a pipe, articles are saved using the +function specified by the `gnus-author-copy-saver' variable.") + +(defvar gnus-mail-self-blind nil + "*Non-nil means insert a BCC header in all outgoing articles. +This will result in having a copy of the article mailed to yourself. +The BCC header is inserted when the post buffer is initialized, so you +can remove or alter the BCC header to override the default.") + +(defvar gnus-author-copy-saver (function rmail-output) + "*A function called to save outgoing articles. +This function will be called with the same of the file to store the +article in. The default function is `rmail-output' which saves in Unix +mailbox format.") + +(defvar gnus-user-login-name nil + "*The login name of the user. +Got from the function `user-login-name' if undefined.") + +(defvar gnus-user-full-name nil + "*The full name of the user. +Got from the NAME environment variable if undefined.") + +(defvar gnus-user-from-line nil + "*Your full, complete e-mail address. +Overrides the other Gnus variables if it is non-nil. + +Here are two example values of this variable: + + \"Lars Magne Ingebrigtsen <larsi@ifi.uio.no>\" + +and + + \"larsi@ifi.uio.no (Lars Magne Ingebrigtsen)\" + +The first version is recommended, but the name has to be quoted if it +contains non-alphanumerical characters.") + +(defvar gnus-signature-file "~/.signature" + "*Your signature file. +If the variable is a string that doesn't correspond to a file, the +string itself is inserted.") + +(defvar gnus-signature-function nil + "*A function that should return a signature file name. +The function will be called with the name of the newsgroup being +posted to. +If the function returns a string that doesn't correspond to a file, the +string itself is inserted. +If the function returns nil, the `gnus-signature-file' variable will +be used instead.") + +(defvar gnus-required-headers + '(From Date Newsgroups Subject Message-ID Organization Lines X-Newsreader) + "*Headers to be generated or prompted for when posting an article. +RFC977 and RFC1036 require From, Date, Newsgroups, Subject, +Message-ID. Organization, Lines and X-Newsreader are optional. If +you want Gnus not to insert some header, remove it from this list.") + +(defvar gnus-deletable-headers '(Message-ID Date) + "*Headers to be deleted if they already exists and were generated by Gnus previously.") + +(defvar gnus-removable-headers '(NNTP-Posting-Host Bcc Xref) + "*Headers to be removed unconditionally before posting.") + +(defvar gnus-check-before-posting + '(subject-cmsg multiple-headers sendsys message-id from + long-lines control-chars size new-text + signature) + "In non-nil, Gnus will attempt to run some checks on outgoing posts. +If this variable is t, Gnus will check everything it can. If it is a +list, then those elements in that list will be checked.") + +(defvar gnus-delete-supersedes-headers + "^Path:\\|^Date\\|^NNTP-Posting-Host:\\|^Supersedes:" + "*Header lines matching this regexp will be deleted before posting. +It's best to delete old Path and Date headers before posting to avoid +any confusion.") + +(defvar gnus-auto-mail-to-author nil + "*If non-nil, mail the authors of articles a copy of your follow-ups. +If this variable is `ask', the user will be prompted for whether to +mail a copy. The string given by `gnus-mail-courtesy-message' will be +inserted at the beginning of the mail copy. + +Mail is sent using the function specified by the +`gnus-mail-send-method' variable.") + +;; Added by Ethan Bradford <ethanb@ptolemy.astro.washington.edu>. +(defvar gnus-mail-courtesy-message + "The following message is a courtesy copy of an article\nthat has been posted as well.\n\n" + "*This is inserted at the start of a mailed copy of a posted message. +If this variable is nil, no such courtesy message will be added.") + +(defvar gnus-mail-reply-method (function gnus-mail-reply-using-mail) + "*Function to compose a reply. +Three pre-made functions are `gnus-mail-reply-using-mail' (sendmail); +`gnus-mail-reply-using-mhe' (MH-E); and `gnus-mail-reply-using-vm'.") + +(defvar gnus-mail-forward-method (function gnus-mail-forward-using-mail) + "*Function to forward the current message to another user. +Three pre-made functions are `gnus-mail-forward-using-mail' (sendmail); +`gnus-mail-forward-using-mhe' (MH-E); and `gnus-mail-forward-using-vm'.") + +(defvar gnus-mail-other-window-method 'gnus-mail-other-window-using-mail + "*Function to compose mail in the other window. +Three pre-made functions are `gnus-mail-other-window-using-mail' +(sendmail); `gnus-mail-other-window-using-mhe' (MH-E); and +`gnus-mail-other-window-using-vm'.") + +(defvar gnus-mail-send-method send-mail-function + "*Function to mail a message which is also being posted as an article. +The message must have To or Cc header. The default is copied from +the variable `send-mail-function'.") + +(defvar gnus-inews-article-function 'gnus-inews-article + "*Function to post an article.") + +(defvar gnus-inews-article-hook (list 'gnus-inews-do-fcc) + "*A hook called before finally posting an article. +The default hook (`gnus-inews-do-fcc') does FCC processing (ie. saves +the article to a file).") + +(defvar gnus-inews-article-header-hook nil + "*A hook called after inserting the headers in an article to be posted. +The hook is called from the *post-news* buffer, narrowed to the +headers.") + +(defvar gnus-mail-hook nil + "*A hook called as the last thing after setting up a mail buffer.") + +;;; Internal variables. + +(defvar gnus-post-news-buffer "*post-news*") +(defvar gnus-mail-buffer "*mail*") +(defvar gnus-summary-send-map nil) +(defvar gnus-article-copy nil) +(defvar gnus-reply-subject nil) + +(eval-and-compile + (autoload 'gnus-uu-post-news "gnus-uu" nil t) + (autoload 'rmail-output "rmailout")) + + +;;; +;;; Gnus Posting Functions +;;; + +(define-prefix-command 'gnus-summary-send-map) +(define-key gnus-summary-mode-map "S" 'gnus-summary-send-map) +(define-key gnus-summary-send-map "p" 'gnus-summary-post-news) +(define-key gnus-summary-send-map "f" 'gnus-summary-followup) +(define-key gnus-summary-send-map "F" 'gnus-summary-followup-with-original) +(define-key gnus-summary-send-map "b" 'gnus-summary-followup-and-reply) +(define-key gnus-summary-send-map "B" 'gnus-summary-followup-and-reply-with-original) +(define-key gnus-summary-send-map "c" 'gnus-summary-cancel-article) +(define-key gnus-summary-send-map "s" 'gnus-summary-supersede-article) +(define-key gnus-summary-send-map "r" 'gnus-summary-reply) +(define-key gnus-summary-send-map "R" 'gnus-summary-reply-with-original) +(define-key gnus-summary-send-map "m" 'gnus-summary-mail-other-window) +(define-key gnus-summary-send-map "u" 'gnus-uu-post-news) +(define-key gnus-summary-send-map "om" 'gnus-summary-mail-forward) +(define-key gnus-summary-send-map "op" 'gnus-summary-post-forward) +(define-key gnus-summary-send-map "Om" 'gnus-uu-digest-mail-forward) +(define-key gnus-summary-send-map "Op" 'gnus-uu-digest-post-forward) + +;;; Internal functions. + +(defun gnus-number-base36 (num len) + (if (if (< len 0) (<= num 0) (= len 0)) + "" + (concat (gnus-number-base36 (/ num 36) (1- len)) + (char-to-string (aref "zyxwvutsrqponmlkjihgfedcba9876543210" + (% num 36)))))) + +;;; Post news commands of Gnus group mode and summary mode + +(defun gnus-group-mail () + "Start composing a mail." + (interactive) + (funcall gnus-mail-other-window-method)) + +(defun gnus-group-post-news () + "Post an article." + (interactive) + (let ((gnus-newsgroup-name nil)) + (gnus-post-news 'post nil nil gnus-article-buffer))) + +(defun gnus-summary-post-news () + "Post an article." + (interactive) + (gnus-set-global-variables) + (gnus-post-news 'post gnus-newsgroup-name)) + +(defun gnus-summary-followup (yank &optional yank-articles) + "Compose a followup to an article. +If prefix argument YANK is non-nil, original article is yanked automatically." + (interactive "P") + (gnus-set-global-variables) + (if yank-articles (gnus-summary-goto-subject (car yank-articles))) + (save-window-excursion + (gnus-summary-select-article)) + (let ((headers (gnus-get-header-by-number (gnus-summary-article-number))) + (gnus-newsgroup-name gnus-newsgroup-name)) + ;; Check Followup-To: poster. + (set-buffer gnus-article-buffer) + (if (and gnus-use-followup-to + (string-equal "poster" (gnus-fetch-field "followup-to")) + (or (not (memq gnus-use-followup-to '(t ask))) + (not (gnus-y-or-n-p + "Do you want to ignore `Followup-To: poster'? ")))) + ;; Mail to the poster. + (gnus-summary-reply yank) + (gnus-post-news nil gnus-newsgroup-name + headers gnus-article-buffer + (or yank-articles (not (not yank))))))) + +(defun gnus-summary-followup-with-original (n) + "Compose a followup to an article and include the original article." + (interactive "P") + (gnus-summary-followup t (gnus-summary-work-articles n))) + +;; Suggested by Daniel Quinlan <quinlan@best.com>. +(defun gnus-summary-followup-and-reply (yank &optional yank-articles) + "Compose a followup and do an auto mail to author." + (interactive "P") + (gnus-set-global-variables) + (let ((gnus-auto-mail-to-author t)) + (gnus-summary-followup yank yank-articles))) + +(defun gnus-summary-followup-and-reply-with-original (n) + "Compose a followup, include the original, and do an auto mail to author." + (interactive "P") + (gnus-summary-followup-and-reply t (gnus-summary-work-articles n))) + +(defun gnus-summary-cancel-article (n) + "Cancel an article you posted." + (interactive "P") + (gnus-set-global-variables) + (let ((articles (gnus-summary-work-articles n))) + (while articles + (gnus-summary-select-article t nil nil (car articles)) + (and (gnus-eval-in-buffer-window gnus-article-buffer (gnus-cancel-news)) + (gnus-summary-mark-as-read (car articles) gnus-canceled-mark)) + (gnus-summary-remove-process-mark (car articles)) + (gnus-article-hide-headers-if-wanted) + (setq articles (cdr articles))))) + +(defun gnus-summary-supersede-article () + "Compose an article that will supersede a previous article. +This is done simply by taking the old article and adding a Supersedes +header line with the old Message-ID." + (interactive) + (gnus-set-global-variables) + (gnus-summary-select-article t) + (if (not + (string-equal + (downcase (mail-strip-quoted-names + (mail-header-from gnus-current-headers))) + (downcase (mail-strip-quoted-names (gnus-inews-user-name))))) + (error "This article is not yours.")) + (save-excursion + (set-buffer gnus-article-buffer) + (let ((buffer-read-only nil)) + (goto-char (point-min)) + (search-forward "\n\n" nil t) + (if (not (re-search-backward "^Message-ID: " nil t)) + (error "No Message-ID in this article")))) + (if (gnus-post-news 'post gnus-newsgroup-name) + (progn + (erase-buffer) + (insert-buffer gnus-article-buffer) + (if (search-forward "\n\n" nil t) + (forward-char -1) + (goto-char (point-max))) + (narrow-to-region (point-min) (point)) + (goto-char (point-min)) + (and gnus-delete-supersedes-headers + (delete-matching-lines gnus-delete-supersedes-headers)) + (goto-char (point-min)) + (if (not (re-search-forward "^Message-ID: " nil t)) + (error "No Message-ID in this article") + (replace-match "Supersedes: " t t)) + (goto-char (point-max)) + (insert mail-header-separator) + (widen) + (forward-line 1)))) + + +;;;###autoload +(defalias 'sendnews 'gnus-post-news) + +;;;###autoload +(defalias 'postnews 'gnus-post-news) + +(defun gnus-copy-article-buffer (&optional article-buffer) + ;; make a copy of the article buffer with all text properties removed + ;; this copy is in the buffer gnus-article-copy. + ;; if ARTICLE-BUFFER is nil, gnus-article-buffer is used + ;; this buffer should be passed to all mail/news reply/post routines. + (setq gnus-article-copy (get-buffer-create " *gnus article copy*")) + (buffer-disable-undo gnus-article-copy) + (or (memq gnus-article-copy gnus-buffer-list) + (setq gnus-buffer-list (cons gnus-article-copy gnus-buffer-list))) + (let ((article-buffer (or article-buffer gnus-article-buffer))) + (if (and (get-buffer article-buffer) + (buffer-name (get-buffer article-buffer))) + (save-excursion + (set-buffer article-buffer) + (widen) + (copy-to-buffer gnus-article-copy (point-min) (point-max)) + (set-text-properties (point-min) (point-max) + nil gnus-article-copy))))) + +(defun gnus-post-news (post &optional group header article-buffer yank subject) + "Begin editing a new USENET news article to be posted. +Type \\[describe-mode] in the buffer to get a list of commands." + (interactive (list t)) + (gnus-copy-article-buffer article-buffer) + (if (or (not gnus-novice-user) + gnus-expert-user + (not (eq 'post + (nth 1 (assoc + (format "%s" (car (gnus-find-method-for-group + gnus-newsgroup-name))) + gnus-valid-select-methods)))) + (and group + (assq 'to-address + (nth 5 (nth 2 (gnus-gethash group gnus-newsrc-hashtb))))) + (gnus-y-or-n-p "Are you sure you want to post to all of USENET? ")) + (let ((sumart (if (not post) + (save-excursion + (set-buffer gnus-summary-buffer) + (cons (current-buffer) gnus-current-article)))) + (from (and header (mail-header-from header))) + (winconf (current-window-configuration)) + real-group) + (and gnus-interactive-post + (not gnus-expert-user) + post (not group) + (progn + (setq gnus-newsgroup-name + (setq group + (completing-read "Group: " gnus-active-hashtb))) + (or subject + (setq subject (read-string "Subject: "))))) + (setq mail-reply-buffer gnus-article-copy) + + (let ((newsgroup-name (or group gnus-newsgroup-name ""))) + (setq real-group (and group (gnus-group-real-name group))) + (setq gnus-post-news-buffer + (gnus-request-post-buffer + post real-group subject header gnus-article-copy + (nth 2 (and group (gnus-gethash group gnus-newsrc-hashtb))) + (or (cdr (assq 'to-group + (nth 5 (nth 2 (gnus-gethash + newsgroup-name + gnus-newsrc-hashtb))))) + (if (and (boundp 'gnus-followup-to-function) + gnus-followup-to-function + gnus-article-copy) + (save-excursion + (set-buffer gnus-article-copy) + (funcall gnus-followup-to-function group)))) + gnus-use-followup-to)) + (if post + (gnus-configure-windows 'post 'force) + (if yank + (gnus-configure-windows 'followup-yank 'force) + (gnus-configure-windows 'followup 'force))) + (gnus-overload-functions) + (make-local-variable 'gnus-article-reply) + (make-local-variable 'gnus-article-check-size) + (make-local-variable 'gnus-reply-subject) + (setq gnus-reply-subject (and header (mail-header-subject header))) + (setq gnus-article-reply sumart) + ;; Handle `gnus-auto-mail-to-author'. + ;; Suggested by Daniel Quinlan <quinlan@best.com>. + ;; Revised to respect Reply-To by Ulrik Dickow <dickow@nbi.dk>. + (let ((to (and (not post) + (if (if (eq gnus-auto-mail-to-author 'ask) + (y-or-n-p "Also send mail to author? ") + gnus-auto-mail-to-author) + (or (save-excursion + (set-buffer gnus-article-copy) + (gnus-fetch-field "reply-to")) + from))))) + (if to + (if (mail-fetch-field "To") + (progn + (beginning-of-line) + (insert "Cc: " to "\n")) + (mail-position-on-field "To") + (insert to)))) + ;; Handle author copy using BCC field. + (if (and gnus-mail-self-blind + (not (mail-fetch-field "bcc"))) + (progn + (mail-position-on-field "Bcc") + (insert (if (stringp gnus-mail-self-blind) + gnus-mail-self-blind + (user-login-name))))) + ;; Handle author copy using FCC field. + (if gnus-author-copy + (progn + (mail-position-on-field "Fcc") + (insert gnus-author-copy))) + (goto-char (point-min)) + (if post + (cond ((not group) + (re-search-forward "^Newsgroup:" nil t) + (end-of-line)) + ((not subject) + (re-search-forward "^Subject:" nil t) + (end-of-line)) + (t + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (forward-line 1))) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (forward-line 1) + (if (not yank) + () + (save-excursion + (if (not (listp yank)) + (news-reply-yank-original nil) + (setq yank (reverse yank)) + (while yank + (save-excursion + (save-window-excursion + (set-buffer gnus-summary-buffer) + (gnus-summary-select-article nil nil nil (car yank)) + (gnus-summary-remove-process-mark (car yank))) + (let ((mail-reply-buffer gnus-article-copy)) + (gnus-copy-article-buffer) + (let ((news-reply-yank-message-id + (save-excursion + (set-buffer gnus-article-copy) + (mail-fetch-field "message-id"))) + (news-reply-yank-from + (save-excursion + (set-buffer gnus-article-copy) + (mail-fetch-field "from")))) + (news-reply-yank-original nil)) + (setq yank (cdr yank))))))))) + (if gnus-post-prepare-function + (funcall gnus-post-prepare-function group)) + (run-hooks 'gnus-post-prepare-hook) + (make-local-variable 'gnus-prev-winconf) + (setq gnus-prev-winconf winconf)))) + (setq gnus-article-check-size (cons (buffer-size) (gnus-article-checksum))) + (message "") + t) + +(defun gnus-inews-news (&optional use-group-method) + "Send a news message. +If given a prefix, and the group is a foreign group, this function +will attempt to use the foreign server to post the article." + (interactive "P") + (or gnus-current-select-method + (setq gnus-current-select-method gnus-select-method)) + (let* ((case-fold-search nil) + (server-running (gnus-server-opened gnus-current-select-method)) + (reply gnus-article-reply) + error post-result) + (save-excursion + ;; Connect to default NNTP server if necessary. + ;; Suggested by yuki@flab.fujitsu.junet. + (gnus-start-news-server) ;Use default server. + ;; NNTP server must be opened before current buffer is modified. + (widen) + (goto-char (point-min)) + (run-hooks 'news-inews-hook) + (save-restriction + (narrow-to-region + (point-min) + (progn + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (match-beginning 0))) + + ;; Correct newsgroups field: change sequence of spaces to comma and + ;; eliminate spaces around commas. Eliminate imbedded line breaks. + (goto-char (point-min)) + (if (re-search-forward "^Newsgroups: +" nil t) + (save-restriction + (narrow-to-region + (point) + (if (re-search-forward "^[^ \t]" nil t) + (match-beginning 0) + (forward-line 1) + (point))) + (goto-char (point-min)) + (while (re-search-forward "\n[ \t]+" nil t) + (replace-match " " t t)) ;No line breaks (too confusing) + (goto-char (point-min)) + (while (re-search-forward "[ \t\n]*,[ \t\n]*\\|[ \t]+" nil t) + (replace-match "," t t)) + (goto-char (point-min)) + ;; Remove a trailing comma. + (if (re-search-forward ",$" nil t) + (replace-match "" t t)))) + + ;; Added by Per Abrahamsen <abraham@iesd.auc.dk>. + ;; Help save the the world! + (or + gnus-expert-user + (let ((newsgroups (mail-fetch-field "newsgroups")) + (followup-to (mail-fetch-field "followup-to")) + groups to) + (if (and newsgroups + (string-match "," newsgroups) (not followup-to)) + (progn + (while (string-match "," newsgroups) + (setq groups + (cons (list (substring newsgroups + 0 (match-beginning 0))) + groups)) + (setq newsgroups (substring newsgroups (match-end 0)))) + (setq groups (nreverse (cons (list newsgroups) groups))) + + (setq to + (completing-read "Followups to: (default all groups) " + groups)) + (if (> (length to) 0) + (progn + (goto-char (point-min)) + (insert "Followup-To: " to "\n"))))))) + + ;; Cleanup Followup-To. + (goto-char (point-min)) + (if (search-forward-regexp "^Followup-To: +" nil t) + (save-restriction + (narrow-to-region + (point) + (if (re-search-forward "^[^ \t]" nil 'end) + (match-beginning 0) + (point-max))) + (goto-char (point-min)) + (replace-regexp "\n[ \t]+" " ") ;No line breaks (too confusing) + (goto-char (point-min)) + (replace-regexp "[ \t\n]*,[ \t\n]*\\|[ \t]+" ","))) + + ;; Mail the message too if To:, Bcc:. or Cc: exists. + (let* ((types '("to" "bcc" "cc")) + (ty types) + fcc-line) + (while ty + (or (mail-fetch-field (car ty) nil t) + (setq types (delete (car ty) types))) + (setq ty (cdr ty))) + + (if (not types) + ;; We do not want to send mail. + () + (if (not gnus-mail-send-method) + (progn + (ding) + (gnus-message + 1 "No mailer defined. To: and/or Cc: fields ignored.") + (sit-for 1)) + (save-excursion + ;; We want to remove Fcc, because we want to handle + ;; that one ourselves... + + (goto-char (point-min)) + (if (re-search-forward "^Fcc: " nil t) + (progn + (setq fcc-line + (buffer-substring + (progn (beginning-of-line) (point)) + (progn (forward-line 1) (point)))) + (forward-line -1) + (gnus-delete-line))) + + ;; We generate a Message-ID so that the mail and the + ;; news copy of the message both get the same ID. + (or (mail-fetch-field "message-id") + (not (memq 'Message-ID gnus-required-headers)) + (progn + (goto-char (point-max)) + (insert "Message-ID: " (gnus-inews-message-id) "\n"))) + + (save-restriction + (widen) + (gnus-message 5 "Sending via mail...") + + (if (and gnus-mail-courtesy-message + (or (member "to" types) + (member "cc" types))) + ;; We only want to insert the courtesy mail + ;; message if we use to or cc; bcc should not + ;; have one. Well, if both bcc and to are + ;; present, it will get one anyway. + (progn + ;; Insert "courtesy" mail message. + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote + mail-header-separator) "$")) + (forward-line 1) + (insert gnus-mail-courtesy-message) + (funcall gnus-mail-send-method) + (goto-char (point-min)) + (search-forward gnus-mail-courtesy-message) + (replace-match "" t t)) + (funcall gnus-mail-send-method)) + + (gnus-message 5 "Sending via mail...done") + + (goto-char (point-min)) + (narrow-to-region + (point) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$"))) + (goto-char (point-min)) + (while (re-search-forward "^BCC:" nil t) + (delete-region (match-beginning 0) + ;; There might be continuation headers. + (if (re-search-forward "^[^ \t]" nil t) + (match-beginning 0) + ;; Uhm... or something like this. + (forward-line 1) + (point))))) + (if fcc-line + (progn + (goto-char (point-max)) + (insert fcc-line)))))))) + + ;; Send to server. + (gnus-message 5 "Posting to USENET...") + (setq post-result (funcall gnus-inews-article-function use-group-method)) + (cond ((eq post-result 'illegal) + (setq error t) + (ding)) + (post-result + (gnus-message 5 "Posting to USENET...done") + (if (gnus-buffer-exists-p (car-safe reply)) + (progn + (save-excursion + (set-buffer gnus-summary-buffer) + (gnus-summary-mark-article-as-replied + (cdr reply))))) + (set-buffer-modified-p nil)) + (t + ;; We cannot signal an error. + (setq error t) + (ding) + (gnus-message 1 "Article rejected: %s" + (gnus-status-message gnus-select-method))))) + ;; If NNTP server is opened by gnus-inews-news, close it by myself. + (or server-running + (gnus-close-server (gnus-find-method-for-group gnus-newsgroup-name))) + (let ((conf gnus-prev-winconf)) + (if (not error) + (progn + (bury-buffer) + ;; Restore last window configuration. + (and conf (set-window-configuration conf))))))) + +(defun gnus-inews-check-post () + "Check whether the post looks ok." + (or + (not gnus-check-before-posting) + (and + ;; We narrow to the headers and check them first. + (save-excursion + (save-restriction + (goto-char (point-min)) + (narrow-to-region + (point) + (progn + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (match-beginning 0))) + (goto-char (point-min)) + (and + ;; Check for commands in Subject. + (or + (gnus-check-before-posting 'subject-cmsg) + (save-excursion + (if (string-match "^cmsg " (mail-fetch-field "subject")) + (gnus-y-or-n-p + "The control code \"cmsg \" is in the subject. Really post? ") + t))) + ;; Check for multiple identical headers. + (or (gnus-check-before-posting 'multiple-headers) + (save-excursion + (let (found) + (while (and (not found) (re-search-forward "^[^ \t:]+: " + nil t)) + (save-excursion + (or (re-search-forward + (concat "^" (setq found + (buffer-substring + (match-beginning 0) + (- (match-end 0) 2)))) + nil t) + (setq found nil)))) + (if found + (gnus-y-or-n-p + (format "Multiple %s headers. Really post? " found)) + t)))) + ;; Check for version and sendsys. + (or (gnus-check-before-posting 'sendsys) + (save-excursion + (if (re-search-forward "^Sendsys:\\|^Version:" nil t) + (gnus-y-or-n-p + (format "The article contains a %s command. Really post? " + (buffer-substring (match-beginning 0) + (1- (match-end 0))))) + t))) + ;; Check the Message-Id header. + (or (gnus-check-before-posting 'message-id) + (save-excursion + (let* ((case-fold-search t) + (message-id (mail-fetch-field "message-id"))) + (or (not message-id) + (and (string-match "@" message-id) + (string-match "@[^\\.]*\\." message-id)) + (gnus-y-or-n-p + (format + "The Message-ID looks strange: \"%s\". Really post? " + message-id)))))) + ;; Check the From header. + (or (gnus-check-before-posting 'from) + (save-excursion + (let* ((case-fold-search t) + (from (mail-fetch-field "from"))) + (cond + ((not from) + (gnus-y-or-n-p "There is no From line. Really post? ")) + ((not (string-match "@[^\\.]*\\." from)) + (gnus-y-or-n-p + (format + "The address looks strange: \"%s\". Really post? " from))) + ((string-match "(.*).*(.*)" from) + (gnus-y-or-n-p + (format + "The From header looks strange: \"%s\". Really post? " + from))) + (t t))))) + ))) + ;; Check for long lines. + (or (gnus-check-before-posting 'long-lines) + (save-excursion + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (while (and + (progn + (end-of-line) + (< (current-column) 80)) + (zerop (forward-line 1)))) + (or (bolp) + (eobp) + (gnus-y-or-n-p + (format + "You have lines longer than 79 characters. Really post? "))))) + ;; Check for control characters. + (or (gnus-check-before-posting 'control-chars) + (save-excursion + (if (re-search-forward "[\000-\007\013\015-\037\200-\237]" nil t) + (gnus-y-or-n-p + "The article contains control characters. Really post? ") + t))) + ;; Check excessive size. + (or (gnus-check-before-posting 'size) + (if (> (buffer-size) 60000) + (gnus-y-or-n-p + (format "The article is %d octets long. Really post? " + (buffer-size))) + t)) + ;; Use the (size . checksum) variable to see whether the + ;; article is empty or has only quoted text. + (or + (gnus-check-before-posting 'new-text) + (if (and (= (buffer-size) (car gnus-article-check-size)) + (= (gnus-article-checksum) (cdr gnus-article-check-size))) + (gnus-y-or-n-p + "It looks like there's no new text in your article. Really post? ") + t)) + ;; Check the length of the signature. + (or (gnus-check-before-posting 'signature) + (progn + (goto-char (point-max)) + (if (not (re-search-backward gnus-signature-separator nil t)) + t + (if (> (count-lines (point) (point-max)) 5) + (gnus-y-or-n-p + (format + "Your .sig is %d lines; it should be max 4. Really post? " + (count-lines (point) (point-max)))) + t))))))) + +(defun gnus-article-checksum () + (let ((sum 0)) + (save-excursion + (while (not (eobp)) + (setq sum (logxor sum (following-char))) + (forward-char 1))) + sum)) + +;; Returns non-nil if this type is not to be checked. +(defun gnus-check-before-posting (type) + (not + (or (not gnus-check-before-posting) + (if (listp gnus-check-before-posting) + (memq type gnus-check-before-posting) + t)))) + +(defun gnus-cancel-news () + "Cancel an article you posted." + (interactive) + (if (or gnus-expert-user + (gnus-yes-or-no-p "Do you really want to cancel this article? ")) + (let ((from nil) + (newsgroups nil) + (message-id nil) + (distribution nil)) + (or (gnus-member-of-valid 'post gnus-newsgroup-name) + (error "This backend does not support canceling")) + (save-excursion + ;; Get header info. from original article. + (save-restriction + (gnus-article-show-all-headers) + (goto-char (point-min)) + (search-forward "\n\n" nil 'move) + (narrow-to-region (point-min) (point)) + (setq from (mail-fetch-field "from")) + (setq newsgroups (mail-fetch-field "newsgroups")) + (setq message-id (mail-fetch-field "message-id")) + (setq distribution (mail-fetch-field "distribution"))) + ;; Verify if the article is absolutely user's by comparing + ;; user id with value of its From: field. + (if (not + (string-equal + (downcase (mail-strip-quoted-names from)) + (downcase (mail-strip-quoted-names (gnus-inews-user-name))))) + (progn + (ding) (gnus-message 3 "This article is not yours.") + nil) + ;; Make control article. + (set-buffer (get-buffer-create " *Gnus-canceling*")) + (buffer-disable-undo (current-buffer)) + (erase-buffer) + (insert "Newsgroups: " newsgroups "\n" + "Subject: cancel " message-id "\n" + "Control: cancel " message-id "\n" + (if distribution + (concat "Distribution: " distribution "\n") + "") + mail-header-separator "\n" + "This is a cancel message from " from ".\n") + ;; Send the control article to NNTP server. + (gnus-message 5 "Canceling your article...") + (prog1 + (if (funcall gnus-inews-article-function) + (gnus-message 5 "Canceling your article...done") + (progn + (ding) + (gnus-message 1 "Cancel failed; %s" + (gnus-status-message gnus-newsgroup-name)) + nil) + t) + ;; Kill the article buffer. + (kill-buffer (current-buffer)))))))) + + +;;; Lowlevel inews interface + +(defun gnus-inews-article (&optional use-group-method) + "Post an article in current buffer using NNTP protocol." + (let ((artbuf (current-buffer)) + (tmpbuf (get-buffer-create " *Gnus-posting*"))) + (widen) + (goto-char (point-max)) + ;; require a newline at the end for inews to append .signature to + (or (= (preceding-char) ?\n) + (insert ?\n)) + ;; Prepare article headers. All message body such as signature + ;; must be inserted before Lines: field is prepared. + (save-restriction + (goto-char (point-min)) + (narrow-to-region + (point-min) + (save-excursion + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (match-beginning 0))) + (gnus-inews-remove-headers) + (gnus-inews-insert-headers) + (run-hooks 'gnus-inews-article-header-hook) + (widen)) + ;; Check whether the article is a good Net Citizen. + (if (and gnus-article-check-size + (not (gnus-inews-check-post))) + ;; Aber nein! + 'illegal + ;; Looks ok, so we do the nasty. + (save-excursion + (set-buffer tmpbuf) + (buffer-disable-undo (current-buffer)) + (erase-buffer) + (insert-buffer-substring artbuf) + ;; Remove the header separator. + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (replace-match "" t t) + ;; This hook may insert a signature. + (save-excursion + (goto-char (point-min)) + (let ((gnus-newsgroup-name (or (mail-fetch-field "newsgroups") + gnus-newsgroup-name))) + (run-hooks 'gnus-prepare-article-hook))) + ;; Run final inews hooks. This hook may do FCC. + ;; The article must be saved before being posted because + ;; `gnus-request-post' modifies the buffer. + (run-hooks 'gnus-inews-article-hook) + ;; Post an article to NNTP server. + ;; Return NIL if post failed. + (prog1 + (gnus-request-post + (if use-group-method + (gnus-find-method-for-group gnus-newsgroup-name) + gnus-select-method) use-group-method) + (kill-buffer (current-buffer))))))) + +(defun gnus-inews-remove-headers () + (let ((case-fold-search t) + (headers gnus-removable-headers)) + ;; Remove toxic headers. + (while headers + (goto-char (point-min)) + (and (re-search-forward + (concat "^" (downcase (format "%s" (car headers)))) + nil t) + (delete-region (progn (beginning-of-line) (point)) + (progn (forward-line 1) (point)))) + (setq headers (cdr headers))))) + +(defun gnus-inews-insert-headers () + "Prepare article headers. +Headers already prepared in the buffer are not modified. +Headers in `gnus-required-headers' will be generated." + (let ((Date (gnus-inews-date)) + (Message-ID (gnus-inews-message-id)) + (Organization (gnus-inews-organization)) + (From (gnus-inews-user-name)) + (Path (gnus-inews-path)) + (Subject nil) + (Newsgroups nil) + (Distribution nil) + (Lines (gnus-inews-lines)) + (X-Newsreader gnus-version) + (headers gnus-required-headers) + (case-fold-search t) + header value elem) + ;; First we remove any old generated headers. + (let ((headers gnus-deletable-headers)) + (while headers + (goto-char (point-min)) + (and (re-search-forward + (concat "^" (symbol-name (car headers)) ": *") nil t) + (get-text-property (1+ (match-beginning 0)) 'gnus-deletable) + (gnus-delete-line)) + (setq headers (cdr headers)))) + ;; If there are References, and no "Re: ", then the thread has + ;; changed name. See Son-of-1036. + (if (and (mail-fetch-field "references") + (get-buffer gnus-article-buffer)) + (let ((psubject (gnus-simplify-subject-re + (mail-fetch-field "subject")))) + (or (and psubject gnus-reply-subject + (string= (gnus-simplify-subject-re gnus-reply-subject) + psubject)) + (progn + (string-match "@" Message-ID) + (setq Message-ID + (concat (substring Message-ID 0 (match-beginning 0)) + "_-_" + (substring Message-ID (match-beginning 0)))))))) + ;; Go through all the required headers and see if they are in the + ;; articles already. If they are not, or are empty, they are + ;; inserted automatically - except for Subject, Newsgroups and + ;; Distribution. + (while headers + (goto-char (point-min)) + (setq elem (car headers)) + (if (consp elem) + (setq header (car elem)) + (setq header elem)) + (if (or (not (re-search-forward + (concat "^" (downcase (symbol-name header)) ":") nil t)) + (progn + ;; The header was found. We insert a space after the + ;; colon, if there is none. + (if (/= (following-char) ? ) (insert " ")) + ;; Find out whether the header is empty... + (looking-at "[ \t]*$"))) + ;; So we find out what value we should insert. + (progn + (setq value + (or (if (consp elem) + ;; The element is a cons. Either the cdr is + ;; a string to be inserted verbatim, or it + ;; is a function, and we insert the value + ;; returned from this function. + (or (and (stringp (cdr elem)) (cdr elem)) + (and (fboundp (cdr elem)) (funcall (cdr elem)))) + ;; The element is a symbol. We insert the + ;; value of this symbol, if any. + (and (boundp header) (symbol-value header))) + ;; We couldn't generate a value for this header, + ;; so we just ask the user. + (read-from-minibuffer + (format "Empty header for %s; enter value: " header)))) + ;; Finally insert the header. + (save-excursion + (if (bolp) + (progn + (goto-char (point-max)) + (insert (symbol-name header) ": " value "\n") + (forward-line -1)) + (replace-match value t t)) + ;; Add the deletable property to the headers that require it. + (and (memq header gnus-deletable-headers) + (progn (beginning-of-line) (looking-at "[^:]+: ")) + (add-text-properties + (point) (match-end 0) + '(gnus-deletable t face italic) (current-buffer)))))) + (setq headers (cdr headers))) + ;; Insert new Sender if the From is strange. + (let ((from (mail-fetch-field "from")) + (sender (mail-fetch-field "sender"))) + (if (and from + (not (string= + (downcase (car (gnus-extract-address-components from))) + (downcase (gnus-inews-real-user-address)))) + (or (null sender) + (not + (string= + (downcase (car (gnus-extract-address-components sender))) + (downcase (gnus-inews-real-user-address)))))) + (progn + (goto-char (point-min)) + (and (re-search-forward "^Sender:" nil t) + (progn + (beginning-of-line) + (insert "Original-") + (beginning-of-line))) + (insert "Sender: " (gnus-inews-real-user-address) "\n")))))) + + +(defun gnus-inews-insert-signature () + "Insert a signature file. +If `gnus-signature-function' is bound and returns a string, this +string is used instead of the variable `gnus-signature-file'. +In either case, if the string is a file name, this file is +inserted. If the string is not a file name, the string itself is +inserted. + +If you never want any signature inserted, set both of these variables to +nil." + (save-excursion + (let ((signature + (or (and gnus-signature-function + (funcall gnus-signature-function gnus-newsgroup-name)) + gnus-signature-file))) + (if (and signature + (or (file-exists-p signature) + (string-match " " signature) + (not (string-match + "^/[^/]+/" (expand-file-name signature))))) + (progn + (goto-char (point-max)) + (if (and mail-signature (search-backward "\n-- \n" nil t)) + () + ;; Delete any previous signatures. + (if (search-backward "\n-- \n" nil t) + (delete-region (point) (point-max))) + (or (eolp) (insert "\n")) + (insert "-- \n") + (if (file-exists-p signature) + (insert-file-contents signature) + (insert signature)) + (goto-char (point-max)) + (or (bolp) (insert "\n")))))))) + +;; Written by "Mr. Per Persson" <pp@solace.mh.se>. +(defun gnus-inews-insert-mime-headers () + (let ((mail-header-separator "")) + (or (mail-position-on-field "Mime-Version") + (insert "1.0") + (cond ((save-excursion + (beginning-of-buffer) + (re-search-forward "[\200-\377]" nil t)) + (or (mail-position-on-field "Content-Type") + (insert "text/plain; charset=ISO-8859-1")) + (or (mail-position-on-field "Content-Transfer-Encoding") + (insert "8bit"))) + (t (or (mail-position-on-field "Content-Type") + (insert "text/plain; charset=US-ASCII")) + (or (mail-position-on-field "Content-Transfer-Encoding") + (insert "7bit"))))))) + +(defun gnus-inews-do-fcc () + "Process FCC: fields in current article buffer. +Unless the first character of the field is `|', the article is saved +to the specified file using the function specified by the variable +gnus-author-copy-saver. The default function rmail-output saves in +Unix mailbox format. +If the first character is `|', the contents of the article is send to +a program specified by the rest of the value." + (let ((fcc-list nil) + (fcc-file nil) + (case-fold-search t)) ;Should ignore case. + (save-excursion + (save-restriction + (goto-char (point-min)) + (search-forward "\n\n") + (narrow-to-region (point-min) (point)) + (goto-char (point-min)) + (while (re-search-forward "^FCC:[ \t]*" nil t) + (setq fcc-list + (cons (buffer-substring + (point) + (progn + (end-of-line) + (skip-chars-backward " \t") + (point))) + fcc-list)) + (delete-region (match-beginning 0) + (progn (forward-line 1) (point)))) + ;; Process FCC operations. + (widen) + (while fcc-list + (setq fcc-file (car fcc-list)) + (setq fcc-list (cdr fcc-list)) + (cond ((string-match "^[ \t]*|[ \t]*\\(.*\\)[ \t]*$" fcc-file) + (let ((program (substring fcc-file + (match-beginning 1) (match-end 1)))) + ;; Suggested by yuki@flab.fujitsu.junet. + ;; Send article to named program. + (call-process-region (point-min) (point-max) shell-file-name + nil nil nil "-c" program))) + (t + ;; Suggested by hyoko@flab.fujitsu.junet. + ;; Save article in Unix mail format by default. + (gnus-make-directory (file-name-directory fcc-file)) + (if (and gnus-author-copy-saver + (not (eq gnus-author-copy-saver 'rmail-output))) + (funcall gnus-author-copy-saver fcc-file) + (if (and (file-readable-p fcc-file) + (mail-file-babyl-p fcc-file)) + (gnus-output-to-rmail fcc-file) + (rmail-output fcc-file 1 t t)))))))))) + +(defun gnus-inews-path () + "Return uucp path." + (let ((login-name (gnus-inews-login-name))) + (cond ((null gnus-use-generic-path) + (concat (nth 1 gnus-select-method) "!" login-name)) + ((stringp gnus-use-generic-path) + ;; Support GENERICPATH. Suggested by vixie@decwrl.dec.com. + (concat gnus-use-generic-path "!" login-name)) + (t login-name)))) + +(defun gnus-inews-user-name () + "Return user's network address as \"NAME@DOMAIN (FULL-NAME)\"." + (let ((full-name (gnus-inews-full-name)) + (address (if (or gnus-user-login-name gnus-use-generic-from + gnus-local-domain (getenv "DOMAINNAME")) + (concat (gnus-inews-login-name) "@" + (gnus-inews-domain-name gnus-use-generic-from)) + user-mail-address))) + (or gnus-user-from-line + (concat address + ;; User's full name. + (cond ((string-equal full-name "&") ;Unix hack. + (concat " (" (user-login-name) ")")) + ((string-match "[^ ]+@[^ ]+ +(.*)" address) + "") + (t + (concat " (" full-name ")"))))))) + +(defun gnus-inews-real-user-address () + "Return the \"real\" user address. +This function tries to ignore all user modifications, and +give as trustworthy answer as possible." + (concat (user-login-name) "@" (gnus-inews-full-address))) + +(defun gnus-inews-login-name () + "Return login name." + (or gnus-user-login-name (getenv "LOGNAME") (user-login-name))) + +(defun gnus-inews-full-name () + "Return full user name." + (or gnus-user-full-name (getenv "NAME") (user-full-name))) + +(defun gnus-inews-domain-name (&optional genericfrom) + "Return user's domain name. +If optional argument GENERICFROM is a string, use it as the domain +name; if it is non-nil, strip off local host name from the domain name. +If the function `system-name' returns full internet name and the +domain is undefined, the domain name is got from it." + (if (or genericfrom gnus-local-domain (getenv "DOMAINNAME")) + (let* ((system-name (system-name)) + (domain + (or (if (stringp genericfrom) genericfrom) + (getenv "DOMAINNAME") + gnus-local-domain + ;; Function `system-name' may return full internet name. + ;; Suggested by Mike DeCorte <mrd@sun.soe.clarkson.edu>. + (if (string-match "\\." system-name) + (substring system-name (match-end 0))) + (read-string "Domain name (no host): "))) + (host (or (if (string-match "\\." system-name) + (substring system-name 0 (match-beginning 0))) + system-name))) + (if (string-equal "." (substring domain 0 1)) + (setq domain (substring domain 1))) + ;; Support GENERICFROM as same as standard Bnews system. + ;; Suggested by ohm@kaba.junet and vixie@decwrl.dec.com. + (cond ((null genericfrom) + (concat host "." domain)) + ;;((stringp genericfrom) genericfrom) + (t domain))) + (if (string-match "\\." (system-name)) + (system-name) + (substring user-mail-address + (1+ (string-match "@" user-mail-address)))))) + +(defun gnus-inews-full-address () + (let ((domain (gnus-inews-domain-name)) + (system (system-name)) + (case-fold-search t)) + (if (string-match "\\." system) system + (if (string-match (concat "^" (regexp-quote system)) domain) domain + (concat system "." domain))))) + +(defun gnus-inews-message-id () + "Generate unique Message-ID for user." + ;; Message-ID should not contain a slash and should be terminated by + ;; a number. I don't know the reason why it is so. + (concat "<" (gnus-inews-unique-id) "@" (gnus-inews-full-address) ">")) + +(defvar gnus-unique-id-char nil) + +;; If you ever change this function, make sure the new version +;; cannot generate IDs that the old version could. +;; You might for example insert a "." somewhere (not next to another dot +;; or string boundary), or modify the newsreader name to "Ding". +(defun gnus-inews-unique-id () + ;; Dont use microseconds from (current-time), they may be unsupported. + ;; Instead we use this randomly inited counter. + (setq gnus-unique-id-char + (% (1+ (or gnus-unique-id-char (logand (random t) (1- (lsh 1 20))))) + ;; (current-time) returns 16-bit ints, + ;; and 2^16*25 just fits into 4 digits i base 36. + (* 25 25))) + (let ((tm (if (fboundp 'current-time) + (current-time) '(12191 46742 287898)))) + (concat + (if (memq system-type '(ms-dos emx vax-vms)) + (let ((user (downcase (gnus-inews-login-name)))) + (while (string-match "[^a-z0-9_]" user) + (aset user (match-beginning 0) ?_)) + user) + (gnus-number-base36 (user-uid) -1)) + (gnus-number-base36 (+ (car tm) (lsh (% gnus-unique-id-char 25) 16)) 4) + (gnus-number-base36 (+ (nth 1 tm) (lsh (/ gnus-unique-id-char 25) 16)) 4) + ;; Append the newsreader name, because while the generated + ;; ID is unique to this newsreader, other newsreaders might + ;; otherwise generate the same ID via another algorithm. + ".fsf"))) + + +(defun gnus-inews-date () + "Current time string." + (timezone-make-date-arpa-standard + (current-time-string) (current-time-zone))) + +(defun gnus-inews-organization () + "Return user's organization. +The ORGANIZATION environment variable is used if defined. +If not, the variable `gnus-local-organization' is used instead. +If it is a function, the function will be called with the current +newsgroup name as the argument. +If this is a file name, the contents of this file will be used as the +organization." + (let* ((organization + (or (getenv "ORGANIZATION") + (if gnus-local-organization + (if (and (symbolp gnus-local-organization) + (fboundp gnus-local-organization)) + (funcall gnus-local-organization gnus-newsgroup-name) + gnus-local-organization)) + gnus-organization-file + "~/.organization"))) + (and (stringp organization) + (> (length organization) 0) + (or (file-exists-p organization) + (string-match " " organization) + (not (string-match "^/usr/lib/" organization))) + (save-excursion + (gnus-set-work-buffer) + (if (file-exists-p organization) + (insert-file-contents organization) + (insert organization)) + (goto-char (point-min)) + (while (re-search-forward " *\n *" nil t) + (replace-match " " t t)) + (buffer-substring (point-min) (point-max)))))) + +(defun gnus-inews-lines () + "Count the number of lines and return numeric string." + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (forward-line 1) + (int-to-string (count-lines (point) (point-max)))))) + + +;;; +;;; Gnus Mail Functions +;;; + +;;; Mail reply commands of Gnus summary mode + +(defun gnus-summary-reply (yank &optional yank-articles) + "Reply mail to news author. +If prefix argument YANK is non-nil, original article is yanked automatically. +Customize the variable gnus-mail-reply-method to use another mailer." + (interactive "P") + ;; Bug fix by jbw@bigbird.bu.edu (Joe Wells) + ;; Stripping headers should be specified with mail-yank-ignored-headers. + (gnus-set-global-variables) + (if yank-articles (gnus-summary-goto-subject (car yank-articles))) + (gnus-summary-select-article) + (let ((gnus-newsgroup-name gnus-newsgroup-name)) + (bury-buffer gnus-article-buffer) + (funcall gnus-mail-reply-method (or yank-articles (not (not yank)))))) + +(defun gnus-summary-reply-with-original (n) + "Reply mail to news author with original article. +Customize the variable gnus-mail-reply-method to use another mailer." + (interactive "P") + (gnus-summary-reply t (gnus-summary-work-articles n))) + +(defun gnus-summary-mail-forward (post) + "Forward the current message to another user. +Customize the variable gnus-mail-forward-method to use another mailer." + (interactive "P") + (gnus-set-global-variables) + (gnus-summary-select-article) + (gnus-copy-article-buffer) + (let ((gnus-newsgroup-name gnus-newsgroup-name)) + (if post + (gnus-forward-using-post gnus-article-copy) + (funcall gnus-mail-forward-method gnus-article-copy)))) + +(defun gnus-summary-post-forward () + "Forward the current article to a newsgroup." + (interactive) + (gnus-summary-mail-forward t)) + +(defvar gnus-nastygram-message + "The following article was inappropriately posted to %s.\n" + "Format string to insert in nastygrams. +The current group name will be inserted at \"%s\".") + +(defun gnus-summary-mail-nastygram (n) + "Send a nastygram to the author of the current article." + (interactive "P") + (if (or gnus-expert-user + (gnus-y-or-n-p + "Really send a nastygram to the author of the current article? ")) + (let ((group gnus-newsgroup-name)) + (gnus-summary-reply-with-original n) + (set-buffer gnus-mail-buffer) + (insert (format gnus-nastygram-message group)) + (gnus-mail-send-and-exit)))) + +(defun gnus-summary-mail-other-window () + "Compose mail in other window. +Customize the variable `gnus-mail-other-window-method' to use another +mailer." + (interactive) + (gnus-set-global-variables) + (let ((gnus-newsgroup-name gnus-newsgroup-name)) + (funcall gnus-mail-other-window-method))) + +(defun gnus-mail-reply-using-mail (&optional yank to-address) + (save-excursion + (set-buffer gnus-summary-buffer) + (let ((group (gnus-group-real-name gnus-newsgroup-name)) + (cur (cons (current-buffer) (cdr gnus-article-current))) + (winconf (current-window-configuration)) + from subject date reply-to message-of + references message-id sender follow-to sendto elt) + (set-buffer (get-buffer-create gnus-mail-buffer)) + (mail-mode) + (make-local-variable 'gnus-article-reply) + (setq gnus-article-reply cur) + (make-local-variable 'gnus-prev-winconf) + (setq gnus-prev-winconf winconf) + (if (and (buffer-modified-p) + (> (buffer-size) 0) + (not (gnus-y-or-n-p + "Unsent article being composed; erase it? "))) + () + (erase-buffer) + (save-excursion + (gnus-copy-article-buffer) + (save-restriction + (set-buffer gnus-article-copy) + (gnus-narrow-to-headers) + (if (and (boundp 'gnus-reply-to-function) + gnus-reply-to-function) + (setq follow-to (funcall gnus-reply-to-function group))) + (setq from (mail-fetch-field "from")) + (setq date (or (mail-fetch-field "date") + (mail-header-date gnus-current-headers))) + (and from + (let ((stop-pos + (string-match " *at \\| *@ \\| *(\\| *<" from))) + (setq message-of + (concat (if stop-pos (substring from 0 stop-pos) from) + "'s message of " date)))) + (setq sender (mail-fetch-field "sender")) + (setq subject (or (mail-fetch-field "subject") + "Re: none")) + (or (string-match "^[Rr][Ee]:" subject) + (setq subject (concat "Re: " subject))) + (setq reply-to (mail-fetch-field "reply-to")) + (setq references (mail-fetch-field "references")) + (setq message-id (mail-fetch-field "message-id")) + (widen)) + (setq news-reply-yank-from (or from "(nobody)"))) + (setq news-reply-yank-message-id + (or message-id "(unknown Message-ID)")) + + ;; Gather the "to" addresses out of the follow-to list and remove + ;; them as we go. + (if (and follow-to (listp follow-to)) + (while (setq elt (assoc "To" follow-to)) + (setq sendto (concat sendto (and sendto ", ") (cdr elt))) + (setq follow-to (delq elt follow-to)))) + + (mail-setup (or to-address + (if (and follow-to (not (stringp follow-to))) sendto + (or follow-to reply-to from sender ""))) + subject message-of nil gnus-article-copy nil) + + (auto-save-mode auto-save-default) + (use-local-map (copy-keymap mail-mode-map)) + (local-set-key "\C-c\C-c" 'gnus-mail-send-and-exit) + + (if (and follow-to (listp follow-to)) + (progn + (goto-char (point-min)) + (re-search-forward "^To:" nil t) + (beginning-of-line) + (forward-line 1) + (while follow-to + (insert (car (car follow-to)) ": " (cdr (car follow-to)) "\n") + (setq follow-to (cdr follow-to))))) + (nnheader-insert-references references message-id) + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$")) + (forward-line 1) + (if (not yank) + (gnus-configure-windows 'reply 'force) + (let ((last (point)) + end) + (if (not (listp yank)) + (progn + (save-excursion + (mail-yank-original nil)) + (or mail-yank-hooks mail-citation-hook + (run-hooks 'news-reply-header-hook))) + (while yank + (save-window-excursion + (set-buffer gnus-summary-buffer) + (gnus-summary-select-article nil nil nil (car yank)) + (gnus-summary-remove-process-mark (car yank))) + (save-excursion + (gnus-copy-article-buffer) + (mail-yank-original nil) + (setq end (point))) + (or mail-yank-hooks mail-citation-hook + (run-hooks 'news-reply-header-hook)) + (goto-char end) + (setq yank (cdr yank)))) + (goto-char last)) + (gnus-configure-windows 'reply-yank 'force)) + (run-hooks 'gnus-mail-hook))))) + +(defun gnus-mail-yank-original () + (interactive) + (save-excursion + (mail-yank-original nil)) + (or mail-yank-hooks mail-citation-hook + (run-hooks 'news-reply-header-hook))) + +(defun gnus-mail-send-and-exit () + (interactive) + (let ((reply gnus-article-reply) + (winconf gnus-prev-winconf)) + (mail-send-and-exit nil) + (if (get-buffer gnus-group-buffer) + (progn + (if (gnus-buffer-exists-p (car-safe reply)) + (progn + (set-buffer (car reply)) + (and (cdr reply) + (gnus-summary-mark-article-as-replied + (cdr reply))))) + (and winconf (set-window-configuration winconf)))))) + +(defun gnus-forward-make-subject (buffer) + (save-excursion + (set-buffer buffer) + (concat "[" (if (memq 'mail (assoc (symbol-name + (car (gnus-find-method-for-group + gnus-newsgroup-name))) + gnus-valid-select-methods)) + (gnus-fetch-field "From") + gnus-newsgroup-name) + "] " (or (gnus-fetch-field "Subject") "")))) + +(defun gnus-forward-insert-buffer (buffer) + (let ((beg (goto-char (point-max)))) + (insert "------- Start of forwarded message -------\n") + (insert-buffer buffer) + (goto-char (point-max)) + (insert "------- End of forwarded message -------\n") + ;; Suggested by Sudish Joseph <joseph@cis.ohio-state.edu>. + (goto-char beg) + (while (setq beg (next-single-property-change (point) 'invisible)) + (goto-char beg) + (delete-region beg (or (next-single-property-change + (point) 'invisible) + (point-max)))))) + +(defun gnus-mail-forward-using-mail (&optional buffer) + "Forward the current message to another user using mail." + ;; This is almost a carbon copy of rmail-forward in rmail.el. + (let* ((forward-buffer (or buffer (current-buffer))) + (winconf (current-window-configuration)) + (subject (gnus-forward-make-subject forward-buffer))) + (set-buffer forward-buffer) + (mail nil nil subject) + (use-local-map (copy-keymap (current-local-map))) + (local-set-key "\C-c\C-c" 'gnus-mail-send-and-exit) + (make-local-variable 'gnus-prev-winconf) + (setq gnus-prev-winconf winconf) + (gnus-forward-insert-buffer forward-buffer) + (goto-char (point-min)) + (re-search-forward "^To: " nil t) + (gnus-configure-windows 'mail-forward 'force) + ;; You have a chance to arrange the message. + (run-hooks 'gnus-mail-forward-hook) + (run-hooks 'gnus-mail-hook))) + +(defun gnus-forward-using-post (&optional buffer) + (save-excursion + (let* ((forward-buffer (or buffer (current-buffer))) + (subject (gnus-forward-make-subject forward-buffer)) + (gnus-newsgroup-name nil)) + (gnus-post-news 'post nil nil nil nil subject) + (save-excursion + (gnus-forward-insert-buffer forward-buffer) + ;; You have a chance to arrange the message. + (run-hooks 'gnus-mail-forward-hook))))) + +(defun gnus-mail-other-window-using-mail () + "Compose mail other window using mail." + (let ((winconf (current-window-configuration))) + (mail-other-window nil nil nil nil nil (get-buffer gnus-article-buffer)) + (use-local-map (copy-keymap (current-local-map))) + (local-set-key "\C-c\C-c" 'gnus-mail-send-and-exit) + (make-local-variable 'gnus-prev-winconf) + (setq gnus-prev-winconf winconf) + (run-hooks 'gnus-mail-hook) + (gnus-configure-windows 'summary-mail 'force))) + +(defun gnus-article-mail (yank) + "Send a reply to the address near point. +If YANK is non-nil, include the original article." + (interactive "P") + (let ((address + (buffer-substring + (save-excursion (re-search-backward "[ \t\n]" nil t) (1+ (point))) + (save-excursion (re-search-forward "[ \t\n]" nil t) (1- (point)))))) + (and address + (progn + (switch-to-buffer gnus-summary-buffer) + (funcall gnus-mail-reply-method yank address))))) + +(defun gnus-bug () + "Send a bug report to the Gnus maintainers." + (interactive) + (let ((winconf (current-window-configuration))) + (delete-other-windows) + (switch-to-buffer "*Gnus Help Bug*") + (erase-buffer) + (insert gnus-bug-message) + (goto-char (point-min)) + (pop-to-buffer "*Gnus Bug*") + (erase-buffer) + (mail-mode) + (mail-setup gnus-maintainer nil nil nil nil nil) + (auto-save-mode auto-save-default) + (make-local-variable 'gnus-prev-winconf) + (setq gnus-prev-winconf winconf) + (use-local-map (copy-keymap mail-mode-map)) + (local-set-key "\C-c\C-c" 'gnus-bug-mail-send-and-exit) + (goto-char (point-min)) + (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$")) + (forward-line 1) + (insert (format "%s\n%s\n\n\n\n\n" (gnus-version) (emacs-version))) + (gnus-debug) + (goto-char (point-min)) + (search-forward "Subject: " nil t) + (message ""))) + +(defun gnus-bug-mail-send-and-exit () + "Send the bug message and exit." + (interactive) + (and (get-buffer "*Gnus Help Bug*") + (kill-buffer "*Gnus Help Bug*")) + (gnus-mail-send-and-exit)) + +(defun gnus-debug () + "Attemps to go through the Gnus source file and report what variables have been changed. +The source file has to be in the Emacs load path." + (interactive) + (let ((files '("gnus.el" "gnus-msg.el" "gnus-score.el")) + file dirs expr olist sym) + (message "Please wait while we snoop your variables...") + (sit-for 0) + (save-excursion + (set-buffer (get-buffer-create " *gnus bug info*")) + (buffer-disable-undo (current-buffer)) + (while files + (erase-buffer) + (setq dirs load-path) + (while dirs + (if (or (not (car dirs)) + (not (stringp (car dirs))) + (not (file-exists-p + (setq file (concat (file-name-as-directory + (car dirs)) (car files)))))) + (setq dirs (cdr dirs)) + (setq dirs nil) + (insert-file-contents file) + (goto-char (point-min)) + (or (re-search-forward "^;;* *Internal variables" nil t) + (error "Malformed sources in file %s" file)) + (narrow-to-region (point-min) (point)) + (goto-char (point-min)) + (while (setq expr (condition-case () + (read (current-buffer)) (error nil))) + (condition-case () + (and (eq (car expr) 'defvar) + (stringp (nth 3 expr)) + (or (not (boundp (nth 1 expr))) + (not (equal (eval (nth 2 expr)) + (symbol-value (nth 1 expr))))) + (setq olist (cons (nth 1 expr) olist))) + (error nil))))) + (setq files (cdr files))) + (kill-buffer (current-buffer))) + (insert "------------------- Environment follows -------------------\n\n") + (while olist + (if (boundp (car olist)) + (insert "(setq " (symbol-name (car olist)) + (if (or (consp (setq sym (symbol-value (car olist)))) + (and (symbolp sym) + (not (or (eq sym nil) + (eq sym t))))) + " '" " ") + (prin1-to-string (symbol-value (car olist))) ")\n") + (insert ";; (makeunbound '" (symbol-name (car olist)) ")\n")) + (setq olist (cdr olist))) + (insert "\n\n") + ;; Remove any null chars - they seem to cause trouble for some + ;; mailers. (Byte-compiled output from the stuff above.) + (goto-char (point-min)) + (while (re-search-forward "[\000\200]" nil t) + (replace-match "" t t)))) + +(gnus-ems-redefine) + +(provide 'gnus-msg) + +;;; gnus-msg.el ends here