@transaction.atomic
def do_update_message(user_profile: UserProfile, message: Message, new_stream: Optional[Stream], topic_name: Optional[str], propagate_mode: str, send_notification_to_old_thread: bool, send_notification_to_new_thread: bool, content: Optional[str], rendered_content: Optional[str], prior_mention_user_ids: Set[int], mention_user_ids: Set[int], mention_data: Optional[MentionData]=None) -> int:
    "\n    The main function for message editing.  A message edit event can\n    modify:\n    * the message's content (in which case the caller will have\n      set both content and rendered_content),\n    * the topic, in which case the caller will have set topic_name\n    * or both\n\n    With topic edits, propagate_mode determines whether other message\n    also have their topics edited.\n    "
    timestamp = timezone_now()
    message.last_edit_time = timestamp
    event: Dict[(str, Any)] = {'type': 'update_message', 'user_id': user_profile.id, 'edit_timestamp': datetime_to_timestamp(timestamp), 'message_id': message.id}
    edit_history_event: Dict[(str, Any)] = {'user_id': user_profile.id, 'timestamp': event['edit_timestamp']}
    changed_messages = [message]
    stream_being_edited = None
    if message.is_stream_message():
        stream_id = message.recipient.type_id
        stream_being_edited = get_stream_by_id_in_realm(stream_id, user_profile.realm)
        event['stream_name'] = stream_being_edited.name
    ums = UserMessage.objects.filter(message=message.id)
    if (content is not None):
        assert (rendered_content is not None)
        assert (mention_data is not None)
        for group_id in message.mentions_user_group_ids:
            members = mention_data.get_group_members(group_id)
            message.mentions_user_ids.update(members)
        update_user_message_flags(message, ums)
        event['orig_content'] = message.content
        event['orig_rendered_content'] = message.rendered_content
        edit_history_event['prev_content'] = message.content
        edit_history_event['prev_rendered_content'] = message.rendered_content
        edit_history_event['prev_rendered_content_version'] = message.rendered_content_version
        message.content = content
        message.rendered_content = rendered_content
        message.rendered_content_version = markdown_version
        event['content'] = content
        event['rendered_content'] = rendered_content
        event['prev_rendered_content_version'] = message.rendered_content_version
        event['is_me_message'] = Message.is_status_message(content, rendered_content)
        message.has_attachment = check_attachment_reference_change(message)
        if message.is_stream_message():
            if (topic_name is not None):
                new_topic_name = topic_name
            else:
                new_topic_name = message.topic_name()
            stream_topic: Optional[StreamTopicTarget] = StreamTopicTarget(stream_id=stream_id, topic_name=new_topic_name)
        else:
            stream_topic = None
        info = get_recipient_info(recipient=message.recipient, sender_id=message.sender_id, stream_topic=stream_topic, possible_wildcard_mention=mention_data.message_has_wildcards())
        event['push_notify_user_ids'] = list(info['push_notify_user_ids'])
        event['stream_push_user_ids'] = list(info['stream_push_user_ids'])
        event['stream_email_user_ids'] = list(info['stream_email_user_ids'])
        event['prior_mention_user_ids'] = list(prior_mention_user_ids)
        event['mention_user_ids'] = list(mention_user_ids)
        event['presence_idle_user_ids'] = filter_presence_idle_user_ids(info['active_user_ids'])
        if message.mentions_wildcard:
            event['wildcard_mention_user_ids'] = list(info['wildcard_mention_user_ids'])
        else:
            event['wildcard_mention_user_ids'] = []
        do_update_mobile_push_notification(message, prior_mention_user_ids, info['stream_push_user_ids'])
    if ((topic_name is not None) or (new_stream is not None)):
        orig_topic_name = message.topic_name()
        event['propagate_mode'] = propagate_mode
        event['stream_id'] = message.recipient.type_id
    if (new_stream is not None):
        assert (content is None)
        assert message.is_stream_message()
        assert (stream_being_edited is not None)
        edit_history_event['prev_stream'] = stream_being_edited.id
        event[ORIG_TOPIC] = orig_topic_name
        message.recipient_id = new_stream.recipient_id
        event['new_stream_id'] = new_stream.id
        event['propagate_mode'] = propagate_mode
        subscribers = get_active_subscriptions_for_stream_id(stream_id).select_related('user_profile')
        subs_to_new_stream = list(get_active_subscriptions_for_stream_id(new_stream.id).select_related('user_profile'))
        new_stream_sub_ids = [user.user_profile_id for user in subs_to_new_stream]
        subs_losing_usermessages = [sub for sub in subscribers if (sub.user_profile_id not in new_stream_sub_ids)]
        subs_losing_access = [sub for sub in subs_losing_usermessages if sub.user_profile.is_guest]
        ums = ums.exclude(user_profile_id__in=[sub.user_profile_id for sub in subs_losing_usermessages])
    if (topic_name is not None):
        topic_name = truncate_topic(topic_name)
        message.set_topic_name(topic_name)
        event[ORIG_TOPIC] = orig_topic_name
        event[TOPIC_NAME] = topic_name
        event[TOPIC_LINKS] = topic_links(message.sender.realm_id, topic_name)
        edit_history_event[LEGACY_PREV_TOPIC] = orig_topic_name
    delete_event_notify_user_ids: List[int] = []
    if (propagate_mode in ['change_later', 'change_all']):
        assert ((topic_name is not None) or (new_stream is not None))
        messages_list = update_messages_for_topic_edit(message=message, propagate_mode=propagate_mode, orig_topic_name=orig_topic_name, topic_name=topic_name, new_stream=new_stream)
        changed_messages += messages_list
        if (new_stream is not None):
            assert (stream_being_edited is not None)
            message_ids = [msg.id for msg in changed_messages]
            UserMessage.objects.filter(user_profile_id__in=[sub.user_profile_id for sub in subs_losing_usermessages], message_id__in=message_ids).delete()
            delete_event: DeleteMessagesEvent = {'type': 'delete_message', 'message_ids': message_ids, 'message_type': 'stream', 'stream_id': stream_being_edited.id, 'topic': orig_topic_name}
            delete_event_notify_user_ids = [sub.user_profile_id for sub in subs_losing_access]
            send_event(user_profile.realm, delete_event, delete_event_notify_user_ids)
    if (message.edit_history is not None):
        edit_history = ujson.loads(message.edit_history)
        edit_history.insert(0, edit_history_event)
    else:
        edit_history = [edit_history_event]
    message.edit_history = ujson.dumps(edit_history)
    save_message_for_edit_use_case(message=message)
    realm_id: Optional[int] = None
    if (stream_being_edited is not None):
        realm_id = stream_being_edited.realm_id
    event['message_ids'] = update_to_dict_cache(changed_messages, realm_id)

    def user_info(um: UserMessage) -> Dict[(str, Any)]:
        return {'id': um.user_profile_id, 'flags': um.flags_list()}
    users_to_be_notified = list(map(user_info, ums))
    if (stream_being_edited is not None):
        if stream_being_edited.is_history_public_to_subscribers:
            subscribers = get_active_subscriptions_for_stream_id(stream_id)
            subscribers = subscribers.exclude(user_profile__long_term_idle=True)
            subscribers = subscribers.exclude(user_profile_id__in=[um.user_profile_id for um in ums])
            if (new_stream is not None):
                assert (delete_event_notify_user_ids is not None)
                subscribers = subscribers.exclude(user_profile_id__in=delete_event_notify_user_ids)
            subscriber_ids = [user.user_profile_id for user in subscribers]
            if (new_stream is not None):
                old_stream_unsubbed_guests = [sub for sub in subs_to_new_stream if (sub.user_profile.is_guest and (sub.user_profile_id not in subscriber_ids))]
                subscribers = subscribers.exclude(user_profile_id__in=[sub.user_profile_id for sub in old_stream_unsubbed_guests])
                subscriber_ids = [user.user_profile_id for user in subscribers]
            users_to_be_notified += list(map(subscriber_info, subscriber_ids))
    send_event(user_profile.realm, event, users_to_be_notified)
    if ((len(changed_messages) > 0) and (new_stream is not None) and (stream_being_edited is not None)):
        notify_topic_moved_streams(user_profile, stream_being_edited, orig_topic_name, new_stream, topic_name, send_notification_to_old_thread, send_notification_to_new_thread)
    return len(changed_messages)
