diff --git a/app_qsl_designer.go b/app_qsl_designer.go index 5f9ba38..a77ad5b 100644 --- a/app_qsl_designer.go +++ b/app_qsl_designer.go @@ -45,9 +45,20 @@ const appQSLCardSentField = "APP_OPSLOG_QSL_SENT" // (please send one). Independent of ADIF qsl_rcvd, like the sent field. const appQSLCardRcvdField = "APP_OPSLOG_QSL_RCVD" +// qslCredit closes the default QSL e-mail: who made the card, and where the +// recipient can get the same program. +// +// A DEFAULT, not a signature. It lives in the body template like every other +// line, so an operator who does not want it deletes it once and it is gone — +// and one who has already written their own body never sees it appear, because +// a stored template is returned verbatim and the default is only the fallback. +// Appending it at send time instead would have made it unremovable, which is +// not a thing to do to someone's outgoing mail. +const qslCredit = "--\nDesigned & sent by OpsLog — " + releasesPageURL + const ( defaultQSLEmailSubject = "eQSL — {CALL} de {MYCALL}" - defaultQSLEmailBody = "Hi,\n\nThank you for our QSO! Please find attached your eQSL card.\n\n{DATE} · {BAND} · {MODE}\n\n73,\n{MYCALL}" + defaultQSLEmailBody = "Hi,\n\nThank you for our QSO! Please find attached your eQSL card.\n\n{DATE} · {BAND} · {MODE}\n\n73,\n{MYCALL}\n\n" + qslCredit ) // qslDir is the root of all designer artifacts: templates// and outbox/. diff --git a/changelog.json b/changelog.json index 466ff0b..203c397 100644 --- a/changelog.json +++ b/changelog.json @@ -4,11 +4,13 @@ "date": "", "en": [ "Right-click: update the US county of the selected contacts from the ULS database, replacing a county since renamed or abolished.", - "A QSO logged from WSJT-X, MSHV or a net now appears in Recent QSOs at once, instead of waiting for a delayed auto-upload to send it." + "A QSO logged from WSJT-X, MSHV or a net now appears in Recent QSOs at once, instead of waiting for a delayed auto-upload to send it.", + "New installs: the default QSL e-mail ends with a credit line and a link to OpsLog. It is part of the template, so delete it if you'd rather not." ], "fr": [ "Clic droit : mettre à jour le comté US des contacts sélectionnés depuis la base ULS, pour remplacer un comté renommé ou supprimé.", - "Un QSO logué depuis WSJT-X, MSHV ou un net apparaît aussitôt dans les QSO récents, sans attendre l’envoi d’un upload automatique différé." + "Un QSO logué depuis WSJT-X, MSHV ou un net apparaît aussitôt dans les QSO récents, sans attendre l’envoi d’un upload automatique différé.", + "Nouvelles installations : le mail QSL par défaut se termine par une ligne de crédit et un lien vers OpsLog. Elle fait partie du modèle, supprimable." ] }, { diff --git a/qslcredit_test.go b/qslcredit_test.go new file mode 100644 index 0000000..48b25f2 --- /dev/null +++ b/qslcredit_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "strings" + "testing" +) + +// The default QSL e-mail closes with a credit line carrying the download page. +// +// It is part of the BODY TEMPLATE, deliberately: an operator who does not want +// it deletes it once, and one who already wrote their own body never sees it, +// because a stored template is returned verbatim and the default is only the +// fallback. Appending it at send time would have made it unremovable. +func TestQSLCreditIsInTheDefaultBody(t *testing.T) { + if !strings.HasSuffix(defaultQSLEmailBody, qslCredit) { + t.Errorf("the default QSL e-mail does not end with the credit line:\n%q", defaultQSLEmailBody) + } + // The body still has to be a usable template — the credit is added to it, + // not in place of it. + for _, v := range []string{"{DATE}", "{BAND}", "{MODE}", "{MYCALL}"} { + if !strings.Contains(defaultQSLEmailBody, v) { + t.Errorf("the default QSL e-mail lost %s", v) + } + } +} + +// The link must be the page a person can read. updateCheckURL sits beside it and +// answers JSON — sending a correspondent there is the easy mistake to make. +func TestQSLCreditLinksTheHumanPage(t *testing.T) { + if !strings.Contains(qslCredit, releasesPageURL) { + t.Errorf("credit %q does not carry the releases page", qslCredit) + } + if strings.Contains(qslCredit, "api.github.com") { + t.Errorf("credit %q points at the update API, which answers JSON to whoever clicks it", qslCredit) + } + if !strings.Contains(qslCredit, "OpsLog") { + t.Errorf("credit %q does not name OpsLog", qslCredit) + } +} diff --git a/update.go b/update.go index 3a24d34..2553bf2 100644 --- a/update.go +++ b/update.go @@ -23,6 +23,11 @@ import ( // build (the exe lives there; source stays on Gitea). Adjust the repo if needed. const updateCheckURL = "https://api.github.com/repos/GregTroar/OpsLog/releases/latest" +// releasesPageURL is the same release, for people rather than for the updater: +// the API address above answers JSON, so it is not something to put in front of +// an operator who followed a link out of a QSL e-mail. +const releasesPageURL = "https://github.com/GregTroar/OpsLog/releases/latest" + // UpdateInfo is the result of the version check. type UpdateInfo struct { Current string `json:"current"` // this build's version (appVersion) @@ -367,8 +372,8 @@ func cleanupOldUpdateBinary() { if err != nil { return } - _ = os.Remove(exe + ".old") // the old fixed name - _ = os.Remove(exe + ".new") // a deferred swap that has been applied + _ = os.Remove(exe + ".old") // the old fixed name + _ = os.Remove(exe + ".new") // a deferred swap that has been applied matches, err := filepath.Glob(exe + ".old-*") if err != nil { return