Add signatures

This adds the ability for per-account signatures in the accounts.conf
config file. The signature is added to emails in the editor at the
bottom of the email. This includes when forwarding, replying to, and
composing emails.

There are two config options: signature-file and signature-cmd. The
former allows a signature to be read from a file and the latter allows
an arbitrary command to be executed to return the signature.

The config options have been documented in aerc-config
This commit is contained in:
Jeffas 2019-09-11 20:28:14 +01:00 committed by Drew DeVault
parent a93b4de6f3
commit e2d5c456dc
8 changed files with 84 additions and 9 deletions

View file

@ -29,7 +29,7 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error {
return err return err
} }
acct := aerc.SelectedAccount() acct := aerc.SelectedAccount()
composer := widgets.NewComposer( composer := widgets.NewComposer(aerc,
aerc.Config(), acct.AccountConfig(), acct.Worker(), nil) aerc.Config(), acct.AccountConfig(), acct.Worker(), nil)
tab := aerc.NewTab(composer, "New email") tab := aerc.NewTab(composer, "New email")
composer.OnHeaderChange("Subject", func(subject string) { composer.OnHeaderChange("Subject", func(subject string) {
@ -40,7 +40,7 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error {
} }
tab.Content.Invalidate() tab.Content.Invalidate()
}) })
go composer.SetContents(strings.NewReader(body)) go composer.PrependContents(strings.NewReader(body))
return nil return nil
} }

View file

