92 lines
2.6 KiB
Go
92 lines
2.6 KiB
Go
// Package notify sends owner notifications (unlock/renewal notices).
|
|
// Channels: ntfy (self-hosted, default), SMS (via the sms.Sender), or both,
|
|
// selected by NOTIFY_CHANNEL=ntfy|sms|both.
|
|
package notify
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"wherewoof/frontend/internal/sms"
|
|
)
|
|
|
|
// Notifier delivers messages to the owner.
|
|
type Notifier struct {
|
|
sender sms.Sender // used when the SMS channel is enabled
|
|
client *http.Client
|
|
}
|
|
|
|
// New builds a Notifier. sender may be nil (SMS channel disabled).
|
|
func New(sender sms.Sender) *Notifier {
|
|
return &Notifier{sender: sender, client: &http.Client{Timeout: 10 * time.Second}}
|
|
}
|
|
|
|
// Channel returns the configured channel: ntfy (default), sms, or both.
|
|
func (n *Notifier) Channel() string {
|
|
c := os.Getenv("NOTIFY_CHANNEL")
|
|
if c != "sms" && c != "both" {
|
|
return "ntfy"
|
|
}
|
|
return c
|
|
}
|
|
|
|
// Send delivers the message to the owner's phone (SMS) and/or the ntfy topic.
|
|
// Unlock notices are system messages — they are NOT subject to SMS metering.
|
|
func (n *Notifier) Send(ownerPhone, message string) {
|
|
ch := n.Channel()
|
|
if ch == "sms" || ch == "both" {
|
|
if n.sender != nil {
|
|
if err := n.sender.Send(sms.NormalizeAU(ownerPhone), message); err != nil {
|
|
log.Println("notify sms failed:", err)
|
|
}
|
|
} else {
|
|
log.Println("notify: SMS channel selected but no sender configured")
|
|
}
|
|
}
|
|
if ch == "ntfy" || ch == "both" {
|
|
n.sendNtfy(message)
|
|
}
|
|
}
|
|
|
|
func (n *Notifier) sendNtfy(message string) {
|
|
url := os.Getenv("NTFY_URL")
|
|
if url == "" {
|
|
url = "https://ntfy.sh"
|
|
}
|
|
topic := os.Getenv("NTFY_TOPIC")
|
|
if topic == "" {
|
|
topic = "wherewoof-owner-alerts"
|
|
}
|
|
req, err := http.NewRequest(http.MethodPost, url+"/"+topic, bytes.NewBufferString(message))
|
|
if err != nil {
|
|
log.Println("notify ntfy:", err)
|
|
return
|
|
}
|
|
req.Header.Set("Title", "Where Woof")
|
|
resp, err := n.client.Do(req)
|
|
if err != nil {
|
|
log.Println("notify ntfy failed:", err)
|
|
return
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode >= 300 {
|
|
log.Printf("notify ntfy: status %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
// UnlockMessage builds the owner notice for a gated scan.
|
|
func UnlockMessage(tagCode, reason string) string {
|
|
switch reason {
|
|
case "lapsed":
|
|
return fmt.Sprintf("Where Woof: your tag %s was scanned, but your plan is lapsed. Renew to see the finder's details: https://where-woof.com/account", tagCode)
|
|
case "credits":
|
|
return fmt.Sprintf("Where Woof: your tag %s was scanned, but you're out of SMS credits. Top up or renew to see the finder's details: https://where-woof.com/account", tagCode)
|
|
default:
|
|
return fmt.Sprintf("Where Woof: your tag %s was scanned. Manage your plan: https://where-woof.com/account", tagCode)
|
|
}
|
|
}
|