#!/usr/bin/env hy (import subprocess [run PIPE DEVNULL]) (import threading [Thread]) (import enum [StrEnum]) (import os [path]) (import re) (import json) (import shutil) (setv global-did-delete False) (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 sender None subject None 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))) (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 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") 1 (do (setv global-did-delete True) (msg.tag "-unread" "+deleted") (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)))))) (notmuch "tag" "-notnotified" "*") (when global-did-delete (notmuch "new" "--no-hooks"))