git.fiddlerwoaroof.com
Browse code

Initial prototype finished

Still need to implement attachments properly + cleanup

fiddlerwoaroof authored on 16/02/2016 06:33:53
Showing 4 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,2 @@
1
+.*.sw[a-z]
2
+
... ...
@@ -1,5 +1,6 @@
1 1
 ;;;; package.lisp
2 2
 
3 3
 (defpackage #:simple_list
4
-  (:use #:cl))
4
+  (:use #:cl #:anaphora #:alexandria #:serapeum)
5
+  (:export :main))
5 6
 
... ...
@@ -9,6 +9,7 @@
9 9
                #:alexandria
10 10
                #:anaphora
11 11
                #:serapeum
12
+               #:ubiquitous
12 13
                #:fwoar.lisputils)
13 14
   :serial t
14 15
   :components ((:file "package")
... ...
@@ -4,3 +4,139 @@
4 4
 
5 5
 ;;; "simple_list" goes here. Hacks and glory await!
6 6
 
7
+(defvar *imap-socket*)
8
+
9
+(defmacro define-special-variable-wrapper (name var &body value-form)
10
+  (alexandria:with-gensyms (body)
11
+    `(defmacro ,name (() &body ,body)
12
+       (let ((var ',var) (value-form ',value-form))
13
+         `(let ((,var ,@value-form))
14
+            ,@,body)))))
15
+
16
+(defmacro aprogn (&body body)
17
+  `(let*
18
+     ,(loop for el in body
19
+            collect `(it ,el))
20
+     it))
21
+
22
+(defun get-connection-spec ()
23
+  (clonsigna:make-imap :host (ubiquitous:value 'imap 'host)
24
+                       :port (ubiquitous:value 'imap 'port)
25
+                       :ssl-p (ubiquitous:value 'imap 'ssl-p)))
26
+
27
+(define-special-variable-wrapper with-connection *imap-socket* (get-connection-spec))
28
+
29
+(defun connect-toplevel ()
30
+  (setf *imap-socket* (get-connection-spec)))
31
+
32
+(defun connect-and-authorize (auth-info)
33
+  (clonsigna:cmd-connect *imap-socket*)
34
+  (clonsigna:cmd-login *imap-socket* (car auth-info) (cdr auth-info)))
35
+
36
+
37
+(defun get-mailbox (name)
38
+  (cmd-select *imap-socket* name)
39
+  (mapcar #'princ-to-string
40
+          (alexandria:flatten
41
+            (parse-thread (cmd-thread *imap-socket* :uid-p t)))))
42
+
43
+(defun get-inbox ()
44
+  (get-mailbox "inbox"))
45
+
46
+(defun get-headers (uid)
47
+  (parse-fetch-fields (cmd-fetch-fields *imap-socket* uid)))
48
+
49
+(defun get-bodystructures (uid)
50
+  (let ((headers (get-headers x)))
51
+    (mapcar (lambda (x)
52
+              (make-bodystructure
53
+                (parse-bodystructure
54
+                  (getf x :bodystructure)))))))
55
+
56
+(defun get-raw-message (uid)
57
+  (let* ((message (split-sequence #\Newline (car (cmd-fetch-body uid))))
58
+         (message-without-imap (slice message 0 -1)))
59
+    (string-join message-without-imap #\Newline)))
60
+
61
+(defun get-message (uid)
62
+  (let* ((bodystructures (get-bodystructures uid))
63
+         (bodies (flatten (mapcar #'bodystructure-body-list bodystructures)))
64
+         (sections (flatten (mapcar #'structure-element-section bodies)))
65
+         (charsets (flatten (mapcar (fw.lu::alambda
66
+                                      (cadr (structure-element-body-parameters it)))
67
+                                    bodies))))
68
+    (parse-fetch-body
69
+      (car (cmd-fetch-body *imap-socket* uid :section sections :uid-p t))
70
+      :charset (car charsets))))
71
+
72
+(defvar *mailing-list*)
73
+
74
+(defun lookup-list (list-name &optional (lookup-table *mailing-list*))
75
+  (cdr (assoc list-name lookup-table)))
76
+
77
+(defun email-to-list (email)
78
+  (aprogn
79
+    (car (split-sequence #\@ email :count 1))
80
+    (car (split-sequence #\- it    :count 1 :from-end t))))
81
+
82
+(defun peek-headers (uid)
83
+  (cmd-fetch *imap-socket* uid
84
+             :criteria (format nil "(~{~a ~}BODY.PEEK[header.fields (~{~a ~})])"
85
+                               '(uid flags bodystructure)
86
+                               '(date from to cc bcc subject message-id in-reply-to references))
87
+             :uid-p t))
88
+
89
+(defun send-to-list (list-name from subject message host auth-info)
90
+  (destructuring-bind (recipients . other-headers) (lookup-list list-name)
91
+    (destructuring-bind (user . password) auth-info
92
+      (apply #'cl-smtp:send-email
93
+             (list* host
94
+                    from
95
+                    recipients
96
+                    subject
97
+                    message
98
+                    :ssl t
99
+                    :port 587
100
+                    :authentication `(,user ,password)
101
+                    other-headers)))))
102
+
103
+(defun get-message-envelope-info (uid)
104
+  (let* ((envelope (car (parse-fetch-fields (peek-headers uid))))
105
+         (headers (getf envelope :headers))
106
+         )
107
+    (values (getf headers :to)               ; To
108
+            (getf headers :from)             ; From
109
+            (getf headers :subject)             ; From
110
+            (getf envelope :flags)           ; Message Flags
111
+            (getf envelope :bodystructure)   ; Message bodystructure
112
+            headers                          ; Message headers
113
+            envelope)))                      ; Envelope info
114
+
115
+(defun process-imap-message (uid host auth-info)
116
+  (multiple-value-bind (to from subject flags) (get-message-envelope-info uid)
117
+    (unless (member 'seen flags)
118
+      (let* ((list-name (aprogn (email-to-list to)
119
+                                (string-upcase it)
120
+                                (make-keyword it)))
121
+             (body (get-message uid)))
122
+        (send-to-list list-name from subject body host auth-info)))))
123
+
124
+(defun process-imap-mailbox (name host auth-info)
125
+  (let ((uids (get-mailbox name)))
126
+    (mapcar (lambda (x) (process-imap-message x host auth-info)) uids)))
127
+
128
+(defun process-inbox (host auth-info)
129
+  (process-imap-mailbox "inbox" host auth-info))
130
+
131
+(define-special-variable-wrapper with-mailing-lists *mailing-list* (ubiquitous:value 'mailing-lists))
132
+
133
+(defun main ()
134
+  (ubiquitous:restore 'simple_list)
135
+
136
+  (let ((auth-info (cons (ubiquitous:value 'auth 'user)
137
+                         (ubiquitous:value 'auth 'password))))  
138
+    (with-connection ()
139
+      (with-mailing-lists ()
140
+        (connect-and-authorize auth-info)
141
+        (get-inbox)
142
+        (process-inbox (ubiquitous:value 'imap 'host) auth-info)))))