git.fiddlerwoaroof.com
simple_list.lisp
4890f7f7
 ;;;; simple_list.lisp
 
 (in-package #:simple_list)
 
 ;;; "simple_list" goes here. Hacks and glory await!
 
6307d400
 (defvar *imap-socket*)
0efcfbd7
 (defvar *mailing-list*)
 
 (defclass <mailing-list> ()
   ((name :initform nil :initarg :name :accessor ml-name)
    (emails :initform nil :initarg :name :accessor ml-emails)))
 
 (manardb:defmmclass <persistent-mailing-list> ()
   ((name :initform nil :initarg :name :accessor ml-name)
    (emails :initform nil :initarg :emails :accessor ml-emails)) )
 
 (defmethod print-object ((obj <persistent-mailing-list>) s)
   (print-unreadable-object (obj s :type t :identity t)
     (format s "~a with ~d emails" (ml-name obj) (length (ml-emails obj)))))
 
 (defun make-mailing-list (name &optional persistent)
   (aprog1 (make-instance '<persistent-mailing-list>)
     (setf (slot-value it 'name)
           (make-keyword
             (ctypecase name
               (string (string-to-list-name name))
               (symbol name))))))
 
 (defmacro aprogn (&body body)
   `(let*
      ,(loop for el in body
             collect `(it ,el))
      it))
6307d400
 
 (defmacro define-special-variable-wrapper (name var &body value-form)
   (alexandria:with-gensyms (body)
     `(defmacro ,name (() &body ,body)
        (let ((var ',var) (value-form ',value-form))
          `(let ((,var ,@value-form))
             ,@,body)))))
 
0efcfbd7
 (eval-when (:compile-toplevel :load-toplevel :execute)
   (defun get-connection-spec ()
     (clonsigna:make-imap :host (ubiquitous:value 'imap 'host)
                          :port (ubiquitous:value 'imap 'port)
                          :ssl-p (ubiquitous:value 'imap 'ssl-p))))
6307d400
 
 
 (define-special-variable-wrapper with-connection *imap-socket* (get-connection-spec))