@ -69,7 +69,7 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
"To": to, "To": to,
"Subject": subject, "Subject": subject,
} }
composer := widgets.NewComposer(aerc.Config(), acct.AccountConfig(), composer := widgets.NewComposer(aerc, aerc.Config(), acct.AccountConfig(),
acct.Worker(), defaults) acct.Worker(), defaults)
addTab := func() { addTab := func() {
@ -154,7 +154,7 @@ func forwardBodyPart(store *lib.MessageStore, composer *widgets.Composer,
pipeout, pipein := io.Pipe() pipeout, pipein := io.Pipe()
scanner := bufio.NewScanner(part.Body) scanner := bufio.NewScanner(part.Body)
go composer.SetContents(pipeout) go composer.PrependContents(pipeout)
// TODO: Let user customize the date format used here // TODO: Let user customize the date format used here
io.WriteString(pipein, fmt.Sprintf("Forwarded message from %s on %s:\n\n", io.WriteString(pipein, fmt.Sprintf("Forwarded message from %s on %s:\n\n",
msg.Envelope.From[0].Name, msg.Envelope.From[0].Name,

View file

@ -116,8 +116,8 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
"In-Reply-To": msg.Envelope.MessageId, "In-Reply-To": msg.Envelope.MessageId,
} }
composer := widgets.NewComposer( composer := widgets.NewComposer(aerc, aerc.Config(),
aerc.Config(), acct.AccountConfig(), acct.Worker(), defaults) acct.AccountConfig(), acct.Worker(), defaults)
if args[0] == "reply" { if args[0] == "reply" {
composer.FocusTerminal() composer.FocusTerminal()
@ -170,7 +170,7 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
pipeout, pipein := io.Pipe() pipeout, pipein := io.Pipe()
scanner := bufio.NewScanner(part.Body) scanner := bufio.NewScanner(part.Body)
go composer.SetContents(pipeout) go composer.PrependContents(pipeout)
// TODO: Let user customize the date format used here // TODO: Let user customize the date format used here
io.WriteString(pipein, fmt.Sprintf("On %s %s wrote:\n", io.WriteString(pipein, fmt.Sprintf("On %s %s wrote:\n",
msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"), msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"),

View file

@ -88,6 +88,7 @@ func unsubscribeMailto(aerc *widgets.Aerc, u *url.URL) error {
"Subject": u.Query().Get("subject"), "Subject": u.Query().Get("subject"),
} }
composer := widgets.NewComposer( composer := widgets.NewComposer(
aerc,
aerc.Config(), aerc.Config(),
acct.AccountConfig(), acct.AccountConfig(),
acct.Worker(), acct.Worker(),

View file

@ -55,6 +55,8 @@ type AccountConfig struct {
Params map[string]string Params map[string]string
Outgoing string Outgoing string
OutgoingCredCmd string OutgoingCredCmd string
SignatureFile string
SignatureCmd string
} }
type BindingConfig struct { type BindingConfig struct {

View file

@ -297,6 +297,14 @@ Note that many of these configuration options are written for you, such as
Specifies an optional command that is run to get the source account's Specifies an optional command that is run to get the source account's
password. See each protocol's man page for more details. password. See each protocol's man page for more details.
*signature-file*
Specifies the file to read in order to obtain the signature to be added
to emails sent from this account.
*signature-cmd*
Specifies the command to execute in *sh* in order to obtain the
signature to be added to emails sent from this account. If the command
fails then *signature-file* is used instead.
# BINDS.CONF # BINDS.CONF

View file

@ -430,7 +430,7 @@ func (aerc *Aerc) Mailto(addr *url.URL) error {
defaults[header] = strings.Join(vals, ",") defaults[header] = strings.Join(vals, ",")
} }
} }
composer := NewComposer(aerc.Config(), composer := NewComposer(aerc, aerc.Config(),
acct.AccountConfig(), acct.Worker(), defaults) acct.AccountConfig(), acct.Worker(), defaults)
composer.FocusSubject() composer.FocusSubject()
title := "New email" title := "New email"

View file

@ -2,6 +2,8 @@ package widgets
import ( import (
"bufio" "bufio"
"bytes"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"mime" "mime"
@ -17,6 +19,7 @@ import (
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors" "github.com/pkg/errors"
"git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/config"
@ -29,6 +32,7 @@ type Composer struct {
acct *config.AccountConfig acct *config.AccountConfig
config *config.AercConfig config *config.AercConfig
aerc *Aerc
defaults map[string]string defaults map[string]string
editor *Terminal editor *Terminal
@ -48,7 +52,7 @@ type Composer struct {
width int width int
} }
func NewComposer(conf *config.AercConfig, func NewComposer(aerc *Aerc, conf *config.AercConfig,
acct *config.AccountConfig, worker *types.Worker, defaults map[string]string) *Composer { acct *config.AccountConfig, worker *types.Worker, defaults map[string]string) *Composer {
if defaults == nil { if defaults == nil {
@ -68,6 +72,7 @@ func NewComposer(conf *config.AercConfig,
} }
c := &Composer{ c := &Composer{
aerc: aerc,
editors: editors, editors: editors,
acct: acct, acct: acct,
config: conf, config: conf,
@ -80,6 +85,8 @@ func NewComposer(conf *config.AercConfig,
focusable: focusable, focusable: focusable,
} }
c.AddSignature()
c.updateGrid() c.updateGrid()
c.ShowTerminal() c.ShowTerminal()
@ -140,6 +147,63 @@ func (c *Composer) SetContents(reader io.Reader) *Composer {
return c return c
} }
func (c *Composer) PrependContents(reader io.Reader) {
buf := bytes.NewBuffer(nil)
c.email.Seek(0, io.SeekStart)
io.Copy(buf, c.email)
c.email.Seek(0, io.SeekStart)
io.Copy(c.email, reader)
io.Copy(c.email, buf)
c.email.Sync()
}
func (c *Composer) AppendContents(reader io.Reader) {
c.email.Seek(0, io.SeekEnd)
io.Copy(c.email, reader)
c.email.Sync()
}
func (c *Composer) AddSignature() {
var signature []byte
if c.acct.SignatureCmd != "" {
var err error
signature, err = c.readSignatureFromCmd()
if err != nil {
signature = c.readSignatureFromFile()
}
} else {
signature = c.readSignatureFromFile()
}
c.AppendContents(bytes.NewReader(signature))
}
func (c *Composer) readSignatureFromCmd() ([]byte, error) {
sigCmd := c.acct.SignatureCmd
cmd := exec.Command("sh", "-c", sigCmd)
signature, err := cmd.Output()
if err != nil {
return nil, err
}
return signature, nil
}
func (c *Composer) readSignatureFromFile() []byte {
sigFile := c.acct.SignatureFile
if sigFile == "" {
return nil
}
sigFile, err := homedir.Expand(sigFile)
if err != nil {
return nil
}
signature, err := ioutil.ReadFile(sigFile)
if err != nil {
c.aerc.PushError(fmt.Sprintf(" Error loading signature from file: %v", sigFile))
return nil
}
return signature
}
func (c *Composer) FocusTerminal() *Composer { func (c *Composer) FocusTerminal() *Composer {
if c.editor == nil { if c.editor == nil {
return c return c