The main function for message editing.  A message edit event can
modify:
* the message's content (in which case the caller will have
  set both content and rendered_content),
* the topic, in which case the caller will have set topic_name
* or both

With topic edits, propagate_mode determines whether other message
also have their topics edited.
@transaction.atomic
def do_update_message(user_profile: UserProfile, message: Message, new_stream: Optional[Stream], topic_name: Optional[str], propagate_mode: str, send_notification_to_old_thread: bool, send_notification_to_new_thread: bool, content: Optional[str], rendered_content: Optional[str], prior_mention_user_ids: Set[int], mention_user_ids: Set[int], mention_data: Optional[MentionData]=None) -> int:
    "\n    The main function for message editing.  A message edit event can\n    modify:\n    * the message's content (in which case the caller will have\n      set both content and rendered_content),\n    * the topic, in which case the caller will have set topic_name\n    * or both\n\n    With topic edits, propagate_mode determines whether other message\n    also have their topics edited.\n    "
    timestamp = timezone_now()
    message.last_edit_time = timestamp
    event: Dict[(str, Any)] = {'type': 'update_message', 'user_id': user_profile.id, 'edit_timestamp': datetime_to_timestamp(timestamp), 'message_id': message.id}
    edit_history_event: Dict[(str, Any)] = {'user_id': user_profile.id, 'timestamp': event['edit_timestamp']}
    changed_messages = [message]
    stream_being_edited = None
    if message.is_stream_message():
        stream_id = message.recipient.type_id
        stream_being_edited = get_stream_by_id_in_realm(stream_id, user_profile.realm)
        event['stream_name'] = stream_being_edited.name
    ums = UserMessage.objects.filter(message=message.id)
    if (content is not None):
        assert (rendered_content is not None)
        assert (mention_data is not None)
        for group_id in message.mentions_user_group_ids:
            members = mention_data.get_group_members(group_id)
            message.mentions_user_ids.update(members)
        update_user_message_flags(message, ums)
        event['orig_content'] = message.content
        event['orig_rendered_content'] = message.rendered_content
        edit_history_event['prev_content'] = message.content
        edit_history_event['prev_rendered_content'] = message.rendered_content
        edit_history_event['prev_rendered_content_version'] = message.rendered_content_version
        message.content = content
        message.rendered_content = rendered_content
        message.rendered_content_version = markdown_version
        event['content'] = content
        event['rendered_content'] = rendered_content
        event['prev_rendered_content_version'] = message.rendered_content_version
        event['is_me_message'] = Message.is_status_message(content, rendered_content)
        message.has_attachment = check_attachment_reference_change(message)
        if message.is_stream_message():
            if (topic_name is not None):
                new_topic_name = topic_name
            else:
                new_topic_name = message.topic_name()
            stream_topic: Optional[StreamTopicTarget] = StreamTopicTarget(stream_id=stream_id, topic_name=new_topic_name)
        else:
            stream_topic = None
        info = get_recipient_info(recipient=message.recipient, sender_id=message.sender_id, stream_topic=stream_topic, possible_wildcard_mention=mention_data.message_has_wildcards())
        event['push_notify_user_ids'] = list(info['push_notify_user_ids'])
        event['stream_push_user_ids'] = list(info['stream_push_user_ids'])
        event['stream_email_user_ids'] = list(info['stream_email_user_ids'])
        event['prior_mention_user_ids'] = list(prior_mention_user_ids)
        event['mention_user_ids'] = list(mention_user_ids)
        event['presence_idle_user_ids'] = filter_presence_idle_user_ids(info['active_user_ids'])
        if message.mentions_wildcard:
            event['wildcard_mention_user_ids'] = list(info['wildcard_mention_user_ids'])
        else:
            event['wildcard_mention_user_ids'] = []
        do_update_mobile_push_notification(message, prior_mention_user_ids, info['stream_push_user_ids'])
    if ((topic_name is not None) or (new_stream is not None)):
        orig_topic_name = message.topic_name()
        event['propagate_mode'] = propagate_mode
        event['stream_id'] = message.recipient.type_id
    if (new_stream is not None):
        assert (content is None)
        assert message.is_stream_message()
        assert (stream_being_edited is not None)
        edit_history_event['prev_stream'] = stream_being_edited.id
        event[ORIG_TOPIC] = orig_topic_name
        message.recipient_id = new_stream.recipient_id
        event['new_stream_id'] = new_stream.id
        event['propagate_mode'] = propagate_mode
        subscribers = get_active_subscriptions_for_stream_id(stream_id).select_related('user_profile')
        subs_to_new_stream = list(get_active_subscriptions_for_stream_id(new_stream.id).select_related('user_profile'))
        new_stream_sub_ids = [user.user_profile_id for user in subs_to_new_stream]
        subs_losing_usermessages = [sub for sub in subscribers if (sub.user_profile_id not in new_stream_sub_ids)]
        subs_losing_access = [sub for sub in subs_losing_usermessages if sub.user_profile.is_guest]
        ums = ums.exclude(user_profile_id__in=[sub.user_profile_id for sub in subs_losing_usermessages])
    if (topic_name is not None):
        topic_name = truncate_topic(topic_name)
        message.set_topic_name(topic_name)
        event[ORIG_TOPIC] = orig_topic_name
        event[TOPIC_NAME] = topic_name
        event[TOPIC_LINKS] = topic_links(message.sender.realm_id, topic_name)
        edit_history_event[LEGACY_PREV_TOPIC] = orig_topic_name
    delete_event_notify_user_ids: List[int] = []
    if (propagate_mode in ['change_later', 'change_all']):
        assert ((topic_name is not None) or (new_stream is not None))
        messages_list = update_messages_for_topic_edit(message=message, propagate_mode=propagate_mode, orig_topic_name=orig_topic_name, topic_name=topic_name, new_stream=new_stream)
        changed_messages += messages_list
        if (new_stream is not None):
            assert (stream_being_edited is not None)
            message_ids = [msg.id for msg in changed_messages]
            UserMessage.objects.filter(user_profile_id__in=[sub.user_profile_id for sub in subs_losing_usermessages], message_id__in=message_ids).delete()
            delete_event: DeleteMessagesEvent = {'type': 'delete_message', 'message_ids': message_ids, 'message_type': 'stream', 'stream_id': stream_being_edited.id, 'topic': orig_topic_name}
            delete_event_notify_user_ids = [sub.user_profile_id for sub in subs_losing_access]
            send_event(user_profile.realm, delete_event, delete_event_notify_user_ids)
    if (message.edit_history is not None):
        edit_history = ujson.loads(message.edit_history)
        edit_history.insert(0, edit_history_event)
    else:
        edit_history = [edit_history_event]
    message.edit_history = ujson.dumps(edit_history)
    save_message_for_edit_use_case(message=message)
    realm_id: Optional[int] = None
    if (stream_being_edited is not None):
        realm_id = stream_being_edited.realm_id
    event['message_ids'] = update_to_dict_cache(changed_messages, realm_id)

    def user_info(um: UserMessage) -> Dict[(str, Any)]:
        return {'id': um.user_profile_id, 'flags': um.flags_list()}
    users_to_be_notified = list(map(user_info, ums))
    if (stream_being_edited is not None):
        if stream_being_edited.is_history_public_to_subscribers:
            subscribers = get_active_subscriptions_for_stream_id(stream_id)
            subscribers = subscribers.exclude(user_profile__long_term_idle=True)
            subscribers = subscribers.exclude(user_profile_id__in=[um.user_profile_id for um in ums])
            if (new_stream is not None):
                assert (delete_event_notify_user_ids is not None)
                subscribers = subscribers.exclude(user_profile_id__in=delete_event_notify_user_ids)
            subscriber_ids = [user.user_profile_id for user in subscribers]
            if (new_stream is not None):
                old_stream_unsubbed_guests = [sub for sub in subs_to_new_stream if (sub.user_profile.is_guest and (sub.user_profile_id not in subscriber_ids))]
                subscribers = subscribers.exclude(user_profile_id__in=[sub.user_profile_id for sub in old_stream_unsubbed_guests])
                subscriber_ids = [user.user_profile_id for user in subscribers]
            users_to_be_notified += list(map(subscriber_info, subscriber_ids))
    send_event(user_profile.realm, event, users_to_be_notified)
    if ((len(changed_messages) > 0) and (new_stream is not None) and (stream_being_edited is not None)):
        notify_topic_moved_streams(user_profile, stream_being_edited, orig_topic_name, new_stream, topic_name, send_notification_to_old_thread, send_notification_to_new_thread)
    return len(changed_messages)