Repository: zulip/zulip
Issue: message embeds: Fix nonstandard update_message event for message embeds

In `do_update_embedded_data`, we generate a variant `update_message` event, which has not been properly maintained and doesn't actually match the normal format.  I fixed one issue in 00fd9afad5147beb373caf3410ddc34b0c68a83f; but we should figure out how to make this event actually match the format of a normal `update_message` event, and then update the `GET /events` documentation with clear advice on handling it.  

I'd like us to move that event to be more clearly match the format of normal `update_message` events, with perhaps some clear marker for these variant events.

@amanagr @showell I'd be interested in your thoughts on this.

Changed files:
zerver/tornado/event_queue.py

diff --git a/zerver/tornado/event_queue.py b/zerver/tornado/event_queue.py
index d725a7d39e..72d8ba048f 100644
--- a/zerver/tornado/event_queue.py
+++ b/zerver/tornado/event_queue.py
@@ -1129,50 +1129,62 @@ def process_message_update_event(
     stream_name = event_template.get("stream_name")
     message_id = event_template["message_id"]
 
+    # TODO/compatibility: Modern `update_message` events contain the
+    # rendering_only key, which indicates whether the update is a link
+    # preview rendering update (not a human action). However, because
+    # events may be in the notify_tornado queue at the time we
+    # upgrade, we need the below logic to compute rendering_only based
+    # on the `user_id` key not being present in legacy events that
+    # would have had rendering_only set. Remove this check when one
+    # can no longer directly update from 4.x to main.
+    if "rendering_only" in event_template:
+        rendering_only_update = event_template["rendering_only"]
+    else:
+        rendering_only_update = "user_id" not in event_template
+
     for user_data in users:
         user_profile_id = user_data["id"]
 
-        if "user_id" in event_template:
-            # The user we'll get here will be the sender if the message's
-            # content was edited, and the editor for topic edits. That's
-            # the correct "acting_user" for both cases.
-            acting_user_id = event_template["user_id"]
-        else:
-            # Events without a `user_id` field come from the do_update_embedded_data
-            # code path, and represent just rendering previews; there should be no
-            # real content changes.
-            # It doesn't really matter what we set `acting_user_id` in this case,
-            # because we know this event isn't meant to send notifications.
-            acting_user_id = user_profile_id
-
         user_event = dict(event_template)  # shallow copy, but deep enough for our needs
         for key in user_data.keys():
             if key != "id":
                 user_event[key] = user_data[key]
 
-        flags: Collection[str] = user_event["flags"]
-        user_notifications_data = UserMessageNotificationsData.from_user_id_sets(
-            user_id=user_profile_id,
-            flags=flags,
-            private_message=(stream_name is None),
-            online_push_user_ids=online_push_user_ids,
-            pm_mention_push_disabled_user_ids=pm_mention_push_disabled_user_ids,
-            pm_mention_email_disabled_user_ids=pm_mention_email_disabled_user_ids,
-            stream_push_user_ids=stream_push_user_ids,
-            stream_email_user_ids=stream_email_user_ids,
-            wildcard_mention_user_ids=wildcard_mention_user_ids,
-            muted_sender_user_ids=muted_sender_user_ids,
-            all_bot_user_ids=all_bot_user_ids,
-        )
+        # Events where `rendering_only_update` is True come from the
+        # do_update_embedded_data code path, and represent rendering
+        # previews; there should be no real content changes.
+        # Therefore, we know only events where `rendering_only_update`
+        # is False possibly send notifications.
+        if not rendering_only_update:
 
-        maybe_enqueue_notifications_for_message_update(
-            user_notifications_data=user_notifications_data,
-            message_id=message_id,
-            acting_user_id=acting_user_id,
-            private_message=(stream_name is None),
-            presence_idle=(user_profile_id in presence_idle_user_ids),
-            prior_mentioned=(user_profile_id in prior_mention_user_ids),
-        )
+            # The user we'll get here will be the sender if the message's
+            # content was edited, and the editor for topic edits. That's
+            # the correct "acting_user" for both cases.
+            acting_user_id = event_template["user_id"]
+
+            flags: Collection[str] = user_event["flags"]
+            user_notifications_data = UserMessageNotificationsData.from_user_id_sets(
+                user_id=user_profile_id,
+                flags=flags,
+                private_message=(stream_name is None),
+                online_push_user_ids=online_push_user_ids,
+                pm_mention_push_disabled_user_ids=pm_mention_push_disabled_user_ids,
+                pm_mention_email_disabled_user_ids=pm_mention_email_disabled_user_ids,
+                stream_push_user_ids=stream_push_user_ids,
+                stream_email_user_ids=stream_email_user_ids,
+                wildcard_mention_user_ids=wildcard_mention_user_ids,
+                muted_sender_user_ids=muted_sender_user_ids,
+                all_bot_user_ids=all_bot_user_ids,
+            )
+
+            maybe_enqueue_notifications_for_message_update(
+                user_notifications_data=user_notifications_data,
+                message_id=message_id,
+                acting_user_id=acting_user_id,
+                private_message=(stream_name is None),
+                presence_idle=(user_profile_id in presence_idle_user_ids),
+                prior_mentioned=(user_profile_id in prior_mention_user_ids),
+            )
 
         for client in get_client_descriptors_for_user(user_profile_id):
             if client.accepts_event(user_event):