summaryrefslogtreecommitdiffstats
path: root/internal/bot/format.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/bot/format.go')
-rw-r--r--internal/bot/format.go96
1 files changed, 94 insertions, 2 deletions
diff --git a/internal/bot/format.go b/internal/bot/format.go
index 9a9a22d..b94dee4 100644
--- a/internal/bot/format.go
+++ b/internal/bot/format.go
@@ -9,6 +9,8 @@ import (
"time"
cepis "cep.is/go"
+
+ "cep.is/telegrambot/internal/lookup"
)
// ParseMode is the formatting Telegram applies to every message this bot sends.
@@ -152,8 +154,7 @@ func formatRadius(metres int) string {
func ErrorMessage(err error) string {
switch {
case errors.Is(err, cepis.ErrCEPNotFound):
- return "🔍 CEP não encontrado na base dos Correios (DNE).\n" +
- "Confira os oito dígitos — a base é atualizada a cada quinze dias."
+ return notFoundMessage(err)
case errors.Is(err, cepis.ErrInvalidCEP):
return "✋ CEP inválido. Preciso de oito dígitos: <code>01001000</code> " +
"ou <code>01001-000</code>."
@@ -173,6 +174,97 @@ func ErrorMessage(err error) string {
return "⚠️ Não consegui consultar este CEP agora. Tente de novo em alguns minutos."
}
+// The two halves of the not-found answer, unchanged from the day the bot
+// shipped. Whatever is said about the municipality goes between them: the reply
+// opens by saying the CEP does not exist and closes by saying what to do, so
+// the middle cannot be mistaken for an address.
+const (
+ notFoundHead = "🔍 CEP não encontrado na base dos Correios (DNE)."
+ notFoundAdvice = "Confira os oito dígitos — a base é atualizada a cada quinze dias."
+)
+
+// notFoundMessage answers a CEP that does not exist, naming the municipality it
+// would belong to when the API told us which one.
+//
+// It is worth the extra lines. The CEP that fails is almost never a random
+// number — it is someone who knows the city and guessed the rest, most often at
+// the "-000" that 957 municipalities never received. Telling that person only
+// "não encontrei" wastes the trip; telling them the number would have been in
+// Areal/RJ says which half of their guess was right.
+//
+// What it must never do is read like a hit. There is no 📮, no address block
+// and no CEP suggested from inside the range: the range is sparse — Areal's 118
+// CEPs are spread over nearly five thousand numbers — so its ends are the
+// extremes of what exists, not a list of what is valid, and the reply says so.
+func notFoundMessage(err error) string {
+ faixa := lookup.FaixaOf(err)
+ if faixa == nil || faixa.Localidade == "" {
+ return notFoundHead + "\n" + notFoundAdvice
+ }
+
+ var b strings.Builder
+ b.WriteString(notFoundHead + "\n")
+ b.WriteString("Ele cairia em <b>" + esc(faixa.Place()) + "</b>")
+
+ count := faixaCount(faixa)
+ span := faixaSpan(faixa)
+ switch {
+ case count != "" && span != "":
+ b.WriteString(", que tem " + count + ", " + span + ".\n")
+ b.WriteString("<i>A faixa é esparsa: nem todo número entre os dois é um CEP válido.</i>\n")
+ case count != "":
+ b.WriteString(", que tem " + count + ".\n")
+ case span != "":
+ b.WriteString(", com CEPs " + span + ".\n")
+ b.WriteString("<i>A faixa é esparsa: nem todo número entre os dois é um CEP válido.</i>\n")
+ default:
+ b.WriteString(".\n")
+ }
+
+ b.WriteString(notFoundAdvice)
+ return b.String()
+}
+
+// faixaCount renders how many CEPs the municipality has, or "" when the API did
+// not say. Without it the two ends of the range read as a count of one big
+// block, which is the misreading the number exists to prevent.
+func faixaCount(faixa *lookup.Faixa) string {
+ switch {
+ case faixa.CEPs <= 0:
+ return ""
+ case faixa.CEPs == 1:
+ return "1 CEP"
+ }
+ return strconv.Itoa(faixa.CEPs) + " CEPs"
+}
+
+// faixaSpan renders the ends of the range, or "" when they are missing or are
+// the same number — "de X a X" is not a range, and printing a single CEP there
+// would hand the user a guess to try.
+func faixaSpan(faixa *lookup.Faixa) string {
+ first, last := maskCEP(faixa.CEPIni), maskCEP(faixa.CEPFim)
+ if first == "" || last == "" || first == last {
+ return ""
+ }
+ return "de <code>" + esc(first) + "</code> a <code>" + esc(last) + "</code>"
+}
+
+// maskCEP puts the mask on eight digits, and returns "" for anything else: a
+// CEP the API sent in a shape this bot does not recognise is left out of the
+// sentence rather than printed raw.
+func maskCEP(cep string) string {
+ cep = strings.TrimSpace(cep)
+ if len(cep) != 8 {
+ return ""
+ }
+ for _, r := range cep {
+ if r < '0' || r > '9' {
+ return ""
+ }
+ }
+ return cep[:5] + "-" + cep[5:]
+}
+
// RateLimitMessage is what a user held by the local token bucket is told, once
// per warning window.
func RateLimitMessage(retryAfter time.Duration) string {