123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- #!/usr/bin/env hy
- (import email.parser :as parser)
- (import subprocess [run CompletedProcess 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)))))
- (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)]
- (for [msg msgs]
- (msg.mark-read)))))
- (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 -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
- (mail-obj.get "Message-Id")
- 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 (do
- (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.move "Trash")))))
- (when (< (len sys.argv) 2)
- (print "usage: notify-mail.hy <maildir>" :file sys.stderr)
- (sys.exit 1))
- (when (= (get sys.argv 1) "-h")
- (print "usage: notify-mail.hy <maildir>")
- (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)))))
|