From 084aefef71a55dadba6a6451b7867901fe3bdbdd Mon Sep 17 00:00:00 2001 From: danielcolquitt Date: Sun, 2 Aug 2026 15:45:53 +0100 Subject: [PATCH] Handle FCM events without ding IDs --- ring_doorbell/listen/eventlistener.py | 13 ++++++++++--- tests/test_listen.py | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/ring_doorbell/listen/eventlistener.py b/ring_doorbell/listen/eventlistener.py index 660b91c..bdc324d 100644 --- a/ring_doorbell/listen/eventlistener.py +++ b/ring_doorbell/listen/eventlistener.py @@ -320,9 +320,16 @@ def _get_ring_event(self, msg_data: dict) -> RingEvent | None: event_kind = PUSH_NOTIFICATION_KINDS.get(event_category, "Unknown") device = data["device"] event = data["event"] - event_id = int(event["ding"]["id"]) - created_at = event["ding"]["created_at"] + ding = event["ding"] + created_at = ding["created_at"] create_seconds = parse_datetime(created_at).timestamp() + # Some FCM payloads omit ding.id; created_at remains stable across updates. + event_id_value = ding.get("id") + event_id = ( + int(event_id_value) + if event_id_value is not None + else int(create_seconds * 1000) + ) return RingEvent( event_id, device["id"], @@ -331,7 +338,7 @@ def _get_ring_event(self, msg_data: dict) -> RingEvent | None: kind=event_kind, now=create_seconds, expires_in=DEFAULT_LISTEN_EVENT_EXPIRES_IN, - state=event["ding"]["subtype"], + state=ding["subtype"], ) def _get_legacy_ring_event(self, gcm_data: dict) -> RingEvent | None: diff --git a/tests/test_listen.py b/tests/test_listen.py index 2b3c54b..77f9915 100644 --- a/tests/test_listen.py +++ b/tests/test_listen.py @@ -74,6 +74,27 @@ async def test_active_dings(auth, mocker): await listener.stop() +def test_notification_without_ding_id(auth): + """Test notifications without a Ring event ID are still processed.""" + ring = Ring(auth) + listener = RingEventListener(ring) + received_events = [] + listener.add_notification_callback(received_events.append) + + msg = load_alert_v2("camera_motion", 123456782) + data = json.loads(msg["data"]["data"]) + del data["event"]["ding"]["id"] + msg["data"]["data"] = json.dumps(data) + + listener._on_notification(msg, "1234567") + listener._on_notification(msg, "1234568") + + assert received_events[0].id > 0 + assert received_events[1].id == received_events[0].id + assert received_events[0].is_update is False + assert received_events[1].is_update is True + + async def test_ding_expirey(auth, mocker, freezer: FrozenDateTimeFactory): ring = Ring(auth) listener = RingEventListener(ring)