2019-05-21 22:31:04 +02:00
|
|
|
package widgets
|
|
|
|
|
|
|
|
import (
|
2019-05-22 16:40:08 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-06-25 01:36:11 +02:00
|
|
|
"net"
|
2019-05-21 22:31:04 +02:00
|
|
|
"net/url"
|
2019-05-22 16:40:08 +02:00
|
|
|
"os"
|
2019-05-22 17:13:55 +02:00
|
|
|
"os/exec"
|
2019-05-22 16:40:08 +02:00
|
|
|
"path"
|
2019-06-25 01:36:11 +02:00
|
|
|
"strconv"
|
2019-05-21 22:31:04 +02:00
|
|
|
"strings"
|
|
|
|
|
2020-11-30 23:07:03 +01:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2019-05-22 16:40:08 +02:00
|
|
|
"github.com/go-ini/ini"
|
|
|
|
"github.com/kyoh86/xdg"
|
2019-05-21 22:31:04 +02:00
|
|
|
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/config"
|
|
|
|
"git.sr.ht/~rjarry/aerc/lib/ui"
|
2019-05-21 22:31:04 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
CONFIGURE_BASICS = iota
|
|
|
|
CONFIGURE_INCOMING = iota
|
|
|
|
CONFIGURE_OUTGOING = iota
|
|
|
|
CONFIGURE_COMPLETE = iota
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
IMAP_OVER_TLS = iota
|
|
|
|
IMAP_STARTTLS = iota
|
|
|
|
IMAP_INSECURE = iota
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
SMTP_OVER_TLS = iota
|
|
|
|
SMTP_STARTTLS = iota
|
|
|
|
SMTP_INSECURE = iota
|
|
|
|
)
|
|
|
|
|
|
|
|
type AccountWizard struct {
|
|
|
|
ui.Invalidatable
|
2019-06-05 00:05:46 +02:00
|
|
|
aerc *Aerc
|
|
|
|
conf *config.AercConfig
|
|
|
|
step int
|
|
|
|
steps []*ui.Grid
|
|
|
|
focus int
|
|
|
|
temporary bool
|
2019-05-21 22:31:04 +02:00
|
|
|
// CONFIGURE_BASICS
|
|
|
|
accountName *ui.TextInput
|
|
|
|
email *ui.TextInput
|
|
|
|
fullName *ui.TextInput
|
|
|
|
basics []ui.Interactive
|
|
|
|
// CONFIGURE_INCOMING
|
|
|
|
imapUsername *ui.TextInput
|
|
|
|
imapPassword *ui.TextInput
|
|
|
|
imapServer *ui.TextInput
|
|
|
|
imapMode int
|
|
|
|
imapStr *ui.Text
|
|
|
|
imapUrl url.URL
|
|
|
|
incoming []ui.Interactive
|
|
|
|
// CONFIGURE_OUTGOING
|
|
|
|
smtpUsername *ui.TextInput
|
|
|
|
smtpPassword *ui.TextInput
|
|
|
|
smtpServer *ui.TextInput
|
|
|
|
smtpMode int
|
|
|
|
smtpStr *ui.Text
|
|
|
|
smtpUrl url.URL
|
|
|
|
copySent bool
|
|
|
|
outgoing []ui.Interactive
|
|
|
|
// CONFIGURE_COMPLETE
|
|
|
|
complete []ui.Interactive
|
|
|
|
}
|
|
|
|
|
2019-05-22 16:40:08 +02:00
|
|
|
func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
|
2019-05-21 22:31:04 +02:00
|
|
|
wizard := &AccountWizard{
|
2022-07-03 17:11:12 +02:00
|
|
|
accountName: ui.NewTextInput("", &conf.Ui).Prompt("> "),
|
2019-05-22 16:40:08 +02:00
|
|
|
aerc: aerc,
|
|
|
|
conf: conf,
|
2019-06-05 00:05:46 +02:00
|
|
|
temporary: false,
|
2019-05-22 16:40:08 +02:00
|
|
|
copySent: true,
|
2022-07-03 17:11:12 +02:00
|
|
|
email: ui.NewTextInput("", &conf.Ui).Prompt("> "),
|
|
|
|
fullName: ui.NewTextInput("", &conf.Ui).Prompt("> "),
|
|
|
|
imapPassword: ui.NewTextInput("", &conf.Ui).Prompt("] ").Password(true),
|
|
|
|
imapServer: ui.NewTextInput("", &conf.Ui).Prompt("> "),
|
2020-07-27 10:03:55 +02:00
|
|
|
imapStr: ui.NewText("imaps://", conf.Ui.GetStyle(config.STYLE_DEFAULT)),
|
2022-07-03 17:11:12 +02:00
|
|
|
imapUsername: ui.NewTextInput("", &conf.Ui).Prompt("> "),
|
|
|
|
smtpPassword: ui.NewTextInput("", &conf.Ui).Prompt("] ").Password(true),
|
|
|
|
smtpServer: ui.NewTextInput("", &conf.Ui).Prompt("> "),
|
2020-07-27 10:03:55 +02:00
|
|
|
smtpStr: ui.NewText("smtps://", conf.Ui.GetStyle(config.STYLE_DEFAULT)),
|
2022-07-03 17:11:12 +02:00
|
|
|
smtpUsername: ui.NewTextInput("", &conf.Ui).Prompt("> "),
|
2019-05-21 22:31:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Autofill some stuff for the user
|
|
|
|
wizard.email.OnChange(func(_ *ui.TextInput) {
|
|
|
|
value := wizard.email.String()
|
|
|
|
wizard.imapUsername.Set(value)
|
|
|
|
wizard.smtpUsername.Set(value)
|
|
|
|
if strings.ContainsRune(value, '@') {
|
|
|
|
server := value[strings.IndexRune(value, '@')+1:]
|
|
|
|
wizard.imapServer.Set(server)
|
|
|
|
wizard.smtpServer.Set(server)
|
|
|
|
}
|
|
|
|
wizard.imapUri()
|
|
|
|
wizard.smtpUri()
|
|
|
|
})
|
|
|
|
wizard.imapServer.OnChange(func(_ *ui.TextInput) {
|
2019-06-18 14:07:51 +02:00
|
|
|
imapServerURI := wizard.imapServer.String()
|
|
|
|
smtpServerURI := imapServerURI
|
|
|
|
if strings.HasPrefix(imapServerURI, "imap.") {
|
|
|
|
smtpServerURI = strings.Replace(imapServerURI, "imap.", "smtp.", 1)
|
|
|
|
}
|
|
|
|
wizard.smtpServer.Set(smtpServerURI)
|
2019-05-21 22:31:04 +02:00
|
|
|
wizard.imapUri()
|
|
|
|
wizard.smtpUri()
|
|
|
|
})
|
|
|
|
wizard.imapUsername.OnChange(func(_ *ui.TextInput) {
|
|
|
|
wizard.smtpUsername.Set(wizard.imapUsername.String())
|
|
|
|
wizard.imapUri()
|
|
|
|
wizard.smtpUri()
|
|
|
|
})
|
|
|
|
wizard.imapPassword.OnChange(func(_ *ui.TextInput) {
|
|
|
|
wizard.smtpPassword.Set(wizard.imapPassword.String())
|
|
|
|
wizard.imapUri()
|
|
|
|
wizard.smtpUri()
|
|
|
|
})
|
|
|
|
wizard.smtpServer.OnChange(func(_ *ui.TextInput) {
|
|
|
|
wizard.smtpUri()
|
|
|
|
})
|
|
|
|
wizard.smtpUsername.OnChange(func(_ *ui.TextInput) {
|
|
|
|
wizard.smtpUri()
|
|
|
|
})
|
|
|
|
wizard.smtpPassword.OnChange(func(_ *ui.TextInput) {
|
|
|
|
wizard.smtpUri()
|
|
|
|
})
|
|
|
|
|
|
|
|
basics := ui.NewGrid().Rows([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(8)}, // Introduction
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Account name (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Full name (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Email address (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2019-05-21 22:31:04 +02:00
|
|
|
}).Columns([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2019-05-21 22:31:04 +02:00
|
|
|
})
|
|
|
|
basics.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("\nWelcome to aerc! Let's configure your account.\n\n"+
|
|
|
|
"This wizard supports basic IMAP & SMTP configuration.\n"+
|
|
|
|
"For other configurations, use <Ctrl+q> to exit and read the "+
|
|
|
|
"aerc-config(5) man page.\n"+
|
|
|
|
"Press <Tab> and <Shift+Tab> to cycle between each field in this form, "+
|
|
|
|
"or <Ctrl+j> and <Ctrl+k>.",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_DEFAULT)))
|
2019-05-21 22:31:04 +02:00
|
|
|
basics.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Name for this account? (e.g. 'Personal' or 'Work')",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(1, 0)
|
|
|
|
basics.AddChild(wizard.accountName).
|
|
|
|
At(2, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
basics.AddChild(ui.NewFill(' ', tcell.StyleDefault)).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(3, 0)
|
|
|
|
basics.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Full name for outgoing emails? (e.g. 'John Doe')",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(4, 0)
|
|
|
|
basics.AddChild(wizard.fullName).
|
|
|
|
At(5, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
basics.AddChild(ui.NewFill(' ', tcell.StyleDefault)).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(6, 0)
|
|
|
|
basics.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Your email address? (e.g. 'john@example.org')",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(7, 0)
|
|
|
|
basics.AddChild(wizard.email).
|
|
|
|
At(8, 0)
|
2022-07-03 17:11:12 +02:00
|
|
|
selector := NewSelector([]string{"Next"}, 0, &conf.Ui).
|
2019-06-25 01:36:11 +02:00
|
|
|
OnChoose(func(option string) {
|
|
|
|
email := wizard.email.String()
|
|
|
|
if strings.ContainsRune(email, '@') {
|
|
|
|
server := email[strings.IndexRune(email, '@')+1:]
|
|
|
|
hostport, srv := getSRV(server, []string{"imaps", "imap"})
|
|
|
|
if hostport != "" {
|
|
|
|
wizard.imapServer.Set(hostport)
|
|
|
|
if srv == "imaps" {
|
|
|
|
wizard.imapMode = IMAP_OVER_TLS
|
|
|
|
} else {
|
|
|
|
wizard.imapMode = IMAP_STARTTLS
|
|
|
|
}
|
|
|
|
wizard.imapUri()
|
|
|
|
}
|
2022-07-29 21:42:02 +02:00
|
|
|
hostport, _ = getSRV(server, []string{"submission"})
|
2019-06-25 01:36:11 +02:00
|
|
|
if hostport != "" {
|
|
|
|
wizard.smtpServer.Set(hostport)
|
|
|
|
wizard.smtpMode = SMTP_STARTTLS
|
|
|
|
wizard.smtpUri()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wizard.advance(option)
|
|
|
|
})
|
2020-07-27 10:03:56 +02:00
|
|
|
basics.AddChild(selector).At(9, 0)
|
2019-05-21 22:31:04 +02:00
|
|
|
wizard.basics = []ui.Interactive{
|
2020-07-27 10:03:56 +02:00
|
|
|
wizard.accountName, wizard.fullName, wizard.email, selector,
|
2019-05-21 22:31:04 +02:00
|
|
|
}
|
|
|
|
basics.OnInvalidate(func(_ ui.Drawable) {
|
|
|
|
wizard.Invalidate()
|
|
|
|
})
|
|
|
|
|
|
|
|
incoming := ui.NewGrid().Rows([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(3)}, // Introduction
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Username (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Password (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Server (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Connection mode (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(2)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Connection string
|
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2019-05-21 22:31:04 +02:00
|
|
|
}).Columns([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2019-05-21 22:31:04 +02:00
|
|
|
})
|
2020-07-27 10:03:55 +02:00
|
|
|
incoming.AddChild(ui.NewText("\nConfigure incoming mail (IMAP)",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_DEFAULT)))
|
2019-05-21 22:31:04 +02:00
|
|
|
incoming.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Username",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(1, 0)
|
|
|
|
incoming.AddChild(wizard.imapUsername).
|
|
|
|
At(2, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
incoming.AddChild(ui.NewFill(' ', tcell.StyleDefault)).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(3, 0)
|
|
|
|
incoming.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Password",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(4, 0)
|
|
|
|
incoming.AddChild(wizard.imapPassword).
|
|
|
|
At(5, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
incoming.AddChild(ui.NewFill(' ', tcell.StyleDefault)).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(6, 0)
|
|
|
|
incoming.AddChild(
|
|
|
|
ui.NewText("Server address "+
|
2020-07-27 10:03:55 +02:00
|
|
|
"(e.g. 'mail.example.org' or 'mail.example.org:1313')",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(7, 0)
|
|
|
|
incoming.AddChild(wizard.imapServer).
|
|
|
|
At(8, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
incoming.AddChild(ui.NewFill(' ', tcell.StyleDefault)).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(9, 0)
|
|
|
|
incoming.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Connection mode",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(10, 0)
|
2020-07-27 10:03:56 +02:00
|
|
|
imapMode := NewSelector([]string{
|
2019-05-21 22:31:04 +02:00
|
|
|
"IMAP over SSL/TLS",
|
|
|
|
"IMAP with STARTTLS",
|
|
|
|
"Insecure IMAP",
|
2022-07-03 17:11:12 +02:00
|
|
|
}, 0, &conf.Ui).Chooser(true).OnSelect(func(option string) {
|
2019-05-21 22:31:04 +02:00
|
|
|
switch option {
|
|
|
|
case "IMAP over SSL/TLS":
|
|
|
|
wizard.imapMode = IMAP_OVER_TLS
|
|
|
|
case "IMAP with STARTTLS":
|
|
|
|
wizard.imapMode = IMAP_STARTTLS
|
|
|
|
case "Insecure IMAP":
|
|
|
|
wizard.imapMode = IMAP_INSECURE
|
|
|
|
}
|
|
|
|
wizard.imapUri()
|
|
|
|
})
|
|
|
|
incoming.AddChild(imapMode).At(11, 0)
|
2022-07-03 17:11:12 +02:00
|
|
|
selector = NewSelector([]string{"Previous", "Next"}, 1, &conf.Ui).
|
2019-05-21 22:31:04 +02:00
|
|
|
OnChoose(wizard.advance)
|
2021-10-26 22:42:07 +02:00
|
|
|
incoming.AddChild(ui.NewFill(' ', tcell.StyleDefault)).At(12, 0)
|
2019-05-21 22:31:04 +02:00
|
|
|
incoming.AddChild(wizard.imapStr).At(13, 0)
|
2020-07-27 10:03:56 +02:00
|
|
|
incoming.AddChild(selector).At(14, 0)
|
2019-05-21 22:31:04 +02:00
|
|
|
wizard.incoming = []ui.Interactive{
|
|
|
|
wizard.imapUsername, wizard.imapPassword, wizard.imapServer,
|
2020-07-27 10:03:56 +02:00
|
|
|
imapMode, selector,
|
2019-05-21 22:31:04 +02:00
|
|
|
}
|
|
|
|
incoming.OnInvalidate(func(_ ui.Drawable) {
|
|
|
|
wizard.Invalidate()
|
|
|
|
})
|
|
|
|
|
|
|
|
outgoing := ui.NewGrid().Rows([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(3)}, // Introduction
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Username (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Password (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Server (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Connection mode (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(2)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Connection string
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Padding
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)}, // Copy to sent (label)
|
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(2)}, // (input)
|
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2019-05-21 22:31:04 +02:00
|
|
|
}).Columns([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2019-05-21 22:31:04 +02:00
|
|
|
})
|
2020-07-27 10:03:55 +02:00
|
|
|
outgoing.AddChild(ui.NewText("\nConfigure outgoing mail (SMTP)",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_DEFAULT)))
|
2019-05-21 22:31:04 +02:00
|
|
|
outgoing.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Username",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(1, 0)
|
|
|
|
outgoing.AddChild(wizard.smtpUsername).
|
|
|
|
At(2, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
outgoing.AddChild(ui.NewFill(' ', tcell.StyleDefault)).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(3, 0)
|
|
|
|
outgoing.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Password",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(4, 0)
|
|
|
|
outgoing.AddChild(wizard.smtpPassword).
|
|
|
|
At(5, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
outgoing.AddChild(ui.NewFill(' ', tcell.StyleDefault)).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(6, 0)
|
|
|
|
outgoing.AddChild(
|
|
|
|
ui.NewText("Server address "+
|
2020-07-27 10:03:55 +02:00
|
|
|
"(e.g. 'mail.example.org' or 'mail.example.org:1313')",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(7, 0)
|
|
|
|
outgoing.AddChild(wizard.smtpServer).
|
|
|
|
At(8, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
outgoing.AddChild(ui.NewFill(' ', tcell.StyleDefault)).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(9, 0)
|
|
|
|
outgoing.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Connection mode",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).
|
2019-05-21 22:31:04 +02:00
|
|
|
At(10, 0)
|
2020-07-27 10:03:56 +02:00
|
|
|
smtpMode := NewSelector([]string{
|
2019-05-21 22:31:04 +02:00
|
|
|
"SMTP over SSL/TLS",
|
|
|
|
"SMTP with STARTTLS",
|
|
|
|
"Insecure SMTP",
|
2022-07-03 17:11:12 +02:00
|
|
|
}, 0, &conf.Ui).Chooser(true).OnSelect(func(option string) {
|
2019-05-21 22:31:04 +02:00
|
|
|
switch option {
|
|
|
|
case "SMTP over SSL/TLS":
|
|
|
|
wizard.smtpMode = SMTP_OVER_TLS
|
|
|
|
case "SMTP with STARTTLS":
|
|
|
|
wizard.smtpMode = SMTP_STARTTLS
|
|
|
|
case "Insecure SMTP":
|
|
|
|
wizard.smtpMode = SMTP_INSECURE
|
|
|
|
}
|
|
|
|
wizard.smtpUri()
|
|
|
|
})
|
|
|
|
outgoing.AddChild(smtpMode).At(11, 0)
|
2022-07-03 17:11:12 +02:00
|
|
|
selector = NewSelector([]string{"Previous", "Next"}, 1, &conf.Ui).
|
2019-05-21 22:31:04 +02:00
|
|
|
OnChoose(wizard.advance)
|
2021-10-26 22:42:07 +02:00
|
|
|
outgoing.AddChild(ui.NewFill(' ', tcell.StyleDefault)).At(12, 0)
|
2019-05-21 22:31:04 +02:00
|
|
|
outgoing.AddChild(wizard.smtpStr).At(13, 0)
|
2021-10-26 22:42:07 +02:00
|
|
|
outgoing.AddChild(ui.NewFill(' ', tcell.StyleDefault)).At(14, 0)
|
2019-05-21 22:31:04 +02:00
|
|
|
outgoing.AddChild(
|
2020-07-27 10:03:55 +02:00
|
|
|
ui.NewText("Copy sent messages to 'Sent' folder?",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_HEADER))).At(15, 0)
|
2022-07-03 17:11:12 +02:00
|
|
|
copySent := NewSelector([]string{"Yes", "No"}, 0, &conf.Ui).
|
2019-05-21 22:31:04 +02:00
|
|
|
Chooser(true).OnChoose(func(option string) {
|
|
|
|
switch option {
|
|
|
|
case "Yes":
|
|
|
|
wizard.copySent = true
|
|
|
|
case "No":
|
|
|
|
wizard.copySent = false
|
|
|
|
}
|
|
|
|
})
|
|
|
|
outgoing.AddChild(copySent).At(16, 0)
|
2020-07-27 10:03:56 +02:00
|
|
|
outgoing.AddChild(selector).At(17, 0)
|
2019-05-21 22:31:04 +02:00
|
|
|
wizard.outgoing = []ui.Interactive{
|
|
|
|
wizard.smtpUsername, wizard.smtpPassword, wizard.smtpServer,
|
2020-07-27 10:03:56 +02:00
|
|
|
smtpMode, copySent, selector,
|
2019-05-21 22:31:04 +02:00
|
|
|
}
|
|
|
|
outgoing.OnInvalidate(func(_ ui.Drawable) {
|
|
|
|
wizard.Invalidate()
|
|
|
|
})
|
|
|
|
|
|
|
|
complete := ui.NewGrid().Rows([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_EXACT, Size: ui.Const(7)}, // Introduction
|
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)}, // Previous / Finish / Finish & open tutorial
|
2019-05-21 22:31:04 +02:00
|
|
|
}).Columns([]ui.GridSpec{
|
2022-03-18 09:53:02 +01:00
|
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
2019-05-21 22:31:04 +02:00
|
|
|
})
|
|
|
|
complete.AddChild(ui.NewText(
|
2020-07-27 10:03:55 +02:00
|
|
|
"\nConfiguration complete!\n\n"+
|
|
|
|
"You can go back and double check your settings, or choose 'Finish' to\n"+
|
|
|
|
"save your settings to accounts.conf.\n\n"+
|
|
|
|
"To add another account in the future, run ':new-account'.",
|
|
|
|
conf.Ui.GetStyle(config.STYLE_DEFAULT)))
|
2020-07-27 10:03:56 +02:00
|
|
|
selector = NewSelector([]string{
|
2019-05-21 22:31:04 +02:00
|
|
|
"Previous",
|
|
|
|
"Finish & open tutorial",
|
2019-05-22 17:35:55 +02:00
|
|
|
"Finish",
|
2022-07-03 17:11:12 +02:00
|
|
|
}, 1, &conf.Ui).OnChoose(func(option string) {
|
2019-05-21 22:31:04 +02:00
|
|
|
switch option {
|
|
|
|
case "Previous":
|
|
|
|
wizard.advance("Previous")
|
|
|
|
case "Finish & open tutorial":
|
2019-05-22 16:40:08 +02:00
|
|
|
wizard.finish(true)
|
2019-05-21 22:31:04 +02:00
|
|
|
case "Finish":
|
2019-05-22 16:40:08 +02:00
|
|
|
wizard.finish(false)
|
2019-05-21 22:31:04 +02:00
|
|
|
}
|
|
|
|
})
|
2020-07-27 10:03:56 +02:00
|
|
|
complete.AddChild(selector).At(1, 0)
|
|
|
|
wizard.complete = []ui.Interactive{selector}
|
2019-05-21 22:31:04 +02:00
|
|
|
complete.OnInvalidate(func(_ ui.Drawable) {
|
|
|
|
wizard.Invalidate()
|
|
|
|
})
|
|
|
|
|
|
|
|
wizard.steps = []*ui.Grid{basics, incoming, outgoing, complete}
|
|
|
|
return wizard
|
|
|
|
}
|
|
|
|
|
2019-06-05 00:05:46 +02:00
|
|
|
func (wizard *AccountWizard) ConfigureTemporaryAccount(temporary bool) {
|
|
|
|
wizard.temporary = temporary
|
|
|
|
}
|
|
|
|
|
2019-05-22 16:40:08 +02:00
|
|
|
func (wizard *AccountWizard) errorFor(d ui.Interactive, err error) {
|
|
|
|
if d == nil {
|
2021-01-30 13:51:32 +01:00
|
|
|
wizard.aerc.PushError(err.Error())
|
2019-05-22 16:40:08 +02:00
|
|
|
wizard.Invalidate()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for step, interactives := range [][]ui.Interactive{
|
|
|
|
wizard.basics,
|
|
|
|
wizard.incoming,
|
|
|
|
wizard.outgoing,
|
|
|
|
} {
|
|
|
|
for focus, item := range interactives {
|
|
|
|
if item == d {
|
|
|
|
wizard.Focus(false)
|
|
|
|
wizard.step = step
|
|
|
|
wizard.focus = focus
|
|
|
|
wizard.Focus(true)
|
2021-01-30 13:51:32 +01:00
|
|
|
wizard.aerc.PushError(err.Error())
|
2019-05-22 16:40:08 +02:00
|
|
|
wizard.Invalidate()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wizard *AccountWizard) finish(tutorial bool) {
|
|
|
|
accountsConf := path.Join(xdg.ConfigHome(), "aerc", "accounts.conf")
|
|
|
|
|
|
|
|
// Validation
|
|
|
|
if wizard.accountName.String() == "" {
|
|
|
|
wizard.errorFor(wizard.accountName,
|
|
|
|
errors.New("Account name is required"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if wizard.email.String() == "" {
|
|
|
|
wizard.errorFor(wizard.email,
|
|
|
|
errors.New("Email address is required"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if wizard.fullName.String() == "" {
|
|
|
|
wizard.errorFor(wizard.fullName,
|
|
|
|
errors.New("Full name is required"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if wizard.imapServer.String() == "" {
|
|
|
|
wizard.errorFor(wizard.imapServer,
|
|
|
|
errors.New("IMAP server is required"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if wizard.imapServer.String() == "" {
|
|
|
|
wizard.errorFor(wizard.smtpServer,
|
|
|
|
errors.New("SMTP server is required"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
file, err := ini.Load(accountsConf)
|
2019-05-22 17:35:55 +02:00
|
|
|
if err != nil {
|
2019-05-22 16:40:08 +02:00
|
|
|
file = ini.Empty()
|
|
|
|
}
|
|
|
|
|
|
|
|
var sec *ini.Section
|
|
|
|
if sec, _ = file.GetSection(wizard.accountName.String()); sec != nil {
|
|
|
|
wizard.errorFor(wizard.accountName,
|
|
|
|
errors.New("An account by this name already exists"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sec, _ = file.NewSection(wizard.accountName.String())
|
2022-07-29 22:31:54 +02:00
|
|
|
// these can't really fail
|
|
|
|
sec.NewKey("source", wizard.imapUrl.String()) //nolint:errcheck // can't fail. option shadowing is not enabled and the key is not empty
|
|
|
|
sec.NewKey("outgoing", wizard.smtpUrl.String()) //nolint:errcheck // can't fail. option shadowing is not enabled and the key is not empty
|
|
|
|
sec.NewKey("default", "INBOX") //nolint:errcheck // can't fail. option shadowing is not enabled and the key is not empty
|
2019-05-22 16:40:08 +02:00
|
|
|
if wizard.smtpMode == SMTP_STARTTLS {
|
2022-07-29 22:31:54 +02:00
|
|
|
sec.NewKey("smtp-starttls", "yes") //nolint:errcheck // can't fail. option shadowing is not enabled and the key is not empty
|
2019-05-22 16:40:08 +02:00
|
|
|
}
|
2022-07-29 22:31:54 +02:00
|
|
|
sec.NewKey("from", fmt.Sprintf("%s <%s>", //nolint:errcheck // can't fail. option shadowing is not enabled and the key is not empty
|
2019-05-22 16:40:08 +02:00
|
|
|
wizard.fullName.String(), wizard.email.String()))
|
|
|
|
if wizard.copySent {
|
2022-07-29 22:31:54 +02:00
|
|
|
sec.NewKey("copy-to", "Sent") //nolint:errcheck // can't fail. option shadowing is not enabled and the key is not empty
|
2019-05-22 16:40:08 +02:00
|
|
|
}
|
|
|
|
|
2019-06-05 00:05:46 +02:00
|
|
|
if !wizard.temporary {
|
2022-07-31 22:16:40 +02:00
|
|
|
f, err := os.OpenFile(accountsConf, os.O_WRONLY|os.O_CREATE, 0o600)
|
2019-06-05 00:05:46 +02:00
|
|
|
if err != nil {
|
|
|
|
wizard.errorFor(nil, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err = file.WriteTo(f); err != nil {
|
|
|
|
wizard.errorFor(nil, err)
|
|
|
|
return
|
|
|
|
}
|
2019-05-22 16:40:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
account := config.AccountConfig{
|
|
|
|
Name: sec.Name(),
|
2019-05-22 17:13:55 +02:00
|
|
|
Default: "INBOX",
|
2019-05-22 16:40:08 +02:00
|
|
|
From: sec.Key("from").String(),
|
|
|
|
Source: sec.Key("source").String(),
|
2022-07-30 22:42:49 +02:00
|
|
|
Outgoing: config.RemoteConfig{Value: sec.Key("outgoing").String()},
|
2019-05-22 16:40:08 +02:00
|
|
|
}
|
|
|
|
if wizard.smtpMode == SMTP_STARTTLS {
|
|
|
|
account.Params = map[string]string{
|
|
|
|
"smtp-starttls": "yes",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if wizard.copySent {
|
|
|
|
account.CopyTo = "Sent"
|
|
|
|
}
|
|
|
|
wizard.conf.Accounts = append(wizard.conf.Accounts, account)
|
|
|
|
|
2022-07-19 22:31:51 +02:00
|
|
|
view, err := NewAccountView(wizard.aerc, wizard.conf, &account, wizard.aerc, nil)
|
2020-08-08 11:38:38 +02:00
|
|
|
if err != nil {
|
|
|
|
wizard.aerc.NewTab(errorScreen(err.Error(), wizard.conf.Ui),
|
|
|
|
account.Name)
|
|
|
|
return
|
|
|
|
}
|
2019-05-22 16:40:08 +02:00
|
|
|
wizard.aerc.accounts[account.Name] = view
|
|
|
|
wizard.aerc.NewTab(view, account.Name)
|
|
|
|
|
|
|
|
if tutorial {
|
2019-05-22 17:13:55 +02:00
|
|
|
name := "aerc-tutorial"
|
|
|
|
if _, err := os.Stat("./aerc-tutorial.7"); !os.IsNotExist(err) {
|
|
|
|
// For development
|
|
|
|
name = "./aerc-tutorial.7"
|
|
|
|
}
|
|
|
|
term, err := NewTerminal(exec.Command("man", name))
|
|
|
|
if err != nil {
|
|
|
|
wizard.errorFor(nil, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
wizard.aerc.NewTab(term, "Tutorial")
|
|
|
|
term.OnClose = func(err error) {
|
|
|
|
wizard.aerc.RemoveTab(term)
|
|
|
|
if err != nil {
|
2021-01-30 13:51:32 +01:00
|
|
|
wizard.aerc.PushError(err.Error())
|
2019-05-22 17:13:55 +02:00
|
|
|
}
|
|
|
|
}
|
2019-05-22 16:40:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
wizard.aerc.RemoveTab(wizard)
|
|
|
|
}
|
|
|
|
|
2019-05-21 22:31:04 +02:00
|
|
|
func (wizard *AccountWizard) imapUri() url.URL {
|
|
|
|
host := wizard.imapServer.String()
|
|
|
|
user := wizard.imapUsername.String()
|
|
|
|
pass := wizard.imapPassword.String()
|
|
|
|
var scheme string
|
|
|
|
switch wizard.imapMode {
|
|
|
|
case IMAP_OVER_TLS:
|
|
|
|
scheme = "imaps"
|
|
|
|
case IMAP_STARTTLS:
|
|
|
|
scheme = "imap"
|
|
|
|
case IMAP_INSECURE:
|
|
|
|
scheme = "imap+insecure"
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
userpass *url.Userinfo
|
|
|
|
userwopass *url.Userinfo
|
|
|
|
)
|
|
|
|
if pass == "" {
|
|
|
|
userpass = url.User(user)
|
|
|
|
userwopass = userpass
|
|
|
|
} else {
|
|
|
|
userpass = url.UserPassword(user, pass)
|
|
|
|
userwopass = url.UserPassword(user, strings.Repeat("*", len(pass)))
|
|
|
|
}
|
|
|
|
uri := url.URL{
|
|
|
|
Scheme: scheme,
|
|
|
|
Host: host,
|
|
|
|
User: userpass,
|
|
|
|
}
|
|
|
|
clean := url.URL{
|
|
|
|
Scheme: scheme,
|
|
|
|
Host: host,
|
|
|
|
User: userwopass,
|
|
|
|
}
|
|
|
|
wizard.imapStr.Text("Connection URL: " +
|
|
|
|
strings.ReplaceAll(clean.String(), "%2A", "*"))
|
|
|
|
wizard.imapUrl = uri
|
|
|
|
return uri
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wizard *AccountWizard) smtpUri() url.URL {
|
|
|
|
host := wizard.smtpServer.String()
|
|
|
|
user := wizard.smtpUsername.String()
|
|
|
|
pass := wizard.smtpPassword.String()
|
|
|
|
var scheme string
|
|
|
|
switch wizard.smtpMode {
|
|
|
|
case SMTP_OVER_TLS:
|
|
|
|
scheme = "smtps+plain"
|
|
|
|
case SMTP_STARTTLS:
|
|
|
|
scheme = "smtp+plain"
|
|
|
|
case SMTP_INSECURE:
|
|
|
|
scheme = "smtp+plain"
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
userpass *url.Userinfo
|
|
|
|
userwopass *url.Userinfo
|
|
|
|
)
|
|
|
|
if pass == "" {
|
|
|
|
userpass = url.User(user)
|
|
|
|
userwopass = userpass
|
|
|
|
} else {
|
|
|
|
userpass = url.UserPassword(user, pass)
|
|
|
|
userwopass = url.UserPassword(user, strings.Repeat("*", len(pass)))
|
|
|
|
}
|
|
|
|
uri := url.URL{
|
|
|
|
Scheme: scheme,
|
|
|
|
Host: host,
|
|
|
|
User: userpass,
|
|
|
|
}
|
|
|
|
clean := url.URL{
|
|
|
|
Scheme: scheme,
|
|
|
|
Host: host,
|
|
|
|
User: userwopass,
|
|
|
|
}
|
|
|
|
wizard.smtpStr.Text("Connection URL: " +
|
|
|
|
strings.ReplaceAll(clean.String(), "%2A", "*"))
|
|
|
|
wizard.smtpUrl = uri
|
|
|
|
return uri
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wizard *AccountWizard) Invalidate() {
|
|
|
|
wizard.DoInvalidate(wizard)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wizard *AccountWizard) Draw(ctx *ui.Context) {
|
|
|
|
wizard.steps[wizard.step].Draw(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wizard *AccountWizard) getInteractive() []ui.Interactive {
|
|
|
|
switch wizard.step {
|
|
|
|
case CONFIGURE_BASICS:
|
|
|
|
return wizard.basics
|
|
|
|
case CONFIGURE_INCOMING:
|
|
|
|
return wizard.incoming
|
|
|
|
case CONFIGURE_OUTGOING:
|
|
|
|
return wizard.outgoing
|
|
|
|
case CONFIGURE_COMPLETE:
|
|
|
|
return wizard.complete
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wizard *AccountWizard) advance(direction string) {
|
|
|
|
wizard.Focus(false)
|
|
|
|
if direction == "Next" && wizard.step < len(wizard.steps)-1 {
|
|
|
|
wizard.step++
|
|
|
|
}
|
|
|
|
if direction == "Previous" && wizard.step > 0 {
|
|
|
|
wizard.step--
|
|
|
|
}
|
|
|
|
wizard.focus = 0
|
|
|
|
wizard.Focus(true)
|
|
|
|
wizard.Invalidate()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wizard *AccountWizard) Focus(focus bool) {
|
|
|
|
if interactive := wizard.getInteractive(); interactive != nil {
|
|
|
|
interactive[wizard.focus].Focus(focus)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wizard *AccountWizard) Event(event tcell.Event) bool {
|
|
|
|
interactive := wizard.getInteractive()
|
2022-07-31 14:32:48 +02:00
|
|
|
if event, ok := event.(*tcell.EventKey); ok {
|
2019-05-21 22:31:04 +02:00
|
|
|
switch event.Key() {
|
|
|
|
case tcell.KeyUp:
|
|
|
|
fallthrough
|
2019-07-10 22:39:32 +02:00
|
|
|
case tcell.KeyBacktab:
|
|
|
|
fallthrough
|
2019-05-21 22:31:04 +02:00
|
|
|
case tcell.KeyCtrlK:
|
|
|
|
if interactive != nil {
|
|
|
|
interactive[wizard.focus].Focus(false)
|
|
|
|
wizard.focus--
|
|
|
|
if wizard.focus < 0 {
|
|
|
|
wizard.focus = len(interactive) - 1
|
|
|
|
}
|
|
|
|
interactive[wizard.focus].Focus(true)
|
|
|
|
}
|
|
|
|
wizard.Invalidate()
|
|
|
|
return true
|
|
|
|
case tcell.KeyDown:
|
|
|
|
fallthrough
|
|
|
|
case tcell.KeyTab:
|
|
|
|
fallthrough
|
|
|
|
case tcell.KeyCtrlJ:
|
|
|
|
if interactive != nil {
|
|
|
|
interactive[wizard.focus].Focus(false)
|
|
|
|
wizard.focus++
|
|
|
|
if wizard.focus >= len(interactive) {
|
|
|
|
wizard.focus = 0
|
|
|
|
}
|
|
|
|
interactive[wizard.focus].Focus(true)
|
|
|
|
}
|
|
|
|
wizard.Invalidate()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if interactive != nil {
|
|
|
|
return interactive[wizard.focus].Event(event)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-06-25 01:36:11 +02:00
|
|
|
func getSRV(host string, services []string) (string, string) {
|
|
|
|
var hostport, srv string
|
|
|
|
for _, srv = range services {
|
|
|
|
_, addrs, err := net.LookupSRV(srv, "tcp", host)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if addrs[0].Target != "" && addrs[0].Port > 0 {
|
|
|
|
hostport = net.JoinHostPort(
|
|
|
|
strings.TrimSuffix(addrs[0].Target, "."),
|
|
|
|
strconv.Itoa(int(addrs[0].Port)))
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hostport, srv
|
|
|
|
}
|