From 06234cfeb9b5de8756c3780845d4f13f5ad1fe63 Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Sat, 27 May 2023 02:11:29 -0700 Subject: [PATCH] More mail stuff --- mail/notify-mail.hy | 207 ++++++++++-------- mail/notmuch-post.sh | 11 - systemd/sync-mail.service | 11 - systemd/sync-mail@.service | 11 + systemd/{sync-mail.timer => sync-mail@.timer} | 2 +- 5 files changed, 124 insertions(+), 118 deletions(-) delete mode 100755 mail/notmuch-post.sh delete mode 100644 systemd/sync-mail.service create mode 100644 systemd/sync-mail@.service rename systemd/{sync-mail.timer => sync-mail@.timer} (68%) diff --git a/mail/notify-mail.hy b/mail/notify-mail.hy index 7d8f1fe..438222e 100755 --- a/mail/notify-mail.hy +++ b/mail/notify-mail.hy @@ -1,82 +1,102 @@ #!/usr/bin/env hy -(import subprocess [run PIPE DEVNULL]) +(import email.parser :as parser) +(import subprocess [run PIPE]) (import threading [Thread]) -(import enum [StrEnum]) -(import os [path]) -(import re) -(import json) (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] - (setv LINES "text" - JSON "json")) - -(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 +(defclass MailMessage [] + (setv inbox None + file None + folder None sender None subject None + flags None + read? False attachment? False - notified? False - read? False) - (defn __init__ [self uid mail-root sender subject - attachment? notified? read?] - (setv self.uid uid - self.mail-root mail-root - self.sender sender - self.subject subject - self.attachment? attachment? - self.notified? notified? - self.notified? read?)) - (defn tag [self #* tags] - (notmuch "tag" #* tags (+ "id:" self.uid))) - (defn move [self dest] - (let [old-path (self.get-filename) - name (re.sub "U=[0-9]+:[0-9]+,([DFPRS]*$)" - r"\1" (path.basename old-path)) - new-path (+ self.mail-root "/" dest "/cur/" name)] - (shutil.move old-path new-path))) - (defn get-filename [self] - (get (notmuch "search" "--output=files" (+ "id:" (str self.uid)) - :output OutputType.LINES) 0)) - (defn from-json [root mail-root] - (let [uid (get root "id") - headers (get root "headers") - sender (parse-from-address (get headers "From")) - subject (get headers "Subject") - tags (get root "tags") - attachment? (in "attachment" tags) - notified? (not-in "notnotified" tags) - read? (not-in "unread" tags)] - (Message uid mail-root sender subject attachment? notified? read?))) - (defn __str__ [self] - (+ "Message From \"" self.sender "\": " self.subject - " (" - (if self.attachment? "attachment " "") - (if self.notified? "" "un") "notified " - (if self.read? "" "un") "read" - ")"))) - -(defn parse-from-address [header] - (try - (let [index (str.index header "<")] - (get header (slice 1 (- index 2)))) - (except [ValueError] - header))) + 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)]] @@ -89,32 +109,29 @@ (except [ValueError] None))))) -(defn notify-message [msg] - (msg.tag "-notnotified") - (let [result (notify-send (+ (if msg.attachment? "󰈙 " "") - "New mail from " msg.sender) - msg.subject - :time 10000 - :actions ["Mark Read" "Delete"])] - (match result - 0 (msg.tag "-unread") +(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 - (setv global-did-delete True) - (msg.tag "-unread" "+deleted") + (msg.mark-read) (msg.move "Trash"))))) -(let [mail-root (get (notmuch "config" "get" "database.mail_root" - :output OutputType.LINES) 0) - json-root (notmuch "show" "--format=json" "--body=false" - "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)))))) +(when (< (len sys.argv) 2) + (print "usage: notify-mail.hy " :file sys.stderr) + (sys.exit 1)) -(notmuch "tag" "-notnotified" "*") -(when global-did-delete - (notmuch "new" "--no-hooks")) +(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))))) diff --git a/mail/notmuch-post.sh b/mail/notmuch-post.sh deleted file mode 100755 index 9e5c3bb..0000000 --- a/mail/notmuch-post.sh +++ /dev/null @@ -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" diff --git a/systemd/sync-mail.service b/systemd/sync-mail.service deleted file mode 100644 index 17c4edf..0000000 --- a/systemd/sync-mail.service +++ /dev/null @@ -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 diff --git a/systemd/sync-mail@.service b/systemd/sync-mail@.service new file mode 100644 index 0000000..8ec1b9f --- /dev/null +++ b/systemd/sync-mail@.service @@ -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 diff --git a/systemd/sync-mail.timer b/systemd/sync-mail@.timer similarity index 68% rename from systemd/sync-mail.timer rename to systemd/sync-mail@.timer index e5e6ee0..1b9fa4c 100644 --- a/systemd/sync-mail.timer +++ b/systemd/sync-mail@.timer @@ -1,5 +1,5 @@ [Unit] -Description=Sync mail every minute with mbsync and notmuch +Description=Sync mail every minute with mbsync and send notifications [Timer] OnCalendar=*-*-* *:*:00