More mail stuff

This commit is contained in:
Alexander Rosenberg 2023-05-27 02:11:29 -07:00
parent b613c5b5c5
commit 06234cfeb9
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
5 changed files with 124 additions and 118 deletions

View File

@ -1,82 +1,102 @@
#!/usr/bin/env hy #!/usr/bin/env hy
(import subprocess [run PIPE DEVNULL]) (import email.parser :as parser)
(import subprocess [run PIPE])
(import threading [Thread]) (import threading [Thread])
(import enum [StrEnum])
(import os [path])
(import re)
(import json)
(import shutil) (import shutil)
(import os)
(import sys)
(setv global-did-delete False) (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 OutputType [StrEnum] (defclass MailMessage []
(setv LINES "text" (setv inbox None
JSON "json")) file None
folder None
(defn notmuch [#* args [output None]]
(let [result (run ["notmuch"
#* args]
:stdout (if (is-not output None)
PIPE
DEVNULL)
:text True)]
(match output
OutputType.LINES (str.splitlines result.stdout)
OutputType.JSON (json.loads result.stdout))))
(defclass Message []
(setv uid None
mail-root None
sender None sender None
subject None subject None
flags None
read? False
attachment? False attachment? False
notified? False new? False)
read? False) (defn __init__ [self inbox path sender subject attachment?]
(defn __init__ [self uid mail-root sender subject (let [dir-path (os.path.dirname path)]
attachment? notified? read?] (setv self.inbox inbox
(setv self.uid uid self.file (os.path.basename path)
self.mail-root mail-root self.folder (os.path.relpath (os.path.dirname dir-path)
self.sender sender inbox.maildir-path)
self.subject subject self.sender sender
self.attachment? attachment? self.subject subject
self.notified? notified? self.flags (MailMessage.-get-path-flags self.file)
self.notified? read?)) self.read? (in "S" self.flags)
(defn tag [self #* tags] self.attachment? attachment?
(notmuch "tag" #* tags (+ "id:" self.uid))) self.new? (= (os.path.basename dir-path) "new"))))
(defn move [self dest] (defn get-dir-path [self]
(let [old-path (self.get-filename) (+ self.inbox.maildir-path "/"
name (re.sub "U=[0-9]+:[0-9]+,([DFPRS]*$)" self.folder "/"
r"\1" (path.basename old-path)) (if self.new? "new" "cur")))
new-path (+ self.mail-root "/" dest "/cur/" name)] (defn move [self new-folder]
(shutil.move old-path new-path))) (let [clean-new-folder (MailMessage.-clean-folder new-folder)]
(defn get-filename [self] (when (!= self.folder clean-new-folder)
(get (notmuch "search" "--output=files" (+ "id:" (str self.uid)) (shutil.move (+ (self.get-dir-path) "/" self.file)
:output OutputType.LINES) 0)) (+ self.inbox.maildir-path "/"
(defn from-json [root mail-root] clean-new-folder "/"
(let [uid (get root "id") (if self.new? "new" "cur") "/"
headers (get root "headers") self.file))
sender (parse-from-address (get headers "From")) (setv self.folder clean-new-folder))))
subject (get headers "Subject") (defn process [self]
tags (get root "tags") (when self.new?
attachment? (in "attachment" tags) (shutil.move (+ (self.get-dir-path) "/" self.file)
notified? (not-in "notnotified" tags) (+ self.inbox.maildir-path "/"
read? (not-in "unread" tags)] self.folder
(Message uid mail-root sender subject attachment? notified? read?))) "/cur/"
(defn __str__ [self] self.file))
(+ "Message From \"" self.sender "\": " self.subject (setv self.new? False)))
" (" (defn mark-read [self]
(if self.attachment? "attachment " "") (when (not self.read?)
(if self.notified? "" "un") "notified " (self.flags.add "S")
(if self.read? "" "un") "read" (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)]
(defn parse-from-address [header] (shutil.move (+ dir-path "/" self.file) (+ dir-path "/" new-name))
(try (setv self.file new-name
(let [index (str.index header "<")] self.read? True))))
(get header (slice 1 (- index 2)))) (defn -parse-from-address [header]
(except [ValueError] (try
header))) (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 []]] (defn notify-send [title desc [time 0] [actions []]]
(let [cmd ["notify-send" title desc "-t" (str time)]] (let [cmd ["notify-send" title desc "-t" (str time)]]
@ -89,32 +109,29 @@
(except [ValueError] (except [ValueError]
None))))) None)))))
(defn notify-message [msg] (defn handle-message [msg]
(msg.tag "-notnotified") (msg.process)
(let [result (notify-send (+ (if msg.attachment? "󰈙 " "") (when (not msg.read?)
"New mail from " msg.sender) (match (notify-send (+ (if msg.attachment? "󰈙 " "")
msg.subject "New mail from " msg.sender)
:time 10000 msg.subject
:actions ["Mark Read" "Delete"])] :time 10000
(match result :actions ["Mark Read" "Delete"])
0 (msg.tag "-unread") 0 (msg.mark-read)
1 (do 1 (do
(setv global-did-delete True) (msg.mark-read)
(msg.tag "-unread" "+deleted")
(msg.move "Trash"))))) (msg.move "Trash")))))
(let [mail-root (get (notmuch "config" "get" "database.mail_root" (when (< (len sys.argv) 2)
:output OutputType.LINES) 0) (print "usage: notify-mail.hy <maildir>" :file sys.stderr)
json-root (notmuch "show" "--format=json" "--body=false" (sys.exit 1))
"tag:notnotified" "and"
"tag:unread" "and"
"folder:Inbox"
:output OutputType.JSON)]
(for [msg-json json-root]
(let [msg (Message.from-json (. msg-json [0] [0]) mail-root)]
(Thread.start (Thread :target notify-message
:args #(msg))))))
(notmuch "tag" "-notnotified" "*") (when (= (get sys.argv 1) "-h")
(when global-did-delete (print "usage: notify-mail.hy <maildir>")
(notmuch "new" "--no-hooks")) (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)))))

View File

@ -1,11 +0,0 @@
#!/usr/bin/env zsh
notmuch tag -deleted not folder:Trash
notmuch tag +deleted folder:Trash
notmuch tag -spam not folder:Spam
notmuch tag +spam folder:Spam
notmuch tag -starred not folder:Starred
hy "${HOME}/scripts/mail/notify-mail.hy"

View File

@ -1,11 +0,0 @@
[Unit]
Description=Sync mail with mbsync and notmuch
[Service]
Type=oneshot
ExecStart=-mbsync -a ; notmuch new
RuntimeMaxSec=3m
Restart=on-failure
[Install]
WantedBy=default.target

View File

@ -0,0 +1,11 @@
[Unit]
Description=Sync mail with mbsync and send notifications
[Service]
Type=oneshot
ExecStart=-mbsync -a ; %h/scripts/mail/notify-mail.hy %h/.mail/%i
RuntimeMaxSec=3m
Restart=on-failure
[Install]
WantedBy=default.target

View File

@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Sync mail every minute with mbsync and notmuch Description=Sync mail every minute with mbsync and send notifications
[Timer] [Timer]
OnCalendar=*-*-* *:*:00 OnCalendar=*-*-* *:*:00