#!/usr/bin/env hy (import email.parser :as parser) (import subprocess [run PIPE]) (import threading [Thread]) (import shutil) (import os) (import sys) (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)))))) (defclass MailMessage [] (setv inbox None file None folder None sender None subject None flags None read? False attachment? False new? False) (defn __init__ [self inbox path sender subject attachment?] (let [dir-path (os.path.dirname path)] (setv self.inbox inbox 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 move [self new-folder] (let [clean-new-folder (MailMessage.-clean-folder new-folder)] (when (!= self.folder clean-new-folder) (shutil.move (+ (self.get-dir-path) "/" self.file) (+ 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-dir-path) "/" self.file) (+ 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 -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 -get-path-flags [path] (set (get path (slice (+ (path.rindex ",") 1) None)))) (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 from-file [inbox path] (with [file-obj (open path "r")] (let [parse (parser.Parser) mail-obj (parse.parse file-obj :headersonly True)] (MailMessage inbox path (MailMessage.-parse-from-address (mail-obj.get "From")) (mail-obj.get "Subject") (MailMessage.-message-has-attachment mail-obj)))))) (defn notify-send [title desc [time 0] [actions []]] (let [cmd ["notify-send" title desc "-t" (str time)]] (for [action actions] (cmd.append "-A") (cmd.append action)) (let [result (run cmd :stdout PIPE :text True)] (try (int result.stdout) (except [ValueError] None))))) (defn handle-message [msg] (msg.process) (when (not msg.read?) (match (notify-send (+ (if msg.attachment? "󰈙 " "") "New mail from " msg.sender) msg.subject :time 10000 :actions ["Mark Read" "Delete"]) 0 (msg.mark-read) 1 (do (msg.mark-read) (msg.move "Trash"))))) (when (< (len sys.argv) 2) (print "usage: notify-mail.hy " :file sys.stderr) (sys.exit 1)) (when (= (get sys.argv 1) "-h") (print "usage: notify-mail.hy ") (sys.exit 0)) (let [mail-inbox (MailInbox (get sys.argv 1)) new-msgs (mail-inbox.get-new-messages "Inbox")] (for [msg new-msgs] (Thread.start (Thread :target handle-message :args #(msg)))))