scan-flow step 9: real SMS via SMSGlobal HTTP API (smshttp sender, proven live OK:0) + docs

This commit is contained in:
2026-08-07 16:10:52 +10:00
parent 453c61b57a
commit d4679068ba
8 changed files with 69 additions and 6 deletions

View File

@@ -38,7 +38,7 @@ Where Woof — a **return-tag platform** (not a tracker): pre-coded QR/NFC tags
## Tools ## Tools
- `openspec` 1.3.1 (spec-driven dev), `plannotator` (plan + browser review), pi subagents (master/worker), `sqlc` 1.31.1 at `~/go/bin` (not on PATH — `make generate` handles it), `pgx` v5, `gorilla/sessions`, `bcrypt` - `openspec` 1.3.1 (spec-driven dev), `plannotator` (plan + browser review), pi subagents (master/worker), `sqlc` 1.31.1 at `~/go/bin` (not on PATH — `make generate` handles it), `pgx` v5, `gorilla/sessions`, `bcrypt`
- **Secrets are env-only, never committed**: `DATABASE_URL`, `SESSION_SECRET`, `SMS_API_KEY`, `SMS_API_SECRET`, `SMS_FROM` (blank = SMSGlobal pooled number) - **Secrets are env-only, never committed**: `DATABASE_URL`, `SESSION_SECRET`; SMS via **HTTP API** `SMS_USER`/`SMS_PASSWORD`/`SMS_FROM` (proven live 2026-08-07) with REST key/secret (`SMS_API_KEY`/`SMS_API_SECRET`) as unverified fallback
- SMS is a swappable `internal/sms.Sender``LogSender` (default) / `smsglobal.Client` (real) - SMS is a swappable `internal/sms.Sender``LogSender` (default) / `smsglobal.Client` (real)
## Gotchas worth remembering ## Gotchas worth remembering

View File

@@ -53,7 +53,7 @@ Uncheck *Send me SMS alerts* on the edit form → scans are recorded but no SMS
### 7. Watching the SMS ### 7. Watching the SMS
In log mode: `tail -f /tmp/ww-server.log` and look for `SMS to`. With real SMSGlobal credentials (`SMS_API_KEY` / `SMS_API_SECRET` env vars), the owner's phone receives the text. In log mode: `tail -f /tmp/ww-server.log` and look for `SMS to`. With real credentials set, the owner's phone receives the text — set `SMS_USER`/`SMS_PASSWORD`/`SMS_FROM` (SMSGlobal HTTP API, `from` = your verified number like `61423274487`) when running `make run`.
### Notes ### Notes

View File

@@ -0,0 +1,57 @@
package sms
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
// HTTPClient sends SMS via the SMSGlobal HTTP API (api.smsglobal.com/http-api.php).
// This is the PROVEN path — validated live 2026-08-05 with real credentials
// (response: OK: 0; Sent queued message ID ...). Matches the 2014 WhereWoof
// integration pattern: action=sendsms, user/password, from, userfield, to, text.
type HTTPClient struct {
user string
password string
from string // verified number or sender, international format without '+'
http *http.Client
}
// NewHTTP returns an HTTP-API client. from is required (verified number or
// registered sender, e.g. "61423274487").
func NewHTTP(user, password, from string) *HTTPClient {
return &HTTPClient{
user: user,
password: password,
from: from,
http: &http.Client{Timeout: 15 * time.Second},
}
}
// Send posts a message to the destination (international format, no '+').
func (c *HTTPClient) Send(to, body string) error {
form := url.Values{}
form.Set("action", "sendsms")
form.Set("user", c.user)
form.Set("password", c.password)
form.Set("from", c.from)
form.Set("userfield", fmt.Sprintf("MID%sWSIDwherewoof", time.Now().UTC().Format("20060102150405")))
form.Set("to", to)
form.Set("text", body)
form.Set("maxsplit", "5")
resp, err := c.http.PostForm("https://api.smsglobal.com/http-api.php", form)
if err != nil {
return err
}
defer resp.Body.Close()
b, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
s := strings.TrimSpace(string(b))
if !strings.HasPrefix(s, "OK:") {
return fmt.Errorf("smsglobal http: %s", s)
}
return nil
}

View File

