diff options
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | config/aerc.conf | 9 | ||||
-rw-r--r-- | config/config.go | 2 | ||||
-rw-r--r-- | config/hooks.go | 62 | ||||
-rw-r--r-- | config/triggers.go | 61 | ||||
-rw-r--r-- | doc/aerc-config.5.scd | 27 | ||||
-rw-r--r-- | lib/hooks/exec.go | 22 | ||||
-rw-r--r-- | lib/hooks/interface.go | 6 | ||||
-rw-r--r-- | lib/hooks/mail-received.go | 25 | ||||
-rw-r--r-- | lib/msgstore.go | 2 | ||||
-rw-r--r-- | widgets/account.go | 13 |
11 files changed, 143 insertions, 90 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index cf3c7067..4d5b15b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). `aerc.conf`. - Allow basic shell globbing in `[openers]` MIME types. - Dynamic `msglist_*` styling based on email header values in stylesets. +- Add `mail-received` hook. ### Changed @@ -38,11 +39,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Deprecated - `[ui].index-format` setting has been replaced by `index-columns`. -- `[triggers].new-email` now needs to use `aerc-templates(7)` syntax instead - of the (now deprecated) `index-format` placeholders. - `[statusline].render-format` has been replaced by `status-columns`. - Removed support for go < 1.17. - Removed support for `[ui:subject...]` contextual sections in `aerc.conf`. +- `[triggers]` setting has been replaced by `[hooks]`. ## [0.14.0](https://git.sr.ht/~rjarry/aerc/refs/0.14.0) - 2023-01-04 diff --git a/config/aerc.conf b/config/aerc.conf index 02ea96fe..5ff252f5 100644 --- a/config/aerc.conf +++ b/config/aerc.conf @@ -506,16 +506,13 @@ message/rfc822=colorize # text/plain=gvim {} +125 # message/rfc822=thunderbird -[triggers] +[hooks] # -# Triggers specify commands to execute when certain events occur. -# -# Example: -# new-email=exec notify-send "New email from %n" "%s" +# Hooks are triggered whenever the associated event occurs. # # Executed when a new email arrives in the selected folder -#new-email= +#mail-received=notify-send "New mail from $AERC_FROM_NAME" "$AERC_SUBJECT" [templates] # Templates are used to populate email bodies automatically. diff --git a/config/config.go b/config/config.go index de8cd0d0..d70bcfe0 100644 --- a/config/config.go +++ b/config/config.go @@ -132,7 +132,7 @@ func LoadConfigFromFile(root *string, accts []string) error { if err := parseOpeners(file); err != nil { return err } - if err := parseTriggers(file); err != nil { + if err := parseHooks(file); err != nil { return err } if err := parseUi(file); err != nil { diff --git a/config/hooks.go b/config/hooks.go new file mode 100644 index 00000000..9bbd9031 --- /dev/null +++ b/config/hooks.go @@ -0,0 +1,62 @@ +package config + +import ( + "strings" + + "git.sr.ht/~rjarry/aerc/log" + "github.com/go-ini/ini" +) + +type HooksConfig struct { + MailReceived string `ini:"mail-received"` +} + +var Hooks HooksConfig + +func parseHooks(file *ini.File) error { + err := MapToStruct(file.Section("hooks"), &Hooks, true) + if err != nil { + return err + } + + newEmail := file.Section("triggers").Key("new-email").String() + if Hooks.MailReceived == "" && newEmail != "" { + Hooks.MailReceived = convertNewEmailTrigger(newEmail) + Warnings = append(Warnings, Warning{ + Title: "DEPRECATION NOTICE: [triggers].new-email", + Body: ` +The new-email trigger has been replaced by [hooks].email-received. + +Your configuration in this instance was automatically converted to: + +[hooks] +mail-received = ` + Hooks.MailReceived + ` + +Please verify the accuracy of the above translation. + +Your configuration file was not changed. To make this change permanent and to +dismiss this deprecation warning on launch, copy the above lines into aerc.conf +and remove new-email from it. See aerc-config(5) for more details. +`, + }) + } + + log.Debugf("aerc.conf: [hooks] %#v", Hooks) + return nil +} + +func convertNewEmailTrigger(old string) string { + translations := map[string]string{ + "%a": "$AERC_FROM_ADDRESS", + "%n": "$AERC_FROM_NAME", + "%s": "$AERC_SUBJECT", + "%f": "$AERC_FROM_NAME <$AERC_FROM_ADDRESS>", + "%u": `$(echo "$AERC_FROM_ADDRESS" | cut -d@ -f1)`, + "%v": `$(echo "$AERC_FROM_NAME" | cut -d' ' -f1)`, + } + for replace, with := range translations { + old = strings.ReplaceAll(old, replace, with) + } + old = strings.TrimPrefix(old, "exec ") + return strings.ReplaceAll(old, "%%", "%") +} diff --git a/config/triggers.go b/config/triggers.go deleted file mode 100644 index 82750c2d..00000000 --- a/config/triggers.go +++ /dev/null @@ -1,61 +0,0 @@ -package config - -import ( - "github.com/go-ini/ini" - "github.com/google/shlex" - - "git.sr.ht/~rjarry/aerc/lib/format" - "git.sr.ht/~rjarry/aerc/log" -) - -type TriggersConfig struct { - NewEmail []string `ini:"new-email" parse:"ParseNewEmail"` -} - -var Triggers = new(TriggersConfig) - -func parseTriggers(file *ini.File) error { - if err := MapToStruct(file.Section("triggers"), Triggers, true); err != nil { - return err - } - log.Debugf("aerc.conf: [triggers] %#v", Triggers) - return nil -} - -func (t *TriggersConfig) ParseNewEmail(_ *ini.Section, key *ini.Key) ([]string, error) { - cmd := indexFmtRegexp.ReplaceAllStringFunc( - key.String(), - func(s string) string { - runes := []rune(s) - t, _ := indexVerbToTemplate(runes[len(runes)-1]) - return t - }, - ) - args, err := shlex.Split(cmd) - if err != nil { - return nil, err - } - if cmd != key.String() { - log.Warnf("%s %s", - "The new-email trigger now uses templates instead of %-based placeholders.", - "Backward compatibility will be removed in aerc 0.17.") - Warnings = append(Warnings, Warning{ - Title: "FORMAT CHANGED: [triggers].new-email", - Body: ` -The new-email trigger now uses templates instead of %-based placeholders. - -Your configuration in this instance was automatically converted to: - -[triggers] -new-email = ` + format.ShellQuote(args) + ` - -Your configuration file was not changed. To make this change permanent and to -dismiss this warning on launch, replace the above line into aerc.conf. See -aerc-config(5) for more details. - -The automatic conversion of new-email will be removed in aerc 0.17. -`, - }) - } - return args, nil -} diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index 54c7df7f..9e11abfe 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -819,23 +819,26 @@ text/plain=gvim {} +125 message/rfc822=thunderbird ``` -# TRIGGERS +## HOOKS -Triggers specify commands to execute when certain events occur. They are -configured in the *[triggers]* section of _aerc.conf_. +Hooks are triggered whenever the associated event occurs. The commands are run +in a shell environment with information added to environment variables. -The commands are not shell commands (i.e. they are not executed with _sh -c_) -and will be split in multiple arguments following basic shell quoting. They need -to use one of the commands described in *aerc*(1) without the leading colon *:* -(e.g. _exec foo bar_ instead of _:exec foo bar_). +They are configured in the *[hooks]* section of aerc.conf. -*new-email* = _<command>_ - Executed when a new email arrives in the selected folder. Example: +*mail-received* = _<command>_ + Executed when new mail is received in the selected folder. This will + only work reliably with maildir and some imap servers. - exec notify-send 'New email from {{.From | names | join ", "}}' '{{.Subject}}' + Variables: - Templates specifiers from *aerc-templates*(7) are expanded with respect - to the new message. + - *AERC_FROM_NAME* + - *AERC_FROM_ADDRESS* + - *AERC_SUBJECT* + + Example: + + *mail-received* = _notify-send "New mail from $AERC_FROM_NAME" "$AERC_SUBJECT"_ # TEMPLATES diff --git a/lib/hooks/exec.go b/lib/hooks/exec.go new file mode 100644 index 00000000..bea35f32 --- /dev/null +++ b/lib/hooks/exec.go @@ -0,0 +1,22 @@ +package hooks + +import ( + "os" + "os/exec" + + "git.sr.ht/~rjarry/aerc/log" +) + +func RunHook(h HookType) error { + cmd := h.Cmd() + if cmd == "" { + return nil + } + env := h.Env() + log.Debugf("hooks: running command %q (env %v)", cmd, env) + + proc := exec.Command("sh", "-c", cmd) + proc.Env = os.Environ() + proc.Env = append(proc.Env, env...) + return proc.Run() +} diff --git a/lib/hooks/interface.go b/lib/hooks/interface.go new file mode 100644 index 00000000..ed38c3ac --- /dev/null +++ b/lib/hooks/interface.go @@ -0,0 +1,6 @@ +package hooks + +type HookType interface { + Cmd() string + Env() []string +} diff --git a/lib/hooks/mail-received.go b/lib/hooks/mail-received.go new file mode 100644 index 00000000..66622e9e --- /dev/null +++ b/lib/hooks/mail-received.go @@ -0,0 +1,25 @@ +package hooks + +import ( + "fmt" + + "git.sr.ht/~rjarry/aerc/config" + "git.sr.ht/~rjarry/aerc/models" +) + +type MailReceived struct { + MsgInfo *models.MessageInfo +} + +func (m *MailReceived) Cmd() string { + return config.Hooks.MailReceived +} + +func (m *MailReceived) Env() []string { + from := m.MsgInfo.Envelope.From[0] + return []string{ + fmt.Sprintf("AERC_FROM_NAME=%s", from.Name), + fmt.Sprintf("AERC_FROM_ADDRESS=%s", from.Address), + fmt.Sprintf("AERC_SUBJECT=%s", m.MsgInfo.Envelope.Subject), + } +} diff --git a/lib/msgstore.go b/lib/msgstore.go index 38fa82e6..5349aa62 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -271,7 +271,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { } seen := msg.Info.Flags.Has(models.SeenFlag) recent := msg.Info.Flags.Has(models.RecentFlag) - if !seen && recent { + if !seen && recent && msg.Info.Envelope != nil { store.triggerNewEmail(msg.Info) } if _, ok := store.pendingHeaders[msg.Info.Uid]; msg.Info.Envelope != nil && ok { diff --git a/widgets/account.go b/widgets/account.go index fdd33301..fece0846 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -11,6 +11,7 @@ import ( "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/lib" + "git.sr.ht/~rjarry/aerc/lib/hooks" "git.sr.ht/~rjarry/aerc/lib/marker" "git.sr.ht/~rjarry/aerc/lib/sort" "git.sr.ht/~rjarry/aerc/lib/state" @@ -288,14 +289,12 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) { acct.dirlist.UiConfig(name).ReverseThreadOrder, acct.dirlist.UiConfig(name).SortThreadSiblings, func(msg *models.MessageInfo) { - if len(config.Triggers.NewEmail) == 0 { - return - } - err := acct.aerc.cmd( - config.Triggers.NewEmail, - acct.acct, msg) + err := hooks.RunHook(&hooks.MailReceived{ + MsgInfo: msg, + }) if err != nil { - acct.aerc.PushError(err.Error()) + msg := fmt.Sprintf("mail-received hook: %s", err) + acct.aerc.PushError(msg) } }, func() { if acct.dirlist.UiConfig(name).NewMessageBell { |