Owner notification webhook: Telegram re-point (#23)
Test backoffice (smb-crm) / test (push) Has been cancelled
Test backoffice (smb-crm) / test (push) Has been cancelled
Flask fires a fire-and-forget internal webhook (owner_notify.py, mirroring
mailer.py's background-thread queue) on booking create/cancel/reschedule,
carrying the same {booking, business_name, notify_channel} shape the old
EA-driven "Build booking row" node produced, plus an event field so the
Telegram message can say what actually happened. n8n/booking-sync.json gets
a new webhook + IF node feeding the existing Telegram node directly, so it
no longer needs EA's API to build the notification payload; channel routing
(only telegram sends for now) lives in that IF node.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Owner Telegram notification webhook (#23): fires an internal webhook call
|
||||
on booking create/cancel/reschedule, with the same {booking, business_name,
|
||||
notify_channel} payload shape n8n/booking-sync.json's "Build booking row"
|
||||
step used to hand the Telegram node -- plus an "event" field (that node
|
||||
never needed, since it only ever fired for a new EA booking) so the message
|
||||
text can say what actually happened instead of always "new booking".
|
||||
|
||||
Fire-and-forget over HTTP, mirroring mailer.py's background-thread queue: a
|
||||
slow/unreachable webhook must never block or fail a booking mutation.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import queue
|
||||
import threading
|
||||
import traceback
|
||||
import urllib.request
|
||||
|
||||
# Left blank, sending is skipped (logged, not fatal) -- same convention as
|
||||
# mailer.py's SMTP_HOST. Channel routing (only "telegram" does anything at
|
||||
# launch) lives on the n8n side of this webhook, not here -- see #23.
|
||||
NOTIFY_WEBHOOK_URL = os.environ.get("OWNER_NOTIFY_WEBHOOK_URL", "")
|
||||
|
||||
_queue = queue.Queue()
|
||||
|
||||
|
||||
def _serialize_booking(booking):
|
||||
return {k: (v.isoformat() if hasattr(v, "isoformat") else v)
|
||||
for k, v in booking.items()}
|
||||
|
||||
|
||||
def _send_now(payload):
|
||||
if not NOTIFY_WEBHOOK_URL:
|
||||
print(f"[owner_notify] OWNER_NOTIFY_WEBHOOK_URL not configured, "
|
||||
f"skipping notify for booking {payload['booking'].get('booking_id')}",
|
||||
flush=True)
|
||||
return
|
||||
req = urllib.request.Request(
|
||||
NOTIFY_WEBHOOK_URL,
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10):
|
||||
pass
|
||||
|
||||
|
||||
def _worker():
|
||||
while True:
|
||||
payload = _queue.get()
|
||||
try:
|
||||
_send_now(payload)
|
||||
except Exception: # noqa: BLE001
|
||||
print(f"[owner_notify] send for booking "
|
||||
f"{payload['booking'].get('booking_id')} failed:", flush=True)
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
_queue.task_done()
|
||||
|
||||
|
||||
threading.Thread(target=_worker, daemon=True).start()
|
||||
|
||||
|
||||
def notify(client, booking, event):
|
||||
"""Queue a best-effort owner notification for a booking create/cancel/
|
||||
reschedule (#23). event is "created"/"cancelled"/"rescheduled" -- booking
|
||||
status alone can't distinguish a fresh booking from a rescheduled one
|
||||
(both land as "confirmed"), and the n8n message text needs to say which
|
||||
happened. Fires for every notify_channel value, including one that isn't
|
||||
"telegram" (or is unset) -- that's not an error, the n8n workflow this
|
||||
webhook feeds is what decides whether a given channel actually sends
|
||||
anything, which keeps this call site the same regardless of how many
|
||||
channels exist in the future."""
|
||||
payload = {
|
||||
"event": event,
|
||||
"booking": _serialize_booking(booking),
|
||||
"business_name": (client or {}).get("business_name") or "",
|
||||
"notify_channel": (client or {}).get("notify_channel"),
|
||||
}
|
||||
_queue.put(payload)
|
||||
Reference in New Issue
Block a user