The PHP app, Apache, and .htaccess are replaced by a single Go binary (standard library only). Routing, the JSON response, the landing page, the shared email design, and all four parsers are ported with the same output; JSON object keys are kept in sender order via an ordered decoder. - mail.go: SMTP delivery with quoted-printable body and RFC 2047 subject - main.go: routing, client IP from the proxy, /healthz, -check flag - render.go: email shell, escaping, table helpers, generic renderer - parser_*.go: github, grafana, json, wxinteract - main_test.go: handler, response shape, IP forwarding, every parser - Dockerfile: multi-stage build to distroless static, non-root, HEALTHCHECK - deploy/webhooks.dixon.cx.service: systemd unit for the container - README and PARSER_PROMPT updated for Go; IMPLEMENTATION_SUMMARY removed
119 lines
4.0 KiB
Go
119 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
)
|
|
|
|
// wxinteractParse formats Webex Interact SMS webhook events.
|
|
func wxinteractParse(raw []byte, headers http.Header, meta Meta) (string, string, bool) {
|
|
v, err := decodeJSON(raw)
|
|
payload := asObject(v)
|
|
if err != nil || payload.Len() == 0 {
|
|
return "", "", false
|
|
}
|
|
data := asObject(payload.Get("data"))
|
|
if data == nil {
|
|
return "", "", false
|
|
}
|
|
|
|
status := strOr(data.Get("status"), "Unknown")
|
|
f := func(key string) string { return h(strOr(data.Get(key), "N/A")) }
|
|
|
|
icon, title := "📱", "SMS Event"
|
|
var content string
|
|
|
|
switch status {
|
|
case "submitted":
|
|
icon, title = "📤", "Outbound SMS - Submitted"
|
|
content = section("📤 SMS Details", table(
|
|
row("Message ID", f("message_id")),
|
|
row("To", f("to")),
|
|
row("From", f("from")),
|
|
row("Campaign ID", f("campaign_id")),
|
|
row("Submitted At", f("submitted_at")),
|
|
)) + section("💬 Message Content", `<div class="message-box">`+esc(strOr(data.Get("message"), "N/A"))+`</div>`)
|
|
|
|
case "delivered":
|
|
icon, title = "✅", "Outbound SMS - Delivered"
|
|
content = section("✅ Delivery Details", table(
|
|
row("Message ID", f("message_id")),
|
|
row("To", f("to")),
|
|
row("From", f("from")),
|
|
row("Campaign ID", f("campaign_id")),
|
|
row("Delivered At", f("delivered_at")),
|
|
))
|
|
|
|
case "failed":
|
|
icon, title = "❌", "Outbound SMS - Failed"
|
|
content = section("❌ Failure Details", table(
|
|
row("Message ID", f("message_id")),
|
|
row("To", f("to")),
|
|
row("From", f("from")),
|
|
row("Campaign ID", f("campaign_id")),
|
|
row("Failed At", f("failed_at")),
|
|
)) + section("⚠️ Error Information", `<div class="error-box"><strong>Error Code:</strong> `+f("error_code")+
|
|
`<br><strong>Error Message:</strong> `+esc(strOr(data.Get("error_message"), "N/A"))+`</div>`)
|
|
|
|
case "shortlink_clicked":
|
|
icon, title = "🔗", "Shortlink Clicked"
|
|
content = section("🔗 Click Details", table(
|
|
row("Shortlink", link(strOr(data.Get("shortlink"), "N/A"))),
|
|
row("Original URL", link(strOr(data.Get("original_url"), "N/A"))),
|
|
row("Phone Number", f("phone_number")),
|
|
row("Message ID", f("message_id")),
|
|
row("Campaign ID", f("campaign_id")),
|
|
row("Clicked At", f("clicked_at")),
|
|
row("IP Address", f("ip_address")),
|
|
)) + section("🌐 Browser Information", `<div class="data-box">`+f("user_agent")+`</div>`)
|
|
|
|
case "received":
|
|
icon, title = "📥", "Inbound SMS Received"
|
|
content = section("📥 Inbound SMS Details", table(
|
|
row("Message ID", f("message_id")),
|
|
row("From", f("from")),
|
|
row("To", f("to")),
|
|
row("Keyword", f("keyword")),
|
|
row("Received At", f("received_at")),
|
|
)) + section("💬 Message Content", `<div class="message-box">`+esc(strOr(data.Get("message"), "N/A"))+`</div>`)
|
|
|
|
case "opt_out":
|
|
icon, title = "🚫", "SMS Opt Out Received"
|
|
content = section("🚫 Opt Out Details", table(
|
|
row("Phone Number", f("phone_number")),
|
|
row("From Number", f("from")),
|
|
row("Keyword", f("keyword")),
|
|
row("Message ID", f("message_id")),
|
|
row("Opt Out At", f("opt_out_at")),
|
|
))
|
|
|
|
case "contact_created", "contact_updated":
|
|
icon, title = "👤", "Contacts Callback"
|
|
content = section("👤 Contact Details", table(
|
|
row("Contact ID", f("contact_id")),
|
|
row("Phone Number", f("phone_number")),
|
|
row("First Name", f("first_name")),
|
|
row("Last Name", f("last_name")),
|
|
row("Email", f("email")),
|
|
row("Status", f("status")),
|
|
row("Created At", f("created_at")),
|
|
row("Updated At", f("updated_at")),
|
|
))
|
|
if cf := asObject(data.Get("custom_fields")); cf.Len() > 0 {
|
|
content += section("🏷️ Custom Fields", kvTable(cf))
|
|
}
|
|
|
|
default:
|
|
title = "SMS Event: " + status
|
|
content = section("Event Details", renderDataAsTable(data, 0))
|
|
}
|
|
|
|
body := section("📋 Event Information", table(
|
|
row("Event Type", h(status)),
|
|
row("IP Address", h(meta.IP)),
|
|
)) + content
|
|
|
|
html := emailShell(icon+" Webex Interact: "+h(title), h(meta.Date), body,
|
|
"This Webex Interact webhook was automatically forwarded to your email address.")
|
|
return html, icon + " Webex Interact: " + title, true
|
|
}
|