view my-news.el @ 60:77aefd805030

consume pwd on encryption
author Henry S Thompson <ht@inf.ed.ac.uk>
date Fri, 05 Apr 2024 09:11:09 +0100
parents 95ba4cc6ffe4
children 963ac2f8e386
line wrap: on
line source

(message "my-news")
; (debug-on-entry 'gnus-start-news-server)
(setq
      ;gnus-select-method '(nntp "news.usenet.farm")
      ;gnus-post-method '(nntp "usenet.inf.ed.ac.uk")
      gnus-nntp-server nil		; override local default
      )

(setq 	gnus-use-scoring nil		; not used yet
	gnus-summary-gather-subject-limit nil
	gnus-thread-sort-functions
	'(gnus-thread-sort-by-number gnus-thread-sort-by-simpl-subject)
	gnus-summary-line-format "%U%R%5N%I%(%[%4L: %-12,12A%]%) %s\n"
	gnus-summary-make-false-root 'none
	gnus-mime-display-multipart-related-as-mixed t
	gnus-simplify-subject-regexp "^\\(re[:;.]\\| \\|fwd:\\)*")

(defsubst gnus-trim-simplify-subject (text)
  (if (string-match gnus-simplify-subject-regexp text)
      (substring text (match-end 0))
    text))

(defun gnus-thread-sort-by-simpl-subject (h1 h2)
  "sort by slightly simplified subject"
;  (message (format "%s:%s %s:%s" (mail-header-number (gnus-thread-header h1))(mail-header-subject (gnus-thread-header h1))(mail-header-number (gnus-thread-header h2))(mail-header-subject (gnus-thread-header h2))))
  (let ((case-fold-search t))
    (let ((result
    (string-lessp
     (downcase (gnus-trim-simplify-subject (mail-header-subject
					    (gnus-thread-header h1))))
     (downcase (gnus-trim-simplify-subject (mail-header-subject
					    (gnus-thread-header h2)))))))
;      (message (format " %s\n" result))
      result)))


(setq nnfolder-get-new-mail nil
      nnfolder-inhibit-expiry t
      gnus-secondary-select-methods
      '((nnml "ht"
	      (gnus-show-threads nil)
	      (gnus-article-sort-functions
	       (gnus-article-sort-by-subject gnus-article-sort-by-date))
	      )))
