diff --git a/webhook_receiver_rev.py b/webhook_receiver_rev.py index 55122ba..3729e36 100644 --- a/webhook_receiver_rev.py +++ b/webhook_receiver_rev.py @@ -109,46 +109,36 @@ async def _parse_request(request: Request) -> tuple[str, list[dict]]: Some older/configured endpoints may still use form-encoded. This helper handles both so each endpoint doesn't need to know which format arrived. """ - content_type = request.headers.get("content-type", "") body = await request.body() - - # TEMP DIAGNOSTIC: log every push so we can see what Jimi actually sends. - log.info("push %s: content-type=%r body=%.300s", - request.url.path, content_type, - body.decode("utf-8", errors="replace") if body else "") - if not body: return "", [] - # ── Try JSON body first (integration push format) ────────────────────────── - if "application/json" in content_type or body.lstrip()[:1] == b"{": - try: - payload = json.loads(body) - token = str(payload.get("token", "")) - raw_dl = payload.get("data_list", []) - if isinstance(raw_dl, list): - items = raw_dl[:MAX_ITEMS_PER_POST] - elif isinstance(raw_dl, str): - items = _parse_data_list(raw_dl) - else: - items = [] - log.info("push: parsed JSON body — %d items", len(items)) - return token, items - except (json.JSONDecodeError, TypeError): - log.warning("push: JSON body parse failed") - - # ── Fall back to form-encoded ─────────────────────────────────────────────── + # Jimi integration push format (observed live): + # Content-Type: application/x-www-form-urlencoded + # Body: msgType=&data= + # The `data` field holds a single JSON object per event, not an array. try: form = await request.form() - token = str(form.get("token", "")) - raw_dl = str(form.get("data_list", "")) - items = _parse_data_list(raw_dl) if raw_dl else [] - log.info("push: parsed form body — %d items", len(items)) - return token, items except Exception: - log.warning("push: form body parse failed", exc_info=True) + log.warning("push: form parse failed", exc_info=True) + return "", [] - return "", [] + token = str(form.get("token", "")) + raw_data = form.get("data") or form.get("data_list") or "" + if not raw_data: + return token, [] + + try: + parsed = json.loads(raw_data) + except (json.JSONDecodeError, TypeError): + log.warning("push: data JSON parse failed — %.200s", raw_data) + return token, [] + + items = parsed if isinstance(parsed, list) else [parsed] + if len(items) > MAX_ITEMS_PER_POST: + log.warning("push: truncated %d → %d items", len(items), MAX_ITEMS_PER_POST) + items = items[:MAX_ITEMS_PER_POST] + return token, items def unix_to_ts(v) -> Optional[str]: @@ -351,9 +341,11 @@ async def push_alarm(request: Request): for item in items: try: cur.execute("SAVEPOINT sp") - imei = clean(item.get("deviceImei")) + # Jimi integration push uses `imei` + `alarmTime`, NOT the + # `deviceImei` + `gateTime` fields shown in the API docs. + imei = clean(item.get("imei") or item.get("deviceImei")) alarm_type = clean(item.get("alarmType")) - alarm_time = clean_ts(item.get("gateTime")) + alarm_time = clean_ts(item.get("alarmTime") or item.get("gateTime")) # [BUG-02] Also guard alarm_type — NULL alarm_type violates NOT NULL constraint. if not imei or not alarm_time or not alarm_type: cur.execute("RELEASE SAVEPOINT sp")