0efcfbd7
 (define-special-variable-wrapper with-mailing-lists *mailing-list*
   ((manardb:retrieve-all-instances '<persistent-mailing-list>))
   )
6307d400
 
0efcfbd7
 (defmacro insert-imap-socket (&body body)
   (list* 'progn
          (loop for cmd in body
                collect (list* (car cmd) '*imap-socket* (cdr cmd)))))
6307d400
 
 (defun connect-and-authorize (auth-info)
   (clonsigna:cmd-connect *imap-socket*)
   (clonsigna:cmd-login *imap-socket* (car auth-info) (cdr auth-info)))
 
0efcfbd7
 (defmacro with-authorized-connection ((auth-info)  &body body)
   `(with-connection ()
      (connect-and-authorize ,auth-info)
      ,@body))
 
 (defun connect-toplevel ()
   (setf *imap-socket* (get-connection-spec)))
6307d400
 
 (defun get-mailbox (name)
   (cmd-select *imap-socket* name)
   (mapcar #'princ-to-string
0efcfbd7
           (parse-search (cmd-search *imap-socket* :criteria "(recent unseen)" :uid-p t))))
6307d400
 
0efcfbd7
 (defun get-inbox () (get-mailbox "inbox"))
6307d400
 
 (defun get-headers (uid)
   (parse-fetch-fields (cmd-fetch-fields *imap-socket* uid)))
 
 (defun get-bodystructures (uid)
0efcfbd7
   (let ((headers (get-headers uid)))
6307d400
     (mapcar (lambda (x)
               (make-bodystructure
                 (parse-bodystructure
0efcfbd7
                   (getf x :bodystructure))))
             headers)))
6307d400
 
 (defun get-raw-message (uid)
0efcfbd7
   (let* ((message (split-sequence #\Newline (car (cmd-fetch-body *imap-socket* uid :uid-p t))))
6307d400
          (message-without-imap (slice message 0 -1)))
     (string-join message-without-imap #\Newline)))
 
 (defun get-message (uid)
   (let* ((bodystructures (get-bodystructures uid))
          (bodies (flatten (mapcar #'bodystructure-body-list bodystructures)))
          (sections (flatten (mapcar #'structure-element-section bodies)))
0efcfbd7
          (charsets (flatten (mapcar (fw.lu:alambda
6307d400
                                       (cadr (structure-element-body-parameters it)))
                                     bodies))))
     (parse-fetch-body
       (car (cmd-fetch-body *imap-socket* uid :section sections :uid-p t))
       :charset (car charsets))))
 
 
 (defun lookup-list (list-name &optional (lookup-table *mailing-list*))
   (cdr (assoc list-name lookup-table)))
 
 (defun email-to-list (email)
   (aprogn
     (car (split-sequence #\@ email :count 1))
     (car (split-sequence #\- it    :count 1 :from-end t))))
 
 (defun peek-headers (uid)
   (cmd-fetch *imap-socket* uid
              :criteria (format nil "(~{~a ~}BODY.PEEK[header.fields (~{~a ~})])"
                                '(uid flags bodystructure)
                                '(date from to cc bcc subject message-id in-reply-to references))
              :uid-p t))
 
 (defun send-to-list (list-name from subject message host auth-info)
   (destructuring-bind (recipients . other-headers) (lookup-list list-name)
     (destructuring-bind (user . password) auth-info
       (apply #'cl-smtp:send-email
              (list* host
                     from
                     recipients
                     subject
                     message
                     :ssl t
                     :port 587
                     :authentication `(,user ,password)
                     other-headers)))))
 
 (defun get-message-envelope-info (uid)
   (let* ((envelope (car (parse-fetch-fields (peek-headers uid))))
          (headers (getf envelope :headers))
          )
     (values (getf headers :to)               ; To
             (getf headers :from)             ; From
             (getf headers :subject)             ; From
             (getf envelope :flags)           ; Message Flags
             (getf envelope :bodystructure)   ; Message bodystructure
             headers                          ; Message headers
             envelope)))                      ; Envelope info
 
0efcfbd7
 (defun string-to-list-name (name-string)
   (aprogn name-string
           (string-upcase it)
           (make-keyword it)))
 
6307d400
 (defun process-imap-message (uid host auth-info)
   (multiple-value-bind (to from subject flags) (get-message-envelope-info uid)
     (unless (member 'seen flags)
0efcfbd7
       (let* ((list-name (string-to-list-name (email-to-list to)))
6307d400
              (body (get-message uid)))
         (send-to-list list-name from subject body host auth-info)))))
 
 (defun process-imap-mailbox (name host auth-info)
   (let ((uids (get-mailbox name)))
     (mapcar (lambda (x) (process-imap-message x host auth-info)) uids)))
 
 (defun process-inbox (host auth-info)
   (process-imap-mailbox "inbox" host auth-info))
 
0efcfbd7
 (defun get-auth-info ()
   (cons (ubiquitous:value 'auth 'user)
         (ubiquitous:value 'auth 'password)))
 
 (define-condition simple-list-error () ()
   (:documentation "The base condition for exceptions thrown by this utility"))
 
 (define-condition no-such-list (simple-list-error)
   ((list-name :initarg :name :accessor ml-name))
   (:report (lambda (c s) (format s "No list named ~a" (ml-name c)))))
 
 (defgeneric add-email (mailing-list email)
   (:method :around (mailing-list email)
    (let ((result (call-next-method)))
      (typecase mailing-list
        (symbol result)
        (t mailing-list))))
 
   (:method ((mailing-list <persistent-mailing-list>) email)
    (with-slots (emails) mailing-list
      (unless (member email emails :test #'equalp)
        (push email emails))))
 
   (:method ((list-name symbol) email)
    (let ((lists (remove-if-not
                   (fw.lu:alambda (eq list-name (ml-name it)))
                   (manardb:retrieve-all-instances '<persistent-mailing-list>))))
      (if-let ((mailing-list (car lists)))
        (add-email mailing-list email)
        (error 'no-such-list :name list-name)))))
 
 (defun add-email-to-list (list-name email)
   (restart-case
     (add-email list-name email)
     (make-list () (add-email (make-mailing-list list-name) email))))
 
 (defun main (&optional argv)
6307d400
   (ubiquitous:restore 'simple_list)
0efcfbd7
   (let ((auth-info (get-auth-info))
         (host-name (ubiquitous:value 'imap 'host)))  
     (with-mailing-lists ()
       (format t "Connecting to server ~a" host-name)
       (with-authorized-connection (auth-info)
         (setf +debug+ t)
         (process-inbox host-name auth-info)))))