diff --git a/mail/mail-reader-daemon.hy b/mail/mail-reader-daemon.hy new file mode 100755 index 0000000..45f82fa --- /dev/null +++ b/mail/mail-reader-daemon.hy @@ -0,0 +1,210 @@ +#!/usr/bin/env hy + +(import subprocess [Popen run PIPE DEVNULL]) +(import email.parser :as parser) +(import shutil) +(import csv) +(import sys) +(import os) + +(defclass MailInbox [] + (setv maildir-path None) + (defn __init__ [self maildir-path] + (setv self.maildir-path maildir-path)) + (defn get-new-messages [self folder] + (let [target-dir (+ self.maildir-path "/" folder "/new")] + (lfor file (os.listdir target-dir) + (MailMessage.from-file self (+ target-dir "/" file))))) + (defn find-by-uid [self uid [exclude-msgs None]] + (let [files (str.splitlines + (. (run ["rg" "-Fl" uid self.maildir-path] + :stdout PIPE :text True) stdout)) + output #{}] + (when (is-not exclude-msgs None) + (for [excluded exclude-msgs] + (files.remove (os.path.abspath (excluded.get-full-path))))) + (for [file files] + (let [msg (MailMessage.from-file self file)] + (when (= msg.uid uid) + (output.add msg)))) + output)) + (defn mark-all-read [self uid [exclude-msgs None]] + (let [msgs (self.find-by-uid uid exclude-msgs) + num 0] + (for [msg msgs] + (+= num 1) + (msg.mark-read)) + num)) + (defn mark-all-unread [self uid [exclude-msgs None]] + (let [msgs (self.find-by-uid uid exclude-msgs) + num 0] + (for [msg msgs] + (+= num 1) + (msg.mark-unread)) + num))) + +(defclass MailMessage [] + (setv inbox None + uid None + file None + folder None + sender None + subject None + flags None + read? False + attachment? False + new? False) + (defn __init__ [self inbox uid path sender subject attachment?] + (let [dir-path (os.path.dirname path)] + (setv self.inbox inbox + self.uid uid + self.file (os.path.basename path) + self.folder (os.path.relpath (os.path.dirname dir-path) + inbox.maildir-path) + self.sender sender + self.subject subject + self.flags (MailMessage.get-path-flags self.file) + self.read? (in "S" self.flags) + self.attachment? attachment? + self.new? (= (os.path.basename dir-path) "new")))) + (defn get-dir-path [self] + (+ self.inbox.maildir-path "/" + self.folder "/" + (if self.new? "new" "cur"))) + (defn get-full-path [self] + (+ (self.get-dir-path) "/" self.file)) + (defn move [self new-folder] + (let [clean-new-folder (MailMessage.-clean-folder new-folder)] + (when (!= self.folder clean-new-folder) + (shutil.move (self.get-full-path) + (+ self.inbox.maildir-path "/" + clean-new-folder "/" + (if self.new? "new" "cur") "/" + self.file)) + (setv self.folder clean-new-folder)))) + (defn process [self] + (when self.new? + (shutil.move (self.get-full-path) + (+ self.inbox.maildir-path "/" + self.folder + "/cur/" + self.file)) + (setv self.new? False))) + (defn mark-read [self] + (when (not self.read?) + (self.flags.add "S") + (let [base-name (get self.file (slice (+ (self.file.rindex ",") 1))) + new-name (+ base-name (str.join "" self.flags)) + dir-path (self.get-dir-path)] + (shutil.move (+ dir-path "/" self.file) (+ dir-path "/" new-name)) + (setv self.file new-name + self.read? True)))) + (defn mark-unread [self] + (when self.read? + (self.flags.remove "S") + (let [base-name (get self.file (slice (+ (self.file.rindex ",") 1))) + new-name (+ base-name (str.join "" self.flags)) + dir-path (self.get-dir-path)] + (shutil.move (+ dir-path "/" self.file) (+ dir-path "/" new-name)) + (setv self.file new-name + self.read? False)))) + (defn -parse-from-address [header] + (try + (let [index (str.index header "<")] + (get header (slice 1 (- index 2)))) + (except [ValueError] + header))) + (defn -clean-folder [folder] + (when (str.startswith folder "/") + (setv folder (get folder (slice 1)))) + (when (str.endswith folder "/") + (setv folder (get folder (slice None -1)))) + folder) + (defn -message-has-attachment [mail-obj] + (when (mail-obj.is_multipart) + (for [part (mail-obj.walk)] + (when (str.startswith (part.get "Content-Disposition") "attachment") + (return True)))) + False) + (defn get-path-flags [path] + (set (get path (slice (+ (path.rindex ",") 1) None)))) + (defn from-file [inbox path] + (with [file-obj (open path "r")] + (let [parse (parser.Parser) + mail-obj (parse.parse file-obj :headersonly True)] + (MailMessage inbox + (mail-obj.get "Message-Id") + path + (MailMessage.-parse-from-address (mail-obj.get "From")) + (mail-obj.get "Subject") + (MailMessage.-message-has-attachment mail-obj)))))) + +(defclass INotifyEvent [] + (setv path None + flags #{} + name None) + (defn __init__ [self path flags name] + (setv self.path (if (path.endswith "/") + (get path (slice 0 -1)) + path) + self.flags (set (flags.split ",")) + self.name name)) + (defn get-full-path [self] + (+ self.path "/" self.name)) + (defn dir? [self] + (in "ISDIR" self.flags)) + (defn mail-file? [self] + (and (not (self.dir?)) + (in (os.path.basename self.path) ["new" "cur"]))) + (defn __str__ [self] + (+ "Event(" + self.path "," + (str self.flags) "," + self.name ")"))) + +(defn handle-mail-move [inbox from-event to-event] + (let [from-flags (MailMessage.get-path-flags from-event.name) + to-flags (MailMessage.get-path-flags to-event.name)] + (cond (and (in "S" from-flags) (not-in "S" to-flags)) + (let [to-msg (MailMessage.from-file inbox (to-event.get-full-path))] + (inbox.mark-all-unread to-msg.uid :exclude-msgs [to-msg])) + (and (in "S" to-flags) (not-in "S" from-flags)) + (let [to-msg (MailMessage.from-file inbox (to-event.get-full-path))] + (inbox.mark-all-read to-msg.uid :exclude-msgs [to-msg])) + True + 0))) + +(when (< (len sys.argv) 2) + (print "usage: mail-reader-daemon.hy " :file sys.stderr) + (sys.exit 1)) + +(when (= (get sys.argv 1) "-h") + (print "usage: mail-reader-daemon.hy ") + (sys.exit 0)) + +(with [process (Popen ["inotifywait" + "-mrce" "MOVED_FROM,MOVED_TO" + (get sys.argv 1)] + :stdout PIPE + :stderr DEVNULL + :text True)] + (let [reader (csv.reader process.stdout) + inbox (MailInbox (get sys.argv 1)) + skip-count 0 + from-event None] + (for [csv-line reader] + (let [event (INotifyEvent #* csv-line)] + (setv from-event + (cond (> skip-count 0) + (do + (-= skip-count 1) + None) + (is from-event None) + event + True + (do + (when (from-event.mail-file?) + (setv skip-count (* 2 (handle-mail-move inbox + from-event + event)))) + None))))))) diff --git a/mail/notify-mail.hy b/mail/notify-mail.hy index 21795a8..2d6dd94 100755 --- a/mail/notify-mail.hy +++ b/mail/notify-mail.hy @@ -140,11 +140,11 @@ :time 10000 :actions ["Mark Read" "Delete"]) 0 (do - (msg.mark-read) - (msg.inbox.mark-all-read msg.uid :exclude-msgs [msg])) + (msg.mark-read)) + ;;(msg.inbox.mark-all-read msg.uid :exclude-msgs [msg])) 1 (do (msg.mark-read) - (msg.inbox.mark-all-read msg.uid :exclude-msgs [msg]) + ;;(msg.inbox.mark-all-read msg.uid :exclude-msgs [msg]) (msg.move "Trash"))))) (when (< (len sys.argv) 2)