;;; fixup clarinews
;(autoload 'gnus-clarinews-fun "clari-clean" "Clean ClariNews articles" t)
;(add-hook 'gnus-article-prepare-hook 'gnus-clarinews-fun)


(defun gnus-Subject-sort-by-subject-and-date (reverse)
  "Sort subject display buffer by subject alphabetically. `Re:'s are ignored.
If case-fold-search is non-nil, case of letters is ignored.  Date is used
if subjects are equal
Argument REVERSE means reverse order."
  (interactive "P")
  (gnus-summary-sort-summary
   (function
    (lambda (a b)
      (let ((s-a (gnus-trim-simplify-subject (nntp-header-subject a)))
	    (s-b (gnus-trim-simplify-subject (nntp-header-subject b)))
	    )
	(or (gnus-string-lessp s-a s-b)
	    (and (gnus-string-equal s-a s-b)
		 (gnus-date-lessp (nntp-header-date a)
				  (nntp-header-date b)))))))
   reverse
   ))

;(require 'util-mde) ; for string-replace-regexp-2


(defun gnus-string-equal (a b)
  "Return T if first arg string is equal than second in lexicographic order.
If case-fold-search is non-nil, case of letters is ignored."
  (if case-fold-search
      (string-equal (downcase a) (downcase b)) (string-equal a b)))

(defun gnus-Group-update-and-vanish ()
  "update newsrc and restore config pre-group selection"
  (interactive)
  (gnus-group-force-update)
  (if gnus-pre-config
      (set-window-configuration gnus-pre-config))
;  (setq gnus-pre-config nil)
  )

;; Database stuff
(defun open-white ()
  (setq whitelist-db (open-database (concat my-mail-dir "/white") 'berkeley-db)))
(defun save-white ()
  (close-database whitelist-db)
  (open-white))

(defun open-ad ()
  (setq adlist-db (open-database (concat my-mail-dir "/ad") 'berkeley-db)))

(defun save-ad ()
  (close-database adlist-db)
  (open-ad))

(defun open-quaker ()
  (setq quaker-db (open-database (concat my-mail-dir "/quaker") 'berkeley-db)))
(defun save-quaker ()
  (close-database quaker-db)
  (open-quaker))

(defvar database-names '(whitelist-db adlist-db quaker-db) "sic")

(defun db-status (&optional name)
  "Check on the whereabouts of a name"
  (interactive)
  (let ((addr
	 (or name
	     (progn
	       (gnus-summary-goto-article (gnus-summary-article-number))
	       (get-canonical-from-addr (get-current-from-components)))))
	res)
    (dolist (dbn database-names)
      (if (get-database addr (eval dbn))
	  (setq res (cons dbn res))))
    (if name
	res
      (message "%s" res))))

(defun add-white (&optional addToBBDB)
  (interactive "P")
  (gnus-summary-goto-article (gnus-summary-article-number))
  (let* ((components (get-current-from-components))
	 (addr (get-canonical-from-addr components)))
    (if (new-white addr)
	(save-white))
    (if addToBBDB
	(let ((bbdb-no-duplicates-p t))
	  (bbdb-create-internal (car components) nil (cadr components)
				nil nil nil)))))

(defun add-ad ()             
  (interactive)                 
  (gnus-summary-goto-article (gnus-summary-article-number)) 
  (let ((addr (get-current-from-addr)))
    (if (or (not (get-database addr whitelist-db))
	    (yes-or-no-p "Already white, really convert to ad?"))
	(if (new-ad addr)
	    (save-ad)))))
 
(defun add-quaker()
  (interactive)
  (let ((addr (get-addr-before-point)))
    (when (new-quaker addr)
      (save-quaker))
    (quaker-sig-maybe)))

; not needed anymore because of gnus-posting-styles (q.v. in gnus-init)
(defun quaker-sig-if-to-quaker ()
  (let ((message-options))
    (save-excursion (message-options-set-recipient))
    (let* ((recipStr (message-options-get 'message-recipients))
	   (recips (split-string (downcase recipStr)
				 ",[ \f\t\n\r\v]+" t)))
      (while (and recips
		  (not (quaker-sig-if-quaker-1 (car recips))))
	(setq recips (cdr recips))))))

(defun to-quaker-p ()
  (let ((message-options))
    (save-excursion (message-options-set-recipient))
    (let* ((recipStr (message-options-get 'message-recipients))
	   (recips (split-string (downcase recipStr)
				 ",[ \f\t\n\r\v]+" t)))
      (while (and recips
		  (not (get-database (car recips) quaker-db)))
	(setq recips (cdr recips)))
      (not (null recips)))))

(defun quaker-sig-if-quaker ()
  (quaker-sig-if-quaker-1 (get-addr-before-point)))
    
(defun quaker-sig-if-quaker-1 (addr)
  (if (get-database addr quaker-db)
      (progn (quaker-sig-maybe)
	     t)))

(defun kill-white ()             
  (interactive)                 
  (gnus-summary-goto-article (gnus-summary-article-number)) 
  (let ((addr (downcase (get-current-from-addr))))
    (rem-white addr)))

(defun kill-ad ()             
  (interactive)                 
  (gnus-summary-goto-article (gnus-summary-article-number)) 
  (let ((addr (downcase (get-current-from-addr))))
    (rem-ad addr)))

(defun get-from-gnus-addr ()
  (get-from-addr (gnus-fetch-field "From")))

(defun get-from-addr (addr)
  (get-canonical-from-addr (gnus-extract-address-components addr)))

(defun get-canonical-from-addr (components)
  (downcase (cadr components)))
 
(defun get-current-from-addr () 
  (with-current-buffer gnus-article-buffer 
    (get-from-gnus-addr))) 

(defun get-current-from-components () 
  (with-current-buffer gnus-article-buffer 
    (gnus-extract-address-components (gnus-fetch-field "From"))))

(defun get-addr-before-point ()
  (let ((cur (point)))
    (save-excursion
      (get-from-addr (buffer-substring (+ (search-backward " ") 1) cur)))
    ))

(defun blacken-and-delete (group)
  ;; mis-named now
  ;; this is part of the expiry processing for xxxSPAM groups, and
  ;; actually whitens the from addresses of #-marked articles
  ;; The return value is crucial (and crucially outside of the scope of the if)
  (if  (memq number
	     (with-current-buffer gnus-summary-buffer
	       gnus-newsgroup-processable))
      (let ((addr (get-from-gnus-addr)))
	(new-white addr)))
  'delete)

(defun unwhiten-and-delete (group)
  ;; unused except in stale groups -- usable as an expiry
  (if (memq number
	    (with-current-buffer gnus-summary-buffer
	      gnus-newsgroup-processable))
      (let ((addr (get-from-gnus-addr)))
	(remove-database addr whitelist-db)))
  'delete)

(defun known-black (list)
  (if (get-database (get-from-gnus-addr) blacklist-db)
      list))

(defun white-spam (list)
  (if (or (equal (get-database (get-from-gnus-addr) whitelist-db) "t")
	  (let ((case-fold-search t)
		(subj (gnus-fetch-field "Subject"))
		(from (get-from-gnus-addr)))
	    (or
	     (and subj (string-match white-subjects subj))
	     (and from
		  (let ((fromDom (substring from (+ 1 (search "@" from)))))
		    (and fromDom (member fromDom white-domains)))))))
      list))

(defun ad-spam (list)
  (if (let ((from (get-from-gnus-addr)))
	(or
	 (equal (get-database from adlist-db) "t")
	 (and from
	      (let ((fromDom (substring from (+ 1 (search "@" from)))))
		(and fromDom (member fromDom ad-domains))))
       ))
      list))

(defun bogoNote (group)
  (if  (memq number
	     (with-current-buffer gnus-summary-buffer
	       gnus-newsgroup-processable))
      (let ((addr (get-from-gnus-addr)))
	(new-white addr)))
  (shell-command-on-region (point-min) (point-max)
			   "/afs/inf.ed.ac.uk/user/h/ht/share/bin/local/makeBogo")
  'delete)

(defun whiten-recip ()
  ;;; a hook for outgoing mail
  (let* ((recips (message-options-get 'message-recipients))
         (res (mapcar (function new-white)
		      (split-string (downcase recips)
				    ",[ \f\t\n\r\v]*" t))))
    (while (and res (not (car res)))
      (setq res (cdr res)))
    (if res (save-white))))


(defun new-white (addr)
  (if (get-database addr whitelist-db)
      nil
    (put-database addr "t" whitelist-db)
    t))

(defun new-ad (addr)
  (if (get-database addr adlist-db)
      nil
    (put-database addr "t" adlist-db)
    t))

(defun rem-ad (addr)
  (remove-database addr adlist-db)
  (save-ad))

(defun new-quaker (addr)
  (if (get-database addr quaker-db)
      nil
    (put-database addr "t" quaker-db)
    t))

(defun rem-white (addr)
 (remove-database (downcase addr) whitelist-db)
 (save-white))

(defun bogoOK (group)
  (shell-command-on-region (point-min) (point-max)
			   "/afs/inf.ed.ac.uk/user/h/ht/share/bin/local/makeNonBogo")
  'delete)

(defun del-dups ()
  (interactive)
  (gnus-summary-sort-by-subject)
  (gnus-summary-clear-mark-forward 1)
  (goto-char (point-min))
  (let ((pos))
    (while (setq pos (search-forward "] " nil t))
      (end-of-line)
      (let ((subj (buffer-substring pos (point))))
	(unless (equal subj "")
	  (let ((target (if (< (length subj) 26)
			    (concat "] " subj "\n")
			  (concat "] " (substring subj 0 25))))
		(done 0)
		(case-fold-search nil))
	  (while (and (= done 0)
		      (search-forward target nil t))
	    (forward-char -3)
	    (setq done (gnus-summary-mark-as-read-forward 1))))))))
  (gnus-summary-limit-to-unread)
  (gnus-summary-sort-by-original))

(defun mark-and-mark (n)
  (interactive "p")
  (while (>= n 1)
    (gnus-summary-mark-as-read)
    (gnus-summary-mark-as-processable 1)
    (setq n (- n 1))))

(defun split-on-whole-field (field pat list)
  (goto-char (point-max))
  (let ((hit (assq pat wsp-cache))
	rpat)
    (if hit
	(setq rpat (cdr hit))
      (setq rpat 
	 (concat "^"
		 field
		 ":\\s-*"
		 (if (stringp pat)
		     pat
		   (cdr (assq pat
			      nnmail-split-abbrev-alist)))
		 "$"))
      (setq wsp-cache (cons (cons pat rpat) wsp-cache)))
    (if (re-search-backward rpat nil t)
	list)))

(defun ht-gnus-summary-delete-forward ()
  "REAL delete for nnmail gnus"  
  (interactive)
  (gnus-summary-delete-article)
  (gnus-summary-next-unread-article))

;; run the first time we make a summary window
(defun gnus-summary-mode-fun1 ()
  "install ht's mods"
  (define-key gnus-summary-mode-map "D" 'ht-gnus-summary-delete-forward)
  (define-key gnus-summary-mode-map "~" 'mark-and-mark)
  (define-key gnus-summary-mode-map "\M-d" 'gnus-edit-and-move-to-diary)
  (define-key gnus-summary-mode-map "\M-e" 'gnus-extract-attachment)
  (define-key gnus-summary-mode-map "\M-w" 'add-white)
  (define-key gnus-summary-mode-map [(control meta w)] 'copy-region-to-kill)
  (define-key gnus-summary-mode-map "\M-h" 'showMPAhtml)
  ;(define-key gnus-summary-mode-map [(control meta w)] 'kill-white)
  (define-key gnus-summary-mode-map "\M-a" 'add-ad)
  (define-key gnus-summary-mode-map "\M-n" 'ht-next-unseen-maybe)
  (define-key gnus-summary-mode-map "\M-c" 'ht-catchup-and-next-unseen)
  (define-key gnus-summary-mime-map "O" 'ht-article-save-parts)
  (define-key gnus-summary-backend-map "M" 'ht-move-to-pers)
  (remove-hook 'gnus-summary-mode-hook 'gnus-summary-mode-fun1))

(defun message-mode-fun1 ()
  (define-key message-mode-map [(control meta q)] 'add-quaker)
  (remove-hook 'message-mode-hook 'message-mode-fun1))

(defun ht-catchup-and-next-unseen ()
  (interactive)
  (when (gnus-summary-catchup nil t nil 'fast)
    (gnus-summary-exit)
    (previous-line 1)
    (ht-next-with-unseen 1)))    

(defun ht-next-unseen-maybe (n)
  (interactive "p")
  (cond
     ((eq (gnus-summary-next-unread-subject n) n)
      (gnus-summary-exit)
      (previous-line 1)
      (if (ht-next-with-unseen n)
	  (ht-read-group-unseen-only)))))

(defun ht-gnus-pers-refresh (n)
  (interactive "p")
  (let ((gn (concat "nnml+ht:pers-"
		    (format-time-string "%Y-%m" (current-time))))
	(jr ht-gnus-just-read))
    (gnus-group-get-new-news)
    (let ((nn (gnus-number-of-unseen-articles-in-group gn)))
      (gnus-group-goto-group gn)
      (cond
       ((> nn 0)
	(gnus-group-read-group nn))
       ((> n 1)
	(let ((gnus-auto-select-subject
	       (lambda ()
		 (goto-char (point-max))
		 (previous-line 1))))
	  (gnus-group-read-group nil t)))
       (t (goto-char (point-min))
	  (ht-next-with-unseen 1))))
    (message "read: %s" ht-gnus-just-read)
  ))

(defun no-select ()
  (if (member gnus-newsgroup-name no-select-groups)
      (progn (make-variable-buffer-local 'gnus-auto-select-first)
	     (setq gnus-auto-select-first nil))))

(defun showMPAhtml ()
  "Show the text/html parts of an multipart/alternative message using lynx"
  (interactive)
  (gnus-summary-select-article)
  (with-current-buffer gnus-original-article-buffer
    (shell-command-on-region (point-min) (point-max)
			     ;(expand-file-name
			     "/home/ht/bin/showMPA.sh"
			     ;)
    ))
  )


;; run the first time we make a group window
(defun gnus-group-mode-fun1 ()
  "install ht's mods"
  (require 'gnus-msg)
  (define-key gnus-group-mode-map "\M-\C-g" 'ht-gnus-pers-refresh)
  (define-key gnus-group-mode-map "\M-n" 'ht-next-with-unseen)
  (define-key gnus-group-mode-map "\M-p" 'ht-previous-with-unseen)
  (define-key gnus-group-mode-map "\M- " 'ht-read-group-unseen-only)
  (define-key gnus-send-bounce-map "R" 'resend-to-schemadev)
  (define-key gnus-send-bounce-map "x" 'flush-all-nogoods)
  (remove-hook 'gnus-group-mode-hook 'gnus-group-mode-fun1))

(defun flush-all-nogoods ()
  (interactive)
  (while (re-search-forward
	  "] \\(\\(Returned\\|\\([Uu]n\\|[Nn]on-?\\)deliver\\(able\\|ed\\)\\)\\( [Mm]ail\\|:?\\)\\|DELIVERY FAILURE\\|Delivery \\(Notification: Delivery has failed\\|Status Notification .\\(Failure\\|Delay\\).\\)\\|failure \\(notice\\|delivery\\)\\)"
	  nil t)
    (gnus-summary-mark-as-read)
    (end-of-line)))

(defun gnus-user-format-function-t (header)
  "display the to field (for archive messages)"
  (let ((n (mail-header-number header)))
    (with-current-buffer nntp-server-buffer
      (save-excursion
	(save-restriction
	  (let ((inhibit-point-motion-hooks t))
	    (goto-char (point-min))
	    (let ((beg (search-forward (format " %d Article retrieved." n)))
		  (end (search-forward "\n.\n")))
	      (narrow-to-region beg end)
	      (goto-char beg)
	      (message-fetch-field "To"))))))))

(defun gnus-extract-attachment ()
  "extract attachments from a multi-part mime message"
  (interactive)
  (let ((sm gnus-show-mime))
    (if sm
	(progn (setq gnus-show-mime nil)
	       (gnus-summary-select-article t 'force))
        )
    (gnus-summary-show-all-headers)
    (with-current-buffer gnus-article-buffer
      (save-excursion
	(save-restriction
	  (mime/viewer-mode)
	  (delete-other-windows)
	  (let ((pt 0))
	    (while (progn
		     (mime-viewer/next-content)
		     (and
		      (equal "*Preview-*Article**" (buffer-name (current-buffer)))
		      (not (= pt (point)))))
	      (setq pt (point))
	      (if (looking-at "^\\[[0-9]* \\([^ ]+ \\)+<")
		  (mime-viewer/extract-content)))))))
    (kill-buffer "*Preview-*Article**")
    (setq gnus-show-mime sm)
    ))

;;; Why???
(make-variable-buffer-local 'gnus-extra-headers)
(make-variable-buffer-local 'nnmail-extra-headers)


(defun resend-to-schemadev ()
  (interactive)
  (message "forwarding to xmlschema-dev")
  (gnus-summary-resend-message "xmlschema-dev@w3.org" 1)
  (gnus-summary-next-unread-article))

(defun brutal-resend ()
  (interactive)
  (message "editing for resend. . .")
  (unless (eq (gnus-summary-article-number)
	      gnus-current-article)
    (gnus-summary-select-article t))
  (gnus-summary-toggle-header 1)
  (with-current-buffer gnus-article-buffer
    (toggle-read-only)
    (gnus-article-date-original)
    (goto-char (point-min))
    (replace-regexp "^\\(X-Diagnostic\\|X-Envelope-To\\|X-Original-To\\|Delivered-To\\):.*\n" "")
    (goto-char (point-min))
    (gnus-summary-edit-article-done
     (or (mail-header-references gnus-current-headers) "")
     (gnus-group-read-only-p) gnus-summary-buffer nil))
  (call-interactively (function gnus-summary-resend-message))
  (gnus-summary-next-unread-article))

; (unless (fboundp 'builtin-coding-system-p)
;   (fset 'builtin-coding-system-p (symbol-function 'coding-system-p))
;   (defun coding-system-p (obj)
;     (cond
;      ((builtin-coding-system-p obj) t)
;      ((memq obj '(utf-8 gb2312 koi8-r iso-8859-1))
;       (message (format "Coding system: %s" obj))
;       t))))

;;; dangerous hack to improve display of names and subjects in mail/news
(if nil (progn
(require 'mm-util)
(defun mm-decode-coding-string (str cs)
  (if (and str (eq cs 'utf-8))
      (if (or (string-match "Â" str)
	      (string-match "Ã" str))
	(let* ((r 0)			; read pointer
	       (w 0)			; write pointer
	       (l (length str)))
	  (while (< r l)
	    (let* ((c (aref str r))
		   (i (char-int c)))
	      (cond ((= i 194)
		     (aset str w (aref str (+ r 1)))
		     (setq r (+ r 2)))
		    ((= i 195)
		     (aset str w
			   (int-char
			    (+ 64
			       (char-int (aref str (+ r 1))))))
		     (setq r (+ r 2)))
		    (t
		     (aset str w c)
		     (setq r (+ r 1)))))
	    (setq w (+ w 1)))
	  (substring str 0 w))
	  str)
    str))

(defun mm-sort-coding-systems-predicate (a b)
  ;; from mm-util, abort if no priorities
  (or (not mm-coding-system-priorities)
      (let ((priorities
	     (mapcar (lambda (cs)
		       ;; Note: invalid entries are dropped silently
		       (and (setq cs (mm-coding-system-p cs))
			    (coding-system-base cs)))
		     mm-coding-system-priorities)))
	(and (setq a (mm-coding-system-p a))
	     (if (setq b (mm-coding-system-p b))
		 (> (length (memq (coding-system-base a) priorities))
		    (length (memq (coding-system-base b) priorities)))
	       t)))))))

(require 'browse-url)

;;; This version collects extra lines if you use right-button
;;; to click on a URL
(defun browse-url (url &rest args)
  "Ask a WWW browser to load URL.
Prompts for a URL, defaulting to the URL at or before point.  Variable
`browse-url-browser-function' says which browser to use."
  (interactive (browse-url-interactive-arg "URL: "))
  (unless (interactive-p)
    (setq args (or args (list browse-url-new-window-flag))))
  (if (and (boundp 'event)(= 3 (event-button event)))
      (let ((thisLine url))
	(while (and (progn (forward-char (length thisLine))
			   (eolp))
		    (progn (forward-line 1)
			   (beginning-of-line)
			   (not (looking-at "\\s-"))))
	  (looking-at "\\S-*")
	  (setq thisLine (buffer-substring (match-beginning 0)
					   (match-end 0)))
	  (setq url (concat url thisLine)))))
  (if (functionp browse-url-browser-function)
      (apply browse-url-browser-function url args)
    ;; The `function' can be an alist; look down it for first match
    ;; and apply the function (which might be a lambda).
    (catch 'done
      (dolist (bf browse-url-browser-function)
	(when (string-match (car bf) url)
	  (apply (cdr bf) url args)
	  (throw 'done t)))
      (error "No browse-url-browser-function matching URL %s"
	     url))))

(defun gnus-user-format-function-H (dummy)
  (format "%c"
	  (cond ((eq gnus-tmp-summary-live ?*)
		 ?*)
		((> (gnus-number-of-unseen-articles-in-group gnus-tmp-group) 0)
		 ?.)
		(t ? ))))

(defun ht-next-with-unseen (n)
  (interactive "p")
  (let* ((gvl (mapcar (function string-to-number)
		      (split-string gnus-version-number "\\.")))
	 (pattern (if (or (> (car gvl) 5)
			  (and (eq (car gvl) 5)
			       (or (> (cadr gvl) 10)
				   (and (eq (cadr gvl) 10)
					(> (caddr gvl) 7)))))
		      "\\."
		    ":\\.")))		      
    (if (looking-at pattern)
	(if (< n 0)
	    (backward-char 1)
	  (forward-char 1)))
    (let ((missing 0)
	  (winning (looking-at pattern)))
      (while (and (zerop missing)
		  (not winning))
	(setq missing (gnus-group-next-unread-group n))
	(setq winning (looking-at pattern)))
      winning)))

(defun ht-read-group-unseen-only ()
  (interactive)
  (gnus-group-read-group
   (gnus-number-of-unseen-articles-in-group (gnus-group-group-name))))

(defun ht-previous-with-unseen (n)
  (interactive "p")
  (ht-next-with-unseen (- n)))

(defvar ht-stash-directory (concat my-mail-dir "/stash/"))

(defun ht-save-part (handle n)
  (let ((sup-type (mm-handle-media-supertype handle))
	(sub-type (mm-handle-media-subtype handle)))
    (message (format "%s %s/%s" n sup-type sub-type))
    (cond ((and (equal sup-type "multipart")
		(or (equal sub-type "alternative")
		    (equal sub-type "related")))
	   (let ((alts (cddr handle))
		 (j 0))
	     (while alts
	       (let* ((alt (pop alts))
		      (handle-type (mm-handle-type alt)))
		 (let* ((sub (mm-handle-media-subtype alt))
			(ext (cdr
			      (assoc sub '(("calendar" . "vcs")
					   ("v-calendar" . "vcs"))))))
		   (setq j (+ j 1))
		   (if (not (or (mail-content-type-get
				 (mm-handle-disposition alt) 'filename)
				(mail-content-type-get
				 handle-type 'name)))
		       (nconc
			handle-type
			(list (cons 'name (format "%s.%s.%s"
						  n j (or ext sub))))))
		   (ht-save-part alt (format "%s.%s" n j)))))))
	  ((and (equal sup-type "text")(not
					(member sub-type '("html"
							   "v-calendar"
							   "calendar"))))
	   (message "Skipping text part: %s" (mm-handle-disposition handle)))
	  (t
	   (mm-save-part handle)))))

(defun ht-move-to-pers (n)
  (interactive "p")
  (gnus-summary-move-article n
			     (concat
			      "nnml+ht:pers-"
			      (format-time-string "%Y-%m" (current-time)))))

(defun ht-article-save-parts (n)
  "Save non t/p MIME parts starting at N, which is the numerical prefix."
  (interactive "p2")
  (let ((window (get-buffer-window gnus-article-buffer 'visible))
	frame)
    (when window
      ;; It is necessary to select the article window so that
      ;; `gnus-article-goto-part' may really move the point.
      (setq frame (selected-frame))
      (gnus-select-frame-set-input-focus (window-frame window))
      (unwind-protect
	  (save-window-excursion
	    (select-window window)
	    (let ((len (length gnus-article-mime-handle-alist)))
	      (setq mm-default-directory ht-stash-directory)
	      (while (<= n len)
		(gnus-article-goto-part n)
		(let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
		  (ht-save-part handle n))
		(setq n (+ n 1))
		)))
	(gnus-select-frame-set-input-focus frame))))
  )


(defun gnus-article-part-wrapper (n function)
  (let ((window (get-buffer-window gnus-article-buffer 'visible))
	frame)
    (when window
      ;; It is necessary to select the article window so that
      ;; `gnus-article-goto-part' may really move the point.
      (setq frame (selected-frame))
      (gnus-select-frame-set-input-focus (window-frame window))
      (unwind-protect
	  (save-window-excursion
	    (select-window window)
	    (when (> n (length gnus-article-mime-handle-alist))
	      (error "No such part"))
	    (gnus-article-goto-part n)
	    (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
	      (funcall function handle)))
	(gnus-select-frame-set-input-focus frame)))))

(defun mhstore-me (dir)
  (interactive (list (read-directory-name "Save parts to " "/tmp" "/tmp" t)))
  (let ((art (gnus-summary-article-number)))
    (let* ((grp-parts (split-string gnus-newsgroup-name ":"))
	   (meth (car grp-parts))
	   (grp (cadr grp-parts)))
      (if (string= meth "nnml+ht")
	(let ((doit
		(format (concat "cd %s && mhstore -f "
				my-mail-dir "/Mail/%s/%s) -auto")
			dir grp art)))
	  (message doit)
	  (shell-command doit))
	))))

(defun my-message-send-and-exit (&optional arg)
  (interactive "P")
  (let ((message-required-mail-headers
	 (if arg
	     (mapcar
	      (lambda(x)
		(if(and(consp x)(eq(cdr x)'In-Reply-To))
		    (cons 'optional 'xyzzy)
		  x))
	      message-required-mail-headers)
	   message-required-mail-headers)))
    (orig-message-send-and-exit)))

(require 'message)
(if (not (fboundp 'orig-message-send-and-exit))
     (progn
       (fset 'orig-message-send-and-exit (symbol-function 'message-send-and-exit))
       (fset 'message-send-and-exit (symbol-function 'my-message-send-and-exit))))

;; see message-citation-line-function in message.el
(defun safe-citation ()
  (use-text-not-html)
  (when message-reply-headers
    (let ((from (mail-header-from message-reply-headers)))
      (cond ((string-match "^\"?\\([^\"]*\\)\"? <.*>$" from)
	     (insert (match-string 1 from) " writes:\n\n"))
	    ((string-match "^\\([^<@]*\\)@" from)
	     (insert (match-string 1 from) " writes:\n\n"))
	    (t
	     (insert "[anon] writes:\n\n"))))))

(defun use-text-not-html (&optional clear)
  (when (and (if clear (looking-at "<html")
	       (looking-at "> <html"))
	     (bufferp (get-buffer "*Shell Command Output*")))
    ;; replace HTML only with result of my HTML filter
    (delete-region (point)(mark t))
    (insert-buffer "*Shell Command Output*")
    (when (looking-at "piping")
      (kill-entire-line)
      (indent-rigidly (point) (mark t) -3)
      (if (not clear)
	  (submerge-region (point) (mark t)))))
  )

(setq message-citation-line-function (function safe-citation))


(defvar safelink_pat "https://[a-z0-9.]*safelinks.protection.outlook.com/\\?url=\\(https?%3A%2F%2F[^&<>\"]*\\)[^\"<> ]*")

(require 'url)

(defvar url-ok-chars (nconc
		      '(?/ ?& ?% ?+ ?? ?= ?: ?;
			?#
			)
			url-unreserved-chars))

(defun unsafelink ()
  ;; Thanks to Iain Murray for
  ;; /public/homepages/imurray2/web/code/hacks/unsafelink
  (let ((url-unreserved-chars url-ok-chars))
    (while (re-search-forward safelink_pat nil t)
      (let ((res (match-string 1)))
	(replace-match "")
	;; unhexify uses regex, so trashes match-string :-(
	(insert (url-hexify-string (url-unhex-string res)))
	))
    ))

(add-hook 'gnus-article-prepare-hook 'unsafelink)

(provide 'my-news)