def send_messages_for_new_subscribers( user_profile: UserProfile, subscribers: Set[UserProfile], new_subscriptions: Dict[str, List[str]], email_to_user_profile: Dict[str, UserProfile], created_streams: List[Stream], announce: bool, ) -> None: """ If you are subscribing lots of new users to new streams, this function can be pretty expensive in terms of generating lots of queries and sending lots of messages. We isolate the code partly to make it easier to test things like excessive query counts by mocking this function so that it doesn't drown out query counts from other code. """ bots = {subscriber.email: subscriber.is_bot for subscriber in subscribers} newly_created_stream_names = {s.name for s in created_streams} realm = user_profile.realm mention_backend = MentionBackend(realm.id) # Inform the user if someone else subscribed them to stuff, # or if a new stream was created with the "announce" option. notifications = [] if new_subscriptions: for email, subscribed_stream_names in new_subscriptions.items(): if email == user_profile.email: # Don't send a Zulip if you invited yourself. continue if bots[email]: # Don't send invitation Zulips to bots continue # For each user, we notify them about newly subscribed streams, except for # streams that were newly created. notify_stream_names = set( subscribed_stream_names) - newly_created_stream_names if not notify_stream_names: continue recipient_user = email_to_user_profile[email] sender = get_system_bot(settings.NOTIFICATION_BOT, recipient_user.realm_id) msg = you_were_just_subscribed_message( acting_user=user_profile, recipient_user=recipient_user, stream_names=notify_stream_names, ) notifications.append( internal_prep_private_message( realm=realm, sender=sender, recipient_user=recipient_user, content=msg, mention_backend=mention_backend, )) if announce and len(created_streams) > 0: notifications_stream = user_profile.realm.get_notifications_stream() if notifications_stream is not None: with override_language( notifications_stream.realm.default_language): if len(created_streams) > 1: content = _( "{user_name} created the following streams: {stream_str}." ) else: content = _( "{user_name} created a new stream {stream_str}.") topic = _("new streams") content = content.format( user_name=silent_mention_syntax_for_user(user_profile), stream_str=", ".join(f"#**{s.name}**" for s in created_streams), ) sender = get_system_bot(settings.NOTIFICATION_BOT, notifications_stream.realm_id) notifications.append( internal_prep_stream_message( sender=sender, stream=notifications_stream, topic=topic, content=content, ), ) if not user_profile.realm.is_zephyr_mirror_realm and len( created_streams) > 0: sender = get_system_bot(settings.NOTIFICATION_BOT, user_profile.realm_id) for stream in created_streams: with override_language(stream.realm.default_language): notifications.append( internal_prep_stream_message( sender=sender, stream=stream, topic=Realm.STREAM_EVENTS_NOTIFICATION_TOPIC, content= _("**{policy}** stream created by {user_name}. **Description:**" ).format( user_name=silent_mention_syntax_for_user( user_profile), policy=get_stream_permission_policy_name( invite_only=stream.invite_only, history_public_to_subscribers=stream. history_public_to_subscribers, is_web_public=stream.is_web_public, ), ) + f"\n```` quote\n{stream.description}\n````", ), ) if len(notifications) > 0: do_send_messages(notifications, mark_as_read=[user_profile.id])
def check_update_message( user_profile: UserProfile, message_id: int, stream_id: Optional[int] = None, topic_name: Optional[str] = None, propagate_mode: str = "change_one", send_notification_to_old_thread: bool = True, send_notification_to_new_thread: bool = True, content: Optional[str] = None, ) -> int: """This will update a message given the message id and user profile. It checks whether the user profile has the permission to edit the message and raises a JsonableError if otherwise. It returns the number changed. """ message, ignored_user_message = access_message(user_profile, message_id) if not user_profile.realm.allow_message_editing: raise JsonableError( _("Your organization has turned off message editing")) # The zerver/views/message_edit.py call point already strips this # via REQ_topic; so we can delete this line if we arrange a # contract where future callers in the embedded bots system strip # use REQ_topic as well (or otherwise are guaranteed to strip input). if topic_name is not None: topic_name = topic_name.strip() if topic_name == message.topic_name(): topic_name = None validate_message_edit_payload(message, stream_id, topic_name, propagate_mode, content) is_no_topic_msg = message.topic_name() == "(no topic)" if content is not None or topic_name is not None: if not can_edit_content_or_topic(message, user_profile, is_no_topic_msg, content, topic_name): raise JsonableError( _("You don't have permission to edit this message")) # If there is a change to the content, check that it hasn't been too long # Allow an extra 20 seconds since we potentially allow editing 15 seconds # past the limit, and in case there are network issues, etc. The 15 comes # from (min_seconds_to_edit + seconds_left_buffer) in message_edit.js; if # you change this value also change those two parameters in message_edit.js. edit_limit_buffer = 20 if content is not None and user_profile.realm.message_content_edit_limit_seconds > 0: deadline_seconds = user_profile.realm.message_content_edit_limit_seconds + edit_limit_buffer if (timezone_now() - message.date_sent) > datetime.timedelta( seconds=deadline_seconds): raise JsonableError( _("The time limit for editing this message has passed")) # If there is a change to the topic, check that the user is allowed to # edit it and that it has not been too long. If this is not the user who # sent the message, they are not the admin, and the time limit for editing # topics is passed, raise an error. if (topic_name is not None and message.sender != user_profile and not user_profile.is_realm_admin and not user_profile.is_moderator and not is_no_topic_msg): deadline_seconds = Realm.DEFAULT_COMMUNITY_TOPIC_EDITING_LIMIT_SECONDS + edit_limit_buffer if (timezone_now() - message.date_sent) > datetime.timedelta( seconds=deadline_seconds): raise JsonableError( _("The time limit for editing this message's topic has passed") ) rendering_result = None links_for_embed: Set[str] = set() prior_mention_user_ids: Set[int] = set() mention_data: Optional[MentionData] = None if content is not None: if content.rstrip() == "": content = "(deleted)" content = normalize_body(content) mention_backend = MentionBackend(user_profile.realm_id) mention_data = MentionData( mention_backend=mention_backend, content=content, ) prior_mention_user_ids = get_mentions_for_message_updates(message.id) # We render the message using the current user's realm; since # the cross-realm bots never edit messages, this should be # always correct. # Note: If rendering fails, the called code will raise a JsonableError. rendering_result = render_incoming_message( message, content, user_profile.realm, mention_data=mention_data, ) links_for_embed |= rendering_result.links_for_preview if message.is_stream_message() and rendering_result.mentions_wildcard: stream = access_stream_by_id(user_profile, message.recipient.type_id)[0] if not wildcard_mention_allowed(message.sender, stream): raise JsonableError( _("You do not have permission to use wildcard mentions in this stream." )) new_stream = None number_changed = 0 if stream_id is not None: assert message.is_stream_message() if not user_profile.can_move_messages_between_streams(): raise JsonableError( _("You don't have permission to move this message")) try: access_stream_by_id(user_profile, message.recipient.type_id) except JsonableError: raise JsonableError( _("You don't have permission to move this message due to missing access to its stream" )) new_stream = access_stream_by_id(user_profile, stream_id, require_active=True)[0] check_stream_access_based_on_stream_post_policy( user_profile, new_stream) number_changed = do_update_message( user_profile, message, new_stream, topic_name, propagate_mode, send_notification_to_old_thread, send_notification_to_new_thread, content, rendering_result, prior_mention_user_ids, mention_data, ) if links_for_embed: event_data = { "message_id": message.id, "message_content": message.content, # The choice of `user_profile.realm_id` rather than # `sender.realm_id` must match the decision made in the # `render_incoming_message` call earlier in this function. "message_realm_id": user_profile.realm_id, "urls": list(links_for_embed), } queue_json_publish("embed_links", event_data) return number_changed