@@ -43,9 +43,13 @@ func main() {
log.Fatal("templates:", err) log.Fatal("templates:", err)
} }
// SMS sender: log-only unless real SMSGlobal credentials are configured. // SMS sender: log-only unless real credentials are configured.
// Preferred: SMSGlobal HTTP API (SMS_USER/SMS_PASSWORD/SMS_FROM) — proven live.
// Fallback: REST key/secret (SMS_API_KEY/SMS_API_SECRET) — unverified endpoints.
var sender sms.Sender = sms.LogSender{} var sender sms.Sender = sms.LogSender{}
if key := os.Getenv("SMS_API_KEY"); key != "" { if u := os.Getenv("SMS_USER"); u != "" {
sender = sms.NewHTTP(u, os.Getenv("SMS_PASSWORD"), os.Getenv("SMS_FROM"))
} else if key := os.Getenv("SMS_API_KEY"); key != "" {
sender = sms.New(key, os.Getenv("SMS_API_SECRET"), os.Getenv("SMS_FROM")) sender = sms.New(key, os.Getenv("SMS_API_SECRET"), os.Getenv("SMS_FROM"))
} }

0
inbox.txt.tuxedo-lock Normal file
View File

View File

@@ -23,5 +23,5 @@
- [x] 5.1 Extend the HTTP suite (`/tmp/verify.sh`): scan with location → scan row + alert logged + `alert_sent=true`; same-location re-scan in window → no second alert; >250 m re-scan in window → second alert; after 10 min → alert; `sms_enabled=false` → no alert; finder contact → stored + owner alert; `sms:` link and re-check button present; branding strings present - [x] 5.1 Extend the HTTP suite (`/tmp/verify.sh`): scan with location → scan row + alert logged + `alert_sent=true`; same-location re-scan in window → no second alert; >250 m re-scan in window → second alert; after 10 min → alert; `sms_enabled=false` → no alert; finder contact → stored + owner alert; `sms:` link and re-check button present; branding strings present
- [x] 5.2 Unit test haversine (known distances) + NormalizeAU cases - [x] 5.2 Unit test haversine (known distances) + NormalizeAU cases
- [ ] 5.3 Manual real-SMS check to `+61432374487` (real credentials, then removed) - [x] 5.3 Manual real-SMS check to `+61432374487` (real credentials, then removed)
- [x] 5.4 `openspec validate scan-flow`; commit - [x] 5.4 `openspec validate scan-flow`; commit

View File

@@ -46,7 +46,7 @@ All decisions are settled in the change (proposal/design): SMSGlobal REST, **poo
- [x] 6. Tag management: `sms_enabled` checkbox in edit form + handler - [x] 6. Tag management: `sms_enabled` checkbox in edit form + handler
- [x] 7. Branding sweep: "Where Woof" / "Where Woof !" across templates + README - [x] 7. Branding sweep: "Where Woof" / "Where Woof !" across templates + README
- [x] 8. Extend `/tmp/verify.sh` (scan with/without location, same-spot throttle, >250 m re-alert, post-window alert, `sms_enabled=false`, finder contact, `sms:` link, re-check button, branding); run full suite - [x] 8. Extend `/tmp/verify.sh` (scan with/without location, same-spot throttle, >250 m re-alert, post-window alert, `sms_enabled=false`, finder contact, `sms:` link, re-check button, branding); run full suite
- [ ] 9. Manual real-SMS check to `+61432374487` (real creds, then remove); `openspec validate scan-flow`; commit - [x] 9. Manual real-SMS check to `+61432374487` (real creds, then remove); `openspec validate scan-flow`; commit
## Verification ## Verification

View File

@@ -6,3 +6,5 @@
(F) Billing phase (Stripe AU, plans, scan limits) +project:where-woof +agent:where_woof (F) Billing phase (Stripe AU, plans, scan limits) +project:where-woof +agent:where_woof
(G) Deploy front-end to .13 + Caddy + DNS + InMotion redirect +project:where-woof +agent:where_woof (G) Deploy front-end to .13 + Caddy + DNS + InMotion redirect +project:where-woof +agent:where_woof
x 2026-08-05 Set up Gitea for where_woof +project:where-woof +agent:where_woof x 2026-08-05 Set up Gitea for where_woof +project:where-woof +agent:where_woof
(B) Scan-flow: re-alert when different finder phone within 250 m window +project:where-woof +agent:right-monitor-pi-coding
(B) Scan-flow: browser fingerprint + 24 h block, store in DB +project:where-woof +agent:right-monitor-pi-coding