Browse code
Initial prototype finished
Still need to implement attachments properly + cleanup
fiddlerwoaroof authored on 16/02/2016 06:33:53Showing 4 changed files
... | ... |
@@ -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))))) |