This commit is contained in:
2026-06-13 19:14:24 +02:00
parent 0b3e22c97e
commit 81e505e040
19 changed files with 194 additions and 56 deletions
+28 -11
View File
@@ -110,6 +110,7 @@ const (
keyEmailUser = "email.smtp_user"
keyEmailPassword = "email.smtp_password"
keyEmailFrom = "email.from"
keyEmailReplyTo = "email.reply_to" // optional Reply-To: replies go here, not the From sender
keyEmailEncryption = "email.encryption" // "ssl" | "starttls" | "none"
keyEmailAuth = "email.auth" // "1" → SMTP requires authorization (send user/password)
keyEmailAutoSend = "email.auto_send" // "1" → auto-send recording on log when an e-mail is known
@@ -3438,13 +3439,19 @@ func (a *App) saveQSORecording(q *qso.QSO) {
parts = append(parts, time.Now().UTC().Format("20060102_150405"))
name := strings.Join(parts, "_") + "." + ext
path := filepath.Join(a.qsoRecDir(), name)
if err := a.qsoRec.SaveQSO(path); err != nil {
applog.Printf("qso-rec: save failed: %v", err)
// Snapshot the audio synchronously (fast — frees the recorder for the next
// QSO). The slow part is encoding the file (a long MP3), which we defer to a
// goroutine so logging stays snappy.
pcm, err := a.qsoRec.TakeQSO()
if err != nil {
applog.Printf("qso-rec: snapshot failed: %v", err)
return
}
applog.Printf("qso-rec: saved %s", path)
// Remember the recording on the QSO so it can be e-mailed later.
// Stamp the recording's path on the QSO now, synchronously, so it's set
// before the eQSL auto-send reads the QSO (their full-row Updates would
// otherwise race and clobber each other's extras).
if q.ID != 0 {
if q.Extras == nil {
q.Extras = map[string]string{}
@@ -3455,11 +3462,18 @@ func (a *App) saveQSORecording(q *qso.QSO) {
}
}
// Auto-send to the correspondent when enabled and an e-mail is known.
if es, _ := a.GetEmailSettings(); es.Enabled && es.AutoSend && strings.TrimSpace(q.Email) != "" {
qc := *q
go func() { _ = a.sendRecordingEmail(qc, path) }()
}
qc := *q
go func() {
if err := audio.WritePCM(path, pcm); err != nil {
applog.Printf("qso-rec: save failed: %v", err)
return
}
applog.Printf("qso-rec: saved %s", path)
// Auto-send the recording once the file exists.
if es, _ := a.GetEmailSettings(); es.Enabled && es.AutoSend && strings.TrimSpace(qc.Email) != "" {
_ = a.sendRecordingEmail(qc, path)
}
}()
}
// sanitizeFilename makes a callsign safe for a filename (slashes etc.).
@@ -3527,6 +3541,7 @@ type EmailSettings struct {
User string `json:"smtp_user"`
Password string `json:"smtp_password"`
From string `json:"from"`
ReplyTo string `json:"reply_to"` // optional — where correspondents' replies go
Encryption string `json:"encryption"` // "ssl" | "starttls" | "none"
Auth bool `json:"auth"` // SMTP requires authorization
AutoSend bool `json:"auto_send"`
@@ -3542,7 +3557,7 @@ func (a *App) GetEmailSettings() (EmailSettings, error) {
}
m, err := a.settings.GetMany(a.ctx,
keyEmailEnabled, keyEmailHost, keyEmailPort, keyEmailUser, keyEmailPassword,
keyEmailFrom, keyEmailEncryption, keyEmailAuth, keyEmailAutoSend, keyEmailSubject, keyEmailBody)
keyEmailFrom, keyEmailReplyTo, keyEmailEncryption, keyEmailAuth, keyEmailAutoSend, keyEmailSubject, keyEmailBody)
if err != nil {
return out, err
}
@@ -3554,6 +3569,7 @@ func (a *App) GetEmailSettings() (EmailSettings, error) {
out.User = m[keyEmailUser]
out.Password = m[keyEmailPassword]
out.From = m[keyEmailFrom]
out.ReplyTo = m[keyEmailReplyTo]
if e := m[keyEmailEncryption]; e == "ssl" || e == "starttls" || e == "none" {
out.Encryption = e
}
@@ -3593,6 +3609,7 @@ func (a *App) SaveEmailSettings(s EmailSettings) error {
keyEmailUser: strings.TrimSpace(s.User),
keyEmailPassword: s.Password,
keyEmailFrom: strings.TrimSpace(s.From),
keyEmailReplyTo: strings.TrimSpace(s.ReplyTo),
keyEmailEncryption: enc,
keyEmailAuth: b2s(s.Auth),
keyEmailAutoSend: b2s(s.AutoSend),
@@ -3607,7 +3624,7 @@ func (a *App) SaveEmailSettings(s EmailSettings) error {
}
func (a *App) emailConfig(s EmailSettings) email.Config {
return email.Config{Host: s.Host, Port: s.Port, User: s.User, Password: s.Password, From: s.From, Encryption: s.Encryption, Auth: s.Auth}
return email.Config{Host: s.Host, Port: s.Port, User: s.User, Password: s.Password, From: s.From, ReplyTo: s.ReplyTo, Encryption: s.Encryption, Auth: s.Auth}
}
// TestEmail sends a test message to `to` (defaults to the From address) to