compose: warn before sending without attachment
Prevent the embarrassing forgotten attachment scenario by warning the user before sending a message that may need an attachment but does not have one. Whether a message needs an attachment is determined by testing a configurable regex against the message body. Signed-off-by: Jason Cox <dev@jasoncarloscox.com> Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
parent
8ffcd3e5ad
commit
7647dfb8b4
7 changed files with 106 additions and 6 deletions
|
@ -22,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Support for attaching files with `mailto:`-links
|
||||
- Filter commands now have the `AERC_MIME_TYPE` and `AERC_FILENAME` variables
|
||||
defined in their environment.
|
||||
- Warn before sending emails that may need an attachment with
|
||||
`no-attachment-warning` in `aerc.conf`.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -100,6 +100,40 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
rcpts: rcpts,
|
||||
}
|
||||
|
||||
warn, err := composer.ShouldWarnAttachment()
|
||||
if err != nil || warn {
|
||||
msg := "You may have forgotten an attachment."
|
||||
if err != nil {
|
||||
logging.Warnf("failed to check for a forgotten attachment: %v", err)
|
||||
msg = "Failed to check for a forgotten attachment."
|
||||
}
|
||||
|
||||
prompt := widgets.NewPrompt(aerc.Config(),
|
||||
msg+" Abort send? [Y/n] ",
|
||||
func(text string) {
|
||||
if text == "n" || text == "N" {
|
||||
send(aerc, composer, ctx, header, tabName)
|
||||
}
|
||||
}, func(cmd string) ([]string, string) {
|
||||
if cmd == "" {
|
||||
return []string{"y", "n"}, ""
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
},
|
||||
)
|
||||
|
||||
aerc.PushPrompt(prompt)
|
||||
} else {
|
||||
send(aerc, composer, ctx, header, tabName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func send(aerc *widgets.Aerc, composer *widgets.Composer, ctx sendCtx,
|
||||
header *mail.Header, tabName string,
|
||||
) {
|
||||
// we don't want to block the UI thread while we are sending
|
||||
// so we do everything in a goroutine and hide the composer from the user
|
||||
aerc.RemoveTab(composer)
|
||||
|
@ -109,6 +143,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
mode.NoQuit()
|
||||
|
||||
var copyBuf bytes.Buffer // for the Sent folder content if CopyTo is set
|
||||
config := composer.Config()
|
||||
|
||||
failCh := make(chan error)
|
||||
// writer
|
||||
|
@ -116,6 +151,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
defer logging.PanicHandler()
|
||||
|
||||
var sender io.WriteCloser
|
||||
var err error
|
||||
switch ctx.scheme {
|
||||
case "smtp":
|
||||
fallthrough
|
||||
|
@ -151,7 +187,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
// leave no-quit mode
|
||||
defer mode.NoQuitDone()
|
||||
|
||||
err = <-failCh
|
||||
err := <-failCh
|
||||
if err != nil {
|
||||
aerc.PushError(strings.ReplaceAll(err.Error(), "\n", " "))
|
||||
aerc.NewTab(composer, tabName)
|
||||
|
@ -176,7 +212,6 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
composer.SetSent()
|
||||
composer.Close()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func listRecipients(h *mail.Header) ([]*mail.Address, error) {
|
||||
|
|
|
@ -297,6 +297,18 @@ address-book-cmd=
|
|||
# Default: true
|
||||
reply-to-self=true
|
||||
|
||||
#
|
||||
# Warn before sending an email that matches the specified regexp but does not
|
||||
# have any attachments. Leave empty to disable this feature.
|
||||
#
|
||||
# Uses Go's regexp syntax, documented at https://golang.org/s/re2syntax. The
|
||||
# "(?im)" flags are set by default (case-insensitive and multi-line).
|
||||
#
|
||||
# Example:
|
||||
# no-attachment-warning=^[^>]*attach(ed|ment)
|
||||
#
|
||||
no-attachment-warning=
|
||||
|
||||
[filters]
|
||||
#
|
||||
# Filters allow you to pipe an email body through a shell command to render
|
||||
|
|
|
@ -203,10 +203,11 @@ type BindingConfigContext struct {
|
|||
}
|
||||
|
||||
type ComposeConfig struct {
|
||||
Editor string `ini:"editor"`
|
||||
HeaderLayout [][]string `ini:"-"`
|
||||
AddressBookCmd string `ini:"address-book-cmd"`
|
||||
ReplyToSelf bool `ini:"reply-to-self"`
|
||||
Editor string `ini:"editor"`
|
||||
HeaderLayout [][]string `ini:"-"`
|
||||
AddressBookCmd string `ini:"address-book-cmd"`
|
||||
ReplyToSelf bool `ini:"reply-to-self"`
|
||||
NoAttachmentWarning *regexp.Regexp `ini:"-"`
|
||||
}
|
||||
|
||||
type FilterConfig struct {
|
||||
|
@ -523,6 +524,18 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
|
|||
if key == "header-layout" {
|
||||
config.Compose.HeaderLayout = parseLayout(val)
|
||||
}
|
||||
|
||||
if key == "no-attachment-warning" && len(val) > 0 {
|
||||
re, err := regexp.Compile("(?im)" + val)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Invalid no-attachment-warning '%s': %w",
|
||||
val, err,
|
||||
)
|
||||
}
|
||||
|
||||
config.Compose.NoAttachmentWarning = re
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -493,6 +493,20 @@ These options are configured in the *[compose]* section of aerc.conf.
|
|||
|
||||
Default: true
|
||||
|
||||
*no-attachment-warning*
|
||||
Specifies a regular expression against which an email's body should be
|
||||
tested before sending an email with no attachment. If the regexp
|
||||
matches, aerc will warn you before sending the message. Leave empty to
|
||||
disable this feature.
|
||||
|
||||
Uses Go's regexp syntax, documented at https://golang.org/s/re2syntax.
|
||||
The "(?im)" flags are set by default (case-insensitive and multi-line).
|
||||
|
||||
Example:
|
||||
no-attachment-warning=^[^>]\*attach(ed|ment)
|
||||
|
||||
Default: none
|
||||
|
||||
## FILTERS
|
||||
|
||||
Filters allow you to pipe an email body through a shell command to render
|
||||
|
|
|
@ -598,6 +598,10 @@ func (aerc *Aerc) BeginExCommand(cmd string) {
|
|||
aerc.focus(exline)
|
||||
}
|
||||
|
||||
func (aerc *Aerc) PushPrompt(prompt *ExLine) {
|
||||
aerc.prompts.Push(prompt)
|
||||
}
|
||||
|
||||
func (aerc *Aerc) RegisterPrompt(prompt string, cmd []string) {
|
||||
p := NewPrompt(aerc.conf, prompt, func(text string) {
|
||||
if text != "" {
|
||||
|
|
|
@ -776,6 +776,26 @@ func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Composer) ShouldWarnAttachment() (bool, error) {
|
||||
regex := c.config.Compose.NoAttachmentWarning
|
||||
|
||||
if regex == nil || len(c.attachments) > 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err := c.reloadEmail()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "reloadEmail")
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(c.email)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "io.ReadAll")
|
||||
}
|
||||
|
||||
return regex.Match(body), nil
|
||||
}
|
||||
|
||||
func writeMsgImpl(c *Composer, header *mail.Header, writer io.Writer) error {
|
||||
if len(c.attachments) == 0 && len(c.textParts) == 0 {
|
||||
// no attachments
|
||||
|
|
Loading…
Reference in a new issue