def approve_sponsorship(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: from zerver.actions.message_send import internal_send_private_message from zerver.actions.realm_settings import do_change_realm_plan_type do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD_FREE, acting_user=acting_user) customer = get_customer_by_realm(realm) if customer is not None and customer.sponsorship_pending: customer.sponsorship_pending = False customer.save(update_fields=["sponsorship_pending"]) RealmAuditLog.objects.create( realm=realm, acting_user=acting_user, event_type=RealmAuditLog.REALM_SPONSORSHIP_APPROVED, event_time=timezone_now(), ) notification_bot = get_system_bot(settings.NOTIFICATION_BOT, realm.id) for user in realm.get_human_billing_admin_and_realm_owner_users(): with override_language(user.default_language): # Using variable to make life easier for translators if these details change. plan_name = "Zulip Cloud Standard" emoji = ":tada:" message = _( f"Your organization's request for sponsored hosting has been approved! {emoji}.\n" f"You have been upgraded to {plan_name}, free of charge." ) internal_send_private_message(notification_bot, user, message)
def send_welcome_bot_response(send_request: SendMessageRequest) -> None: """Given the send_request object for a private message from the user to welcome-bot, trigger the welcome-bot reply.""" welcome_bot = get_system_bot(settings.WELCOME_BOT, send_request.message.sender.realm_id) human_response_lower = send_request.message.content.lower() content = select_welcome_bot_response(human_response_lower) internal_send_private_message(welcome_bot, send_request.message.sender, content)
def setUp(self) -> None: super().setUp() # This emulates the welcome message sent by the welcome bot to [email protected] # This is only a quick fix - ideally, we would have this message sent by the initialization # code in populate_db.py user = self.example_user("hamlet") welcome_bot = get_system_bot(settings.WELCOME_BOT, user.realm_id) content = "Shortened welcome message." internal_send_private_message(welcome_bot, user, content)
def process_missed_message(to: str, message: EmailMessage) -> None: mm_address = get_usable_missed_message_address(to) mm_address.increment_times_used() user_profile = mm_address.user_profile topic = mm_address.message.topic_name() if mm_address.message.recipient.type == Recipient.PERSONAL: # We need to reply to the sender so look up their personal recipient_id recipient = mm_address.message.sender.recipient else: recipient = mm_address.message.recipient if not is_user_active(user_profile): logger.warning( "Sending user is not active. Ignoring this message notification email." ) return body = construct_zulip_body(message, user_profile.realm) assert recipient is not None if recipient.type == Recipient.STREAM: stream = get_stream_by_id_in_realm(recipient.type_id, user_profile.realm) send_mm_reply_to_stream(user_profile, stream, topic, body) recipient_str = stream.name elif recipient.type == Recipient.PERSONAL: display_recipient = get_display_recipient(recipient) assert not isinstance(display_recipient, str) recipient_str = display_recipient[0]["email"] recipient_user = get_user(recipient_str, user_profile.realm) internal_send_private_message(user_profile, recipient_user, body) elif recipient.type == Recipient.HUDDLE: display_recipient = get_display_recipient(recipient) assert not isinstance(display_recipient, str) emails = [user_dict["email"] for user_dict in display_recipient] recipient_str = ", ".join(emails) internal_send_huddle_message(user_profile.realm, user_profile, emails, body) else: raise AssertionError("Invalid recipient type!") logger.info( "Successfully processed email from user %s to %s", user_profile.id, recipient_str, )
def send_message(self, message: Dict[str, Any]) -> Dict[str, Any]: if not self._rate_limit.is_legal(): self._rate_limit.show_error_and_exit() if message["type"] == "stream": message_id = internal_send_stream_message_by_name( self.user_profile.realm, self.user_profile, message["to"], message["topic"], message["content"], ) return {"id": message_id} assert message["type"] == "private" # Ensure that it's a comma-separated list, even though the # usual 'to' field could be either a List[str] or a str. recipients = ",".join(message["to"]).split(",") if len(message["to"]) == 0: raise EmbeddedBotEmptyRecipientsList(_("Message must have recipients!")) elif len(message["to"]) == 1: recipient_user = get_active_user(recipients[0], self.user_profile.realm) message_id = internal_send_private_message( self.user_profile, recipient_user, message["content"] ) else: message_id = internal_send_huddle_message( self.user_profile.realm, self.user_profile, recipients, message["content"] ) return {"id": message_id}
def send_mm_reply_to_stream(user_profile: UserProfile, stream: Stream, topic: str, body: str) -> None: try: check_send_message( sender=user_profile, client=get_client("Internal"), message_type_name="stream", message_to=[stream.id], topic_name=topic, message_content=body, ) except JsonableError as error: error_message = "Error sending message to stream {stream} via message notification email reply:\n{error}".format( stream=stream.name, error=error.msg) internal_send_private_message( get_system_bot(settings.NOTIFICATION_BOT, user_profile.realm_id), user_profile, error_message, )
def send_initial_pms(user: UserProfile) -> None: organization_setup_text = "" # We need to override the language in this code path, because it's # called from account registration, which is a pre-account API # request and thus may not have the user's language context yet. with override_language(user.default_language): if user.is_realm_admin: help_url = user.realm.uri + "/help/getting-your-organization-started-with-zulip" organization_setup_text = (" " + _( "We also have a guide for [Setting up your organization]({help_url})." )).format(help_url=help_url) welcome_msg = _("Hello, and welcome to Zulip!") + "👋" demo_org_warning = "" if user.realm.demo_organization_scheduled_deletion_date is not None: demo_org_warning = (_( "Note that this is a [demo organization]({demo_org_help_url}) and will be " "**automatically deleted** in 30 days.") + "\n\n") content = "".join([ welcome_msg + " ", _("This is a private message from me, Welcome Bot.") + "\n\n", _("If you are new to Zulip, check out our [Getting started guide]({getting_started_url})!" ), "{organization_setup_text}" + "\n\n", "{demo_org_warning}", _("I can also help you get set up! Just click anywhere on this message or press `r` to reply." ) + "\n\n", _("Here are a few messages I understand:") + " ", bot_commands(), ]) content = content.format( organization_setup_text=organization_setup_text, demo_org_warning=demo_org_warning, demo_org_help_url="/help/demo-organizations", getting_started_url="/help/getting-started-with-zulip", ) internal_send_private_message( get_system_bot(settings.WELCOME_BOT, user.realm_id), user, content)
def _send_cross_realm_personal_message(self) -> int: # Send message from bot to users from different realm. bot_email = "*****@*****.**" internal_realm = get_realm(settings.SYSTEM_BOT_REALM) zulip_user = self.example_user("hamlet") msg_id = internal_send_private_message( sender=get_system_bot(bot_email, internal_realm.id), recipient_user=zulip_user, content="test message", ) assert msg_id is not None return msg_id
def process_new_human_user( user_profile: UserProfile, prereg_user: Optional[PreregistrationUser] = None, default_stream_groups: Sequence[DefaultStreamGroup] = [], realm_creation: bool = False, ) -> None: realm = user_profile.realm mit_beta_user = realm.is_zephyr_mirror_realm if prereg_user is not None: streams: List[Stream] = list(prereg_user.streams.all()) acting_user: Optional[UserProfile] = prereg_user.referred_by # A PregistrationUser should not be used for another UserProfile assert prereg_user.created_user is None, "PregistrationUser should not be reused" else: streams = [] acting_user = None # If the user's invitation didn't explicitly list some streams, we # add the default streams if len(streams) == 0: streams = get_default_subs(user_profile) for default_stream_group in default_stream_groups: default_stream_group_streams = default_stream_group.streams.all() for stream in default_stream_group_streams: if stream not in streams: streams.append(stream) bulk_add_subscriptions( realm, streams, [user_profile], from_user_creation=True, acting_user=acting_user, ) add_new_user_history(user_profile, streams) # mit_beta_users don't have a referred_by field if (not mit_beta_user and prereg_user is not None and prereg_user.referred_by is not None and prereg_user.referred_by.is_active): # This is a cross-realm private message. with override_language(prereg_user.referred_by.default_language): internal_send_private_message( get_system_bot(settings.NOTIFICATION_BOT, prereg_user.referred_by.realm_id), prereg_user.referred_by, _("{user} accepted your invitation to join Zulip!").format( user=f"{user_profile.full_name} <`{user_profile.email}`>"), ) # Revoke all preregistration users except prereg_user, and link prereg_user to # the created user if prereg_user is None: assert not realm_creation, "realm_creation should only happen with a PreregistrationUser" if prereg_user is not None: prereg_user.status = confirmation_settings.STATUS_ACTIVE prereg_user.created_user = user_profile prereg_user.save(update_fields=["status", "created_user"]) # In the special case of realm creation, there can be no additional PreregistrationUser # for us to want to modify - because other realm_creation PreregistrationUsers should be # left usable for creating different realms. if not realm_creation: # Mark any other PreregistrationUsers in the realm that are STATUS_ACTIVE as # inactive so we can keep track of the PreregistrationUser we # actually used for analytics. if prereg_user is not None: PreregistrationUser.objects.filter( email__iexact=user_profile.delivery_email, realm=user_profile.realm).exclude(id=prereg_user.id).update( status=confirmation_settings.STATUS_REVOKED) else: PreregistrationUser.objects.filter( email__iexact=user_profile.delivery_email, realm=user_profile.realm).update( status=confirmation_settings.STATUS_REVOKED) if prereg_user is not None and prereg_user.referred_by is not None: notify_invites_changed(user_profile.realm) notify_new_user(user_profile) # Clear any scheduled invitation emails to prevent them # from being sent after the user is created. clear_scheduled_invitation_emails(user_profile.delivery_email) if realm.send_welcome_emails: enqueue_welcome_emails(user_profile, realm_creation) # We have an import loop here; it's intentional, because we want # to keep all the onboarding code in zerver/lib/onboarding.py. from zerver.lib.onboarding import send_initial_pms send_initial_pms(user_profile)
def consume(self, event: Dict[str, Any]) -> None: start = time.time() if event["type"] == "mark_stream_messages_as_read": user_profile = get_user_profile_by_id(event["user_profile_id"]) for recipient_id in event["stream_recipient_ids"]: count = do_mark_stream_messages_as_read( user_profile, recipient_id) logger.info( "Marked %s messages as read for user %s, stream_recipient_id %s", count, user_profile.id, recipient_id, ) elif event["type"] == "mark_stream_messages_as_read_for_everyone": # This event is generated by the stream deactivation code path. batch_size = 100 offset = 0 while True: messages = Message.objects.filter( recipient_id=event["stream_recipient_id"]).order_by( "id")[offset:offset + batch_size] UserMessage.objects.filter(message__in=messages).extra( where=[UserMessage.where_unread()]).update( flags=F("flags").bitor(UserMessage.flags.read)) offset += len(messages) if len(messages) < batch_size: break logger.info( "Marked %s messages as read for all users, stream_recipient_id %s", offset, event["stream_recipient_id"], ) elif event["type"] == "clear_push_device_tokens": try: clear_push_device_tokens(event["user_profile_id"]) except PushNotificationBouncerRetryLaterError: def failure_processor(event: Dict[str, Any]) -> None: logger.warning( "Maximum retries exceeded for trigger:%s event:clear_push_device_tokens", event["user_profile_id"], ) retry_event(self.queue_name, event, failure_processor) elif event["type"] == "realm_export": realm = Realm.objects.get(id=event["realm_id"]) output_dir = tempfile.mkdtemp(prefix="zulip-export-") export_event = RealmAuditLog.objects.get(id=event["id"]) user_profile = get_user_profile_by_id(event["user_profile_id"]) try: public_url = export_realm_wrapper( realm=realm, output_dir=output_dir, threads=6, upload=True, public_only=True, ) except Exception: export_event.extra_data = orjson.dumps( dict(failed_timestamp=timezone_now().timestamp(), )).decode() export_event.save(update_fields=["extra_data"]) logging.exception( "Data export for %s failed after %s", user_profile.realm.string_id, time.time() - start, stack_info=True, ) notify_realm_export(user_profile) return assert public_url is not None # Update the extra_data field now that the export is complete. export_event.extra_data = orjson.dumps( dict(export_path=urllib.parse.urlparse(public_url).path, )).decode() export_event.save(update_fields=["extra_data"]) # Send a private message notification letting the user who # triggered the export know the export finished. with override_language(user_profile.default_language): content = _( "Your data export is complete and has been uploaded here:\n\n{public_url}" ).format(public_url=public_url) internal_send_private_message( sender=get_system_bot(settings.NOTIFICATION_BOT, realm.id), recipient_user=user_profile, content=content, ) # For future frontend use, also notify administrator # clients that the export happened. notify_realm_export(user_profile) logging.info( "Completed data export for %s in %s", user_profile.realm.string_id, time.time() - start, ) elif event["type"] == "reupload_realm_emoji": # This is a special event queued by the migration for reuploading emojis. # We don't want to run the necessary code in the actual migration, so it simply # queues the necessary event, and the actual work is done here in the queue worker. realm = Realm.objects.get(id=event["realm_id"]) logger.info("Processing reupload_realm_emoji event for realm %s", realm.id) handle_reupload_emojis_event(realm, logger) elif event["type"] == "soft_reactivate": user_profile = get_user_profile_by_id(event["user_profile_id"]) reactivate_user_if_soft_deactivated(user_profile) end = time.time() logger.info("deferred_work processed %s event (%dms)", event["type"], (end - start) * 1000)