Esempio n. 1
0
def add_reaction(
    request: HttpRequest,
    user_profile: UserProfile,
    message_id: int,
    emoji_name: str = REQ(),
    emoji_code: Optional[str] = REQ(default=None),
    reaction_type: str = REQ(default="unicode_emoji")
) -> HttpResponse:
    message, user_message = access_message(user_profile, message_id)

    if emoji_code is None:
        # The emoji_code argument is only required for rare corner
        # cases discussed in the long block comment below.  For simple
        # API clients, we allow specifying just the name, and just
        # look up the code using the current name->code mapping.
        emoji_code = emoji_name_to_emoji_code(message.sender.realm,
                                              emoji_name)[0]

    if Reaction.objects.filter(user_profile=user_profile,
                               message=message,
                               emoji_code=emoji_code,
                               reaction_type=reaction_type).exists():
        raise JsonableError(_("Reaction already exists."))

    query = Reaction.objects.filter(message=message,
                                    emoji_code=emoji_code,
                                    reaction_type=reaction_type)
    if query.exists():
        # If another user has already reacted to this message with
        # same emoji code, we treat the new reaction as a vote for the
        # existing reaction.  So the emoji name used by that earlier
        # reaction takes precendence over whatever was passed in this
        # request.  This is necessary to avoid a message having 2
        # "different" emoji reactions with the same emoji code (and
        # thus same image) on the same message, which looks ugly.
        #
        # In this "voting for an existing reaction" case, we shouldn't
        # check whether the emoji code and emoji name match, since
        # it's possible that the (emoji_type, emoji_name, emoji_code)
        # triple for this existing rection xmay not pass validation
        # now (e.g. because it is for a realm emoji that has been
        # since deactivated).  We still want to allow users to add a
        # vote any old reaction they see in the UI even if that is a
        # deactivated custom emoji, so we just use the emoji name from
        # the existing reaction with no further validation.
        emoji_name = query.first().emoji_name
    else:
        # Otherwise, use the name provided in this request, but verify
        # it is valid in the user's realm (e.g. not a deactivated
        # realm emoji).
        check_emoji_request(message.sender.realm, emoji_name, emoji_code,
                            reaction_type)

    if user_message is None:
        create_historical_message(user_profile, message)

    do_add_reaction(user_profile, message, emoji_name, emoji_code,
                    reaction_type)

    return json_success()
Esempio n. 2
0
def add_reaction_backend(request, user_profile, message_id, emoji_name):
    # type: (HttpRequest, UserProfile, int, Text) -> HttpResponse

    # access_message will throw a JsonableError exception if the user
    # cannot see the message (e.g. for messages to private streams).
    message, user_message = access_message(user_profile, message_id)

    check_valid_emoji(message.sender.realm, emoji_name)

    # We could probably just make this check be a try/except for the
    # IntegrityError from it already existing, but this is a bit cleaner.
    if Reaction.objects.filter(user_profile=user_profile,
                               message=message,
                               emoji_name=emoji_name).exists():
        raise JsonableError(_("Reaction already exists"))

    if user_message is None:
        # Users can see and react to messages sent to streams they
        # were not a subscriber to; in order to receive events for
        # those, we give the user a `historical` UserMessage objects
        # for the message.  This is the same trick we use for starring
        # messages.
        UserMessage.objects.create(user_profile=user_profile,
                                   message=message,
                                   flags=UserMessage.flags.historical | UserMessage.flags.read)

    do_add_reaction(user_profile, message, emoji_name)

    return json_success()
Esempio n. 3
0
def add_reaction_backend(request,
                         user_profile,
                         emoji_name=REQ('emoji'),
                         message_id=REQ('message_id',
                                        converter=to_non_negative_int)):
    # type: (HttpRequest, UserProfile, text_type, int) -> HttpResponse

    # access_message will throw a JsonableError exception if the user
    # cannot see the message (e.g. for messages to private streams).
    message = access_message(user_profile, message_id)[0]

    existing_emojis = set(
        message.sender.realm.get_emoji().keys()) or set(emoji_list)
    if emoji_name not in existing_emojis:
        raise JsonableError(_("Emoji '%s' does not exist" % (emoji_name, )))

    # We could probably just make this check be a try/except for the
    # IntegrityError from it already existing, but this is a bit cleaner.
    if Reaction.objects.filter(user_profile=user_profile,
                               message=message,
                               emoji_name=emoji_name).exists():
        raise JsonableError(_("Reaction already exists"))

    do_add_reaction(user_profile, message, emoji_name)

    return json_success()
Esempio n. 4
0
def add_reaction(request: HttpRequest, user_profile: UserProfile, message_id: int,
                 emoji_name: str=REQ(),
                 emoji_code: Optional[str]=REQ(default=None),
                 reaction_type: str=REQ(default="unicode_emoji")) -> HttpResponse:
    message, user_message = access_message(user_profile, message_id)

    if emoji_code is None:
        # The emoji_code argument is only required for rare corner
        # cases discussed in the long block comment below.  For simple
        # API clients, we allow specifying just the name, and just
        # look up the code using the current name->code mapping.
        emoji_code = emoji_name_to_emoji_code(message.sender.realm,
                                              emoji_name)[0]

    if Reaction.objects.filter(user_profile=user_profile,
                               message=message,
                               emoji_code=emoji_code,
                               reaction_type=reaction_type).exists():
        raise JsonableError(_("Reaction already exists."))

    query = Reaction.objects.filter(message=message,
                                    emoji_code=emoji_code,
                                    reaction_type=reaction_type)
    if query.exists():
        # If another user has already reacted to this message with
        # same emoji code, we treat the new reaction as a vote for the
        # existing reaction.  So the emoji name used by that earlier
        # reaction takes precendence over whatever was passed in this
        # request.  This is necessary to avoid a message having 2
        # "different" emoji reactions with the same emoji code (and
        # thus same image) on the same message, which looks ugly.
        #
        # In this "voting for an existing reaction" case, we shouldn't
        # check whether the emoji code and emoji name match, since
        # it's possible that the (emoji_type, emoji_name, emoji_code)
        # triple for this existing rection xmay not pass validation
        # now (e.g. because it is for a realm emoji that has been
        # since deactivated).  We still want to allow users to add a
        # vote any old reaction they see in the UI even if that is a
        # deactivated custom emoji, so we just use the emoji name from
        # the existing reaction with no further validation.
        emoji_name = query.first().emoji_name
    else:
        # Otherwise, use the name provided in this request, but verify
        # it is valid in the user's realm (e.g. not a deactivated
        # realm emoji).
        check_emoji_request(message.sender.realm, emoji_name,
                            emoji_code, reaction_type)

    if user_message is None:
        create_historical_message(user_profile, message)

    do_add_reaction(user_profile, message, emoji_name, emoji_code, reaction_type)

    return json_success()
def add_emoji_to_message() -> Dict[str, object]:
    user_profile = helpers.example_user("iago")

    # from OpenAPI format data in zulip.yaml
    message_id = 41
    emoji_name = "octopus"
    emoji_code = "1f419"
    reaction_type = "unicode_emoji"

    message = Message.objects.select_related().get(id=message_id)
    do_add_reaction(user_profile, message, emoji_name, emoji_code, reaction_type)

    return {}
Esempio n. 6
0
def add_emoji_to_message() -> Dict[str, List[Dict[None, None]]]:
    user_profile = helpers.example_user('iago')

    # from OpenAPI format data in zulip.yaml
    message_id = 41
    emoji_name = 'octopus'
    emoji_code = '1f419'
    reaction_type = 'unicode_emoji'

    message = Message.objects.select_related().get(id=message_id)
    do_add_reaction(user_profile, message, emoji_name, emoji_code, reaction_type)

    return {}
Esempio n. 7
0
def add_emoji_to_message() -> Dict[str, object]:
    user_profile = helpers.example_user("iago")

    # The message ID here is hardcoded based on the corresponding value
    # for the example message IDs we use in zulip.yaml.
    message_id = 44
    emoji_name = "octopus"
    emoji_code = "1f419"
    reaction_type = "unicode_emoji"

    message = Message.objects.select_related().get(id=message_id)
    do_add_reaction(user_profile, message, emoji_name, emoji_code, reaction_type)

    return {}
Esempio n. 8
0
def send_initial_realm_messages(realm):
    # type: (Realm) -> None
    welcome_bot = get_system_bot(settings.WELCOME_BOT)
    # Make sure each stream created in the realm creation process has at least one message below
    # Order corresponds to the ordering of the streams on the left sidebar, to make the initial Home
    # view slightly less overwhelming
    welcome_messages = [
        {'stream': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "welcome",
         'content': "This is a message on stream `%s` with the topic `welcome`. We'll use this stream "
         "for system-generated notifications." % (Realm.DEFAULT_NOTIFICATION_STREAM_NAME,)},
        {'stream': "core team",
         'topic': "private streams",
         'content': "This is a private stream. Only admins and people you invite "
         "to the stream will be able to see that this stream exists."},
        {'stream': "general",
         'topic': "welcome",
         'content': "Welcome to #**general**."},
        {'stream': "new members",
         'topic': "onboarding",
         'content': "A #**new members** stream is great for onboarding new members.\n\nIf you're "
         "reading this and aren't the first person here, introduce yourself in a new thread "
         "using your name as the topic! Type `c` or click on `New Topic` at the bottom of the "
         "screen to start a new topic."},
        {'stream': "zulip",
         'topic': "topic demonstration",
         'content': "Here is a message in one topic. Replies to this message will go to this topic."},
        {'stream': "zulip",
         'topic': "topic demonstration",
         'content': "A second message in this topic. With [turtles](/static/images/cute/turtle.png)!"},
        {'stream': "zulip",
         'topic': "second topic",
         'content': "This is a message in a second topic.\n\nTopics are similar to email subjects, "
         "in that each conversation should get its own topic. Keep them short, though; one "
         "or two words will do it!"},
    ]  # type: List[Dict[str, Text]]
    messages = [internal_prep_stream_message(
        realm, welcome_bot,
        message['stream'], message['topic'], message['content']) for message in welcome_messages]
    message_ids = do_send_messages(messages)

    # We find the one of our just-sent messages with turtle.png in it,
    # and react to it.  This is a bit hacky, but works and is kinda a
    # 1-off thing.
    turtle_message = Message.objects.get(
        id__in=message_ids,
        subject='topic demonstration',
        content__icontains='cute/turtle.png')
    do_add_reaction(welcome_bot, turtle_message, 'turtle')
Esempio n. 9
0
def send_initial_realm_messages(realm: Realm) -> None:
    welcome_bot = get_system_bot(settings.WELCOME_BOT)
    # Make sure each stream created in the realm creation process has at least one message below
    # Order corresponds to the ordering of the streams on the left sidebar, to make the initial Home
    # view slightly less overwhelming
    welcome_messages = [
        {'stream': Realm.INITIAL_PRIVATE_STREAM_NAME,
         'topic': "private streams",
         'content': "This is a private stream, as indicated by the "
         "lock icon next to the stream name. Private streams are only visible to stream members. "
         "\n\nTo manage this stream, go to [Stream settings](#streams/subscribed) and click on "
         "`%(initial_private_stream_name)s`."},
        {'stream': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "topic demonstration",
         'content': "This is a message on stream #**%(default_notification_stream_name)s** with the "
         "topic `topic demonstration`."},
        {'stream': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "topic demonstration",
         'content': "Topics are a lightweight tool to keep conversations organized. "
         "You can learn more about topics at [Streams and topics](/help/about-streams-and-topics). "},
        {'stream': realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "swimming turtles",
         'content': "This is a message on stream #**%(default_notification_stream_name)s** with the "
         "topic `swimming turtles`.  Why turtles?  Why not.  Who cares anyway?  Excercise your right to Free Speech and don't worry about turtles. \n\n"
         #'content': "Why turtles?  Why not.  Who cares anyway?  Excercise your right to Free Speech and don't worry about turtles."
         "\n\n[](/static/images/cute/turtle.png)"
         "\n\n[Start a new topic](/help/start-a-new-topic) any time you're not replying to a "
         "previous message."},
    ]  # type: List[Dict[str, str]]
    messages = [internal_prep_stream_message_by_name(
        realm, welcome_bot, message['stream'], message['topic'],
        message['content'] % {
            'initial_private_stream_name': Realm.INITIAL_PRIVATE_STREAM_NAME,
            'default_notification_stream_name': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
        }
    ) for message in welcome_messages]
    message_ids = do_send_messages(messages)

    # We find the one of our just-sent messages with turtle.png in it,
    # and react to it.  This is a bit hacky, but works and is kinda a
    # 1-off thing.
    turtle_message = Message.objects.get(
        id__in=message_ids,
        content__icontains='cute/turtle.png')
    (emoji_code, reaction_type) = emoji_name_to_emoji_code(realm, 'turtle')
    do_add_reaction(welcome_bot, turtle_message, 'turtle', emoji_code, reaction_type)
Esempio n. 10
0
def add_reaction_backend(request, user_profile, message_id, emoji_name):
    # type: (HttpRequest, UserProfile, int, Text) -> HttpResponse

    # access_message will throw a JsonableError exception if the user
    # cannot see the message (e.g. for messages to private streams).
    message = access_message(user_profile, message_id)[0]

    check_valid_emoji(message.sender.realm, emoji_name)

    # We could probably just make this check be a try/except for the
    # IntegrityError from it already existing, but this is a bit cleaner.
    if Reaction.objects.filter(user_profile=user_profile,
                               message=message,
                               emoji_name=emoji_name).exists():
        raise JsonableError(_("Reaction already exists"))

    do_add_reaction(user_profile, message, emoji_name)

    return json_success()
Esempio n. 11
0
def add_reaction_backend(request, user_profile, message_id, emoji_name):
    # type: (HttpRequest, UserProfile, int, text_type) -> HttpResponse

    # access_message will throw a JsonableError exception if the user
    # cannot see the message (e.g. for messages to private streams).
    message = access_message(user_profile, message_id)[0]

    existing_emojis = set(message.sender.realm.get_emoji().keys()) or set(emoji_list)
    if emoji_name not in existing_emojis:
        raise JsonableError(_("Emoji '%s' does not exist" % (emoji_name,)))

    # We could probably just make this check be a try/except for the
    # IntegrityError from it already existing, but this is a bit cleaner.
    if Reaction.objects.filter(user_profile=user_profile, message=message, emoji_name=emoji_name).exists():
        raise JsonableError(_("Reaction already exists"))

    do_add_reaction(user_profile, message, emoji_name)

    return json_success()
    def test_command_with_consented_message_id(self) -> None:
        realm = get_realm("zulip")
        self.send_stream_message(self.example_email("othello"), "Verona",
                                 topic_name="Export",
                                 content="Thumbs up for export")
        message = Message.objects.last()
        do_add_reaction(self.example_user("iago"), message, "outbox", "1f4e4",  Reaction.UNICODE_EMOJI)
        do_add_reaction(self.example_user("hamlet"), message, "outbox", "1f4e4",  Reaction.UNICODE_EMOJI)

        with patch("zerver.management.commands.export.export_realm_wrapper") as m:
            call_command(self.COMMAND_NAME, "-r=zulip", "--consent-message-id={}".format(message.id))
            m.assert_called_once_with(realm=realm, public_only=False, consent_message_id=message.id,
                                      delete_after_upload=False, threads=mock.ANY, output_dir=mock.ANY,
                                      upload=False)

        with self.assertRaisesRegex(CommandError, "Message with given ID does not"):
            call_command(self.COMMAND_NAME, "-r=zulip", "--consent-message-id=123456")

        message.last_edit_time = timezone_now()
        message.save()
        with self.assertRaisesRegex(CommandError, "Message was edited. Aborting..."):
            call_command(self.COMMAND_NAME, "-r=zulip", "--consent-message-id={}".format(message.id))

        message.last_edit_time = None
        message.save()
        do_add_reaction(self.mit_user("sipbtest"), message, "outbox", "1f4e4",  Reaction.UNICODE_EMOJI)
        with self.assertRaisesRegex(CommandError, "Users from a different realm reacted to message. Aborting..."):
            call_command(self.COMMAND_NAME, "-r=zulip", "--consent-message-id={}".format(message.id))
    def test_command_with_consented_message_id(self) -> None:
        realm = get_realm("zulip")
        self.send_stream_message(self.example_user("othello"),
                                 "Verona",
                                 topic_name="Export",
                                 content="Thumbs up for export")
        message = Message.objects.last()
        do_add_reaction(self.example_user("iago"), message, "outbox", "1f4e4",
                        Reaction.UNICODE_EMOJI)
        do_add_reaction(self.example_user("hamlet"), message, "outbox",
                        "1f4e4", Reaction.UNICODE_EMOJI)

        with patch("zerver.management.commands.export.export_realm_wrapper") as m, \
                patch('builtins.print') as mock_print:
            call_command(self.COMMAND_NAME, "-r=zulip",
                         f"--consent-message-id={message.id}")
            m.assert_called_once_with(realm=realm,
                                      public_only=False,
                                      consent_message_id=message.id,
                                      delete_after_upload=False,
                                      threads=mock.ANY,
                                      output_dir=mock.ANY,
                                      upload=False)

        self.assertEqual(mock_print.mock_calls, [
            call('\033[94mExporting realm\033[0m: zulip'),
            call('\n\033[94mMessage content:\033[0m\nThumbs up for export\n'),
            call('\033[94mNumber of users that reacted outbox:\033[0m 2\n')
        ])

        with self.assertRaisesRegex(CommandError, "Message with given ID does not"), \
                patch('builtins.print') as mock_print:
            call_command(self.COMMAND_NAME, "-r=zulip",
                         "--consent-message-id=123456")
        self.assertEqual(mock_print.mock_calls, [
            call('\033[94mExporting realm\033[0m: zulip'),
        ])

        message.last_edit_time = timezone_now()
        message.save()
        with self.assertRaisesRegex(CommandError, "Message was edited. Aborting..."), \
                patch('builtins.print') as mock_print:
            call_command(self.COMMAND_NAME, "-r=zulip",
                         f"--consent-message-id={message.id}")
        self.assertEqual(mock_print.mock_calls, [
            call('\033[94mExporting realm\033[0m: zulip'),
        ])

        message.last_edit_time = None
        message.save()
        do_add_reaction(self.mit_user("sipbtest"), message, "outbox", "1f4e4",
                        Reaction.UNICODE_EMOJI)
        with self.assertRaisesRegex(CommandError, "Users from a different realm reacted to message. Aborting..."), \
                patch('builtins.print') as mock_print:
            call_command(self.COMMAND_NAME, "-r=zulip",
                         f"--consent-message-id={message.id}")

        self.assertEqual(mock_print.mock_calls, [
            call('\033[94mExporting realm\033[0m: zulip'),
        ])
Esempio n. 14
0
def send_initial_realm_messages(realm):
    # type: (Realm) -> None
    welcome_bot = get_system_bot(settings.WELCOME_BOT)
    # Make sure each stream created in the realm creation process has at least one message below
    # Order corresponds to the ordering of the streams on the left sidebar, to make the initial Home
    # view slightly less overwhelming
    welcome_messages = [
        {
            'stream':
            Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
            'topic':
            "welcome",
            'content':
            "This is a message on stream `%s` with the topic `welcome`. We'll use this stream "
            "for system-generated notifications." %
            (Realm.DEFAULT_NOTIFICATION_STREAM_NAME, )
        },
        {
            'stream':
            "core team",
            'topic':
            "private streams",
            'content':
            "This is a private stream. Only admins and people you invite "
            "to the stream will be able to see that this stream exists."
        },
        {
            'stream': "general",
            'topic': "welcome",
            'content': "Welcome to #**general**."
        },
        {
            'stream':
            "new members",
            'topic':
            "onboarding",
            'content':
            "A #**new members** stream is great for onboarding new members.\n\nIf you're "
            "reading this and aren't the first person here, introduce yourself in a new thread "
            "using your name as the topic! Type `c` or click on `New Topic` at the bottom of the "
            "screen to start a new topic."
        },
        {
            'stream':
            "zulip",
            'topic':
            "topic demonstration",
            'content':
            "Here is a message in one topic. Replies to this message will go to this topic."
        },
        {
            'stream':
            "zulip",
            'topic':
            "topic demonstration",
            'content':
            "A second message in this topic. With [turtles](/static/images/cute/turtle.png)!"
        },
        {
            'stream':
            "zulip",
            'topic':
            "second topic",
            'content':
            "This is a message in a second topic.\n\nTopics are similar to email subjects, "
            "in that each conversation should get its own topic. Keep them short, though; one "
            "or two words will do it!"
        },
    ]  # type: List[Dict[str, Text]]
    messages = [
        internal_prep_stream_message(realm, welcome_bot, message['stream'],
                                     message['topic'], message['content'])
        for message in welcome_messages
    ]
    message_ids = do_send_messages(messages)

    # We find the one of our just-sent messages with turtle.png in it,
    # and react to it.  This is a bit hacky, but works and is kinda a
    # 1-off thing.
    turtle_message = Message.objects.get(id__in=message_ids,
                                         subject='topic demonstration',
                                         content__icontains='cute/turtle.png')
    do_add_reaction(welcome_bot, turtle_message, 'turtle')
Esempio n. 15
0
    def test_export_realm_with_member_consent(self) -> None:
        realm = Realm.objects.get(string_id='zulip')

        # Create private streams and subscribe users for testing export
        create_stream_if_needed(realm, "Private A", invite_only=True)
        self.subscribe(self.example_user("iago"), "Private A")
        self.subscribe(self.example_user("othello"), "Private A")
        self.send_stream_message(self.example_email("iago"), "Private A",
                                 "Hello Stream A")

        create_stream_if_needed(realm, "Private B", invite_only=True)
        self.subscribe(self.example_user("prospero"), "Private B")
        stream_b_message_id = self.send_stream_message(
            self.example_email("prospero"), "Private B", "Hello Stream B")
        self.subscribe(self.example_user("hamlet"), "Private B")

        create_stream_if_needed(realm, "Private C", invite_only=True)
        self.subscribe(self.example_user("othello"), "Private C")
        self.subscribe(self.example_user("prospero"), "Private C")
        stream_c_message_id = self.send_stream_message(
            self.example_email("othello"), "Private C", "Hello Stream C")

        # Create huddles
        self.send_huddle_message(
            self.example_email("iago"),
            [self.example_email("cordelia"),
             self.example_email("AARON")])
        huddle_a = Huddle.objects.last()
        self.send_huddle_message(self.example_email("ZOE"), [
            self.example_email("hamlet"),
            self.example_email("AARON"),
            self.example_email("othello")
        ])
        huddle_b = Huddle.objects.last()

        huddle_c_message_id = self.send_huddle_message(
            self.example_email("AARON"), [
                self.example_email("cordelia"),
                self.example_email("ZOE"),
                self.example_email("othello")
            ])

        # Create PMs
        pm_a_msg_id = self.send_personal_message(self.example_email("AARON"),
                                                 self.example_email("othello"))
        pm_b_msg_id = self.send_personal_message(
            self.example_email("cordelia"), self.example_email("iago"))
        pm_c_msg_id = self.send_personal_message(self.example_email("hamlet"),
                                                 self.example_email("othello"))
        pm_d_msg_id = self.send_personal_message(self.example_email("iago"),
                                                 self.example_email("hamlet"))

        # Send message advertising export and make users react
        self.send_stream_message(self.example_email("othello"),
                                 "Verona",
                                 topic_name="Export",
                                 content="Thumbs up for export")
        message = Message.objects.last()
        consented_user_ids = [
            self.example_user(user).id for user in ["iago", "hamlet"]
        ]
        do_add_reaction(self.example_user("iago"), message, "+1", "1f44d",
                        Reaction.UNICODE_EMOJI)
        do_add_reaction(self.example_user("hamlet"), message, "+1", "1f44d",
                        Reaction.UNICODE_EMOJI)

        realm_emoji = RealmEmoji.objects.get(realm=realm)
        realm_emoji.delete()
        full_data = self._export_realm(realm, consent_message_id=message.id)
        realm_emoji.save()

        data = full_data['realm']

        self.assertEqual(len(data['zerver_userprofile_crossrealm']), 0)
        self.assertEqual(len(data['zerver_userprofile_mirrordummy']), 0)

        exported_user_emails = self.get_set(data['zerver_userprofile'],
                                            'email')
        self.assertIn(self.example_email('cordelia'), exported_user_emails)
        self.assertIn(self.example_email('hamlet'), exported_user_emails)
        self.assertIn(self.example_email('iago'), exported_user_emails)
        self.assertIn(self.example_email('othello'), exported_user_emails)
        self.assertIn('*****@*****.**', exported_user_emails)
        self.assertIn('*****@*****.**', exported_user_emails)

        exported_streams = self.get_set(data['zerver_stream'], 'name')
        self.assertEqual(
            exported_streams,
            set([
                u'Denmark', u'Rome', u'Scotland', u'Venice', u'Verona',
                u'Private A', u'Private B', u'Private C'
            ]))

        data = full_data['message']
        exported_usermessages = UserMessage.objects.filter(user_profile__in=[
            self.example_user("iago"),
            self.example_user("hamlet")
        ])
        um = exported_usermessages[0]
        self.assertEqual(len(data["zerver_usermessage"]),
                         len(exported_usermessages))
        exported_um = self.find_by_id(data['zerver_usermessage'], um.id)
        self.assertEqual(exported_um['message'], um.message_id)
        self.assertEqual(exported_um['user_profile'], um.user_profile_id)

        exported_message = self.find_by_id(data['zerver_message'],
                                           um.message_id)
        self.assertEqual(exported_message['content'], um.message.content)

        public_stream_names = [
            'Denmark', 'Rome', 'Scotland', 'Venice', 'Verona'
        ]
        public_stream_ids = Stream.objects.filter(
            name__in=public_stream_names).values_list("id", flat=True)
        public_stream_recipients = Recipient.objects.filter(
            type_id__in=public_stream_ids, type=Recipient.STREAM)
        public_stream_message_ids = Message.objects.filter(
            recipient__in=public_stream_recipients).values_list("id",
                                                                flat=True)

        # Messages from Private Stream C are not exported since no member gave consent
        private_stream_ids = Stream.objects.filter(
            name__in=["Private A", "Private B"]).values_list("id", flat=True)
        private_stream_recipients = Recipient.objects.filter(
            type_id__in=private_stream_ids, type=Recipient.STREAM)
        private_stream_message_ids = Message.objects.filter(
            recipient__in=private_stream_recipients).values_list("id",
                                                                 flat=True)

        pm_recipients = Recipient.objects.filter(
            type_id__in=consented_user_ids, type=Recipient.PERSONAL)
        pm_query = Q(recipient__in=pm_recipients) | Q(
            sender__in=consented_user_ids)
        exported_pm_ids = Message.objects.filter(pm_query).values_list(
            "id", flat=True).values_list("id", flat=True)

        # Third huddle is not exported since none of the members gave consent
        huddle_recipients = Recipient.objects.filter(
            type_id__in=[huddle_a.id, huddle_b.id], type=Recipient.HUDDLE)
        pm_query = Q(recipient__in=huddle_recipients) | Q(
            sender__in=consented_user_ids)
        exported_huddle_ids = Message.objects.filter(pm_query).values_list(
            "id", flat=True).values_list("id", flat=True)

        exported_msg_ids = set(public_stream_message_ids) | set(private_stream_message_ids) \
            | set(exported_pm_ids) | set(exported_huddle_ids)
        self.assertEqual(self.get_set(data["zerver_message"], "id"),
                         exported_msg_ids)

        # TODO: This behavior is wrong and should be fixed. The message should not be exported
        # since it was sent before the only consented user iago joined the stream.
        self.assertIn(stream_b_message_id, exported_msg_ids)

        self.assertNotIn(stream_c_message_id, exported_msg_ids)
        self.assertNotIn(huddle_c_message_id, exported_msg_ids)

        self.assertNotIn(pm_a_msg_id, exported_msg_ids)
        self.assertIn(pm_b_msg_id, exported_msg_ids)
        self.assertIn(pm_c_msg_id, exported_msg_ids)
        self.assertIn(pm_d_msg_id, exported_msg_ids)
Esempio n. 16
0
    def add_message_formatting_conversation(self):
        # type: () -> None
        realm = get_realm('zulip')
        stream, _ = create_stream_if_needed(realm, 'zulip features')

        UserProfile.objects.filter(email__contains='stage').delete()
        starr = do_create_user('*****@*****.**', 'password', realm, 'Ada Starr', '')
        self.set_avatar(starr, 'static/images/features/starr.png')
        fisher = do_create_user('*****@*****.**', 'password', realm, 'Bel Fisher', '')
        self.set_avatar(fisher, 'static/images/features/fisher.png')
        twitter_bot = do_create_user('*****@*****.**', 'password', realm, 'Twitter Bot', '',
                                     bot_type=UserProfile.DEFAULT_BOT)
        self.set_avatar(twitter_bot, 'static/images/features/twitter.png')

        bulk_add_subscriptions([stream], list(UserProfile.objects.filter(realm=realm)))

        staged_messages = [
            {'sender': starr,
             'content': "Hey @**Bel Fisher**, check out Zulip's Markdown formatting! "
             "You can have:\n* bulleted lists\n  * with sub-bullets too\n"
             "* **bold**, *italic*, and ~~strikethrough~~ text\n"
             "* LaTeX for mathematical formulas, both inline -- $$O(n^2)$$ -- and displayed:\n"
             "```math\n\\int_a^b f(t)\, dt=F(b)-F(a)\n```"},
            {'sender': fisher,
             'content': "My favorite is the syntax highlighting for code blocks\n"
             "```python\ndef fib(n):\n    # returns the n-th Fibonacci number\n"
             "    return fib(n-1) + fib(n-2)\n```"},
            {'sender': starr,
             'content': "I think you forgot your base case there, Bel :laughing:\n"
             "```quote\n```python\ndef fib(n):\n    # returns the n-th Fibonacci number\n"
             "    return fib(n-1) + fib(n-2)\n```\n```"},
            {'sender': fisher,
             'content': "I'm also a big fan of inline link, tweet, video, and image previews. "
             "Check out this picture of Çet Whalin[](/static/images/features/whale.png)!"},
            {'sender': starr,
             'content': "I just set up a custom linkifier, so `#1234` becomes [#1234](github.com/zulip/zulip/1234), "
             "a link to the corresponding GitHub issue."},
            {'sender': twitter_bot,
             'content': 'https://twitter.com/gvanrossum/status/786661035637772288'},
            {'sender': fisher,
             'content': "Oops, the Twitter bot I set up shouldn't be posting here. Let me go fix that."},
        ]  # type: List[Dict[str, Any]]

        messages = [internal_prep_stream_message(
            realm, message['sender'], stream.name, 'message formatting', message['content']
        ) for message in staged_messages]

        message_ids = do_send_messages(messages)

        preview_message = Message.objects.get(id__in=message_ids, content__icontains='image previews')
        do_add_reaction(starr, preview_message, 'whale')

        twitter_message = Message.objects.get(id__in=message_ids, content__icontains='gvanrossum')
        # Setting up a twitter integration in dev is a decent amount of work. If you need
        # to update this tweet, either copy the format below, or send the link to the tweet
        # to chat.zulip.org and ask an admin of that server to get you the rendered_content.
        twitter_message.rendered_content = (
            '<p><a>https://twitter.com/gvanrossum/status/786661035637772288</a></p>\n'
            '<div class="inline-preview-twitter"><div class="twitter-tweet">'
            '<a><img class="twitter-avatar" '
            'src="https://pbs.twimg.com/profile_images/424495004/GuidoAvatar_bigger.jpg"></a>'
            '<p>Great blog post about Zulip\'s use of mypy: '
            '<a>http://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/</a></p>'
            '<span>- Guido van Rossum (@gvanrossum)</span></div></div>')
        twitter_message.save(update_fields=['rendered_content'])

        # Put a short pause between the whale reaction and this, so that the
        # thumbs_up shows up second
        do_add_reaction(starr, preview_message, 'thumbs_up')
Esempio n. 17
0
def send_initial_realm_messages(realm: Realm) -> None:
    welcome_bot = get_system_bot(settings.WELCOME_BOT, realm.id)
    # Make sure each stream created in the realm creation process has at least one message below
    # Order corresponds to the ordering of the streams on the left sidebar, to make the initial Home
    # view slightly less overwhelming
    content_of_private_streams_topic = (
        _("This is a private stream, as indicated by the lock icon next to the stream name."
          ) + " " + _("Private streams are only visible to stream members.") +
        "\n"
        "\n" +
        _("To manage this stream, go to [Stream settings]({stream_settings_url}) "
          "and click on `{initial_private_stream_name}`.")).format(
              stream_settings_url="#streams/subscribed",
              initial_private_stream_name=Realm.INITIAL_PRIVATE_STREAM_NAME,
          )

    content1_of_topic_demonstration_topic = (_(
        "This is a message on stream #**{default_notification_stream_name}** with the "
        "topic `topic demonstration`.")).format(
            default_notification_stream_name=Realm.
            DEFAULT_NOTIFICATION_STREAM_NAME)

    content2_of_topic_demonstration_topic = (_(
        "Topics are a lightweight tool to keep conversations organized."
    ) + " " + _(
        "You can learn more about topics at [Streams and topics]({about_topics_help_url})."
    )).format(about_topics_help_url="/help/about-streams-and-topics")

    content_of_swimming_turtles_topic = (
        _("This is a message on stream #**{default_notification_stream_name}** with the "
          "topic `swimming turtles`.") + "\n"
        "\n"
        "[](/static/images/cute/turtle.png)"
        "\n"
        "\n" +
        _("[Start a new topic]({start_topic_help_url}) any time you're not replying to a \
        previous message.")).format(
            default_notification_stream_name=Realm.
            DEFAULT_NOTIFICATION_STREAM_NAME,
            start_topic_help_url="/help/start-a-new-topic",
        )

    welcome_messages: List[Dict[str, str]] = [
        {
            "stream": Realm.INITIAL_PRIVATE_STREAM_NAME,
            "topic": "private streams",
            "content": content_of_private_streams_topic,
        },
        {
            "stream": Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
            "topic": "topic demonstration",
            "content": content1_of_topic_demonstration_topic,
        },
        {
            "stream": Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
            "topic": "topic demonstration",
            "content": content2_of_topic_demonstration_topic,
        },
        {
            "stream": realm.DEFAULT_NOTIFICATION_STREAM_NAME,
            "topic": "swimming turtles",
            "content": content_of_swimming_turtles_topic,
        },
    ]

    messages = [
        internal_prep_stream_message_by_name(
            realm,
            welcome_bot,
            message["stream"],
            message["topic"],
            message["content"],
        ) for message in welcome_messages
    ]
    message_ids = do_send_messages(messages)

    # We find the one of our just-sent messages with turtle.png in it,
    # and react to it.  This is a bit hacky, but works and is kinda a
    # 1-off thing.
    turtle_message = Message.objects.select_for_update().get(
        id__in=message_ids, content__icontains="cute/turtle.png")
    (emoji_code, reaction_type) = emoji_name_to_emoji_code(realm, "turtle")
    do_add_reaction(welcome_bot, turtle_message, "turtle", emoji_code,
                    reaction_type)
    def test_command_with_consented_message_id(self) -> None:
        realm = get_realm("zulip")
        self.send_stream_message(
            self.example_user("othello"),
            "Verona",
            topic_name="Export",
            content="Outbox emoji for export",
        )
        message = Message.objects.last()
        assert message is not None
        do_add_reaction(self.example_user("iago"), message, "outbox", "1f4e4",
                        Reaction.UNICODE_EMOJI)
        do_add_reaction(self.example_user("hamlet"), message, "outbox",
                        "1f4e4", Reaction.UNICODE_EMOJI)

        with patch("zerver.management.commands.export.export_realm_wrapper"
                   ) as m, patch("builtins.print") as mock_print, patch(
                       "builtins.input", return_value="y") as mock_input:
            call_command(self.COMMAND_NAME, "-r=zulip",
                         f"--consent-message-id={message.id}")
            m.assert_called_once_with(
                realm=realm,
                public_only=False,
                consent_message_id=message.id,
                threads=mock.ANY,
                output_dir=mock.ANY,
                percent_callback=mock.ANY,
                upload=False,
            )
            mock_input.assert_called_once_with("Continue? [y/N] ")

        self.assertEqual(
            mock_print.mock_calls,
            [
                call("\033[94mExporting realm\033[0m: zulip"),
                call(
                    "\n\033[94mMessage content:\033[0m\nOutbox emoji for export\n"
                ),
                call(
                    "\033[94mNumber of users that reacted outbox:\033[0m 2 / 9 total non-guest users\n"
                ),
            ],
        )

        with self.assertRaisesRegex(CommandError,
                                    "Message with given ID does not"), patch(
                                        "builtins.print") as mock_print:
            call_command(self.COMMAND_NAME, "-r=zulip",
                         "--consent-message-id=123456")
        self.assertEqual(
            mock_print.mock_calls,
            [
                call("\033[94mExporting realm\033[0m: zulip"),
            ],
        )

        message.last_edit_time = timezone_now()
        message.save()
        with self.assertRaisesRegex(CommandError,
                                    "Message was edited. Aborting..."), patch(
                                        "builtins.print") as mock_print:
            call_command(self.COMMAND_NAME, "-r=zulip",
                         f"--consent-message-id={message.id}")
        self.assertEqual(
            mock_print.mock_calls,
            [
                call("\033[94mExporting realm\033[0m: zulip"),
            ],
        )

        message.last_edit_time = None
        message.save()
        do_add_reaction(self.mit_user("sipbtest"), message, "outbox", "1f4e4",
                        Reaction.UNICODE_EMOJI)
        with self.assertRaisesRegex(
                CommandError,
                "Users from a different realm reacted to message. Aborting..."
        ), patch("builtins.print") as mock_print:
            call_command(self.COMMAND_NAME, "-r=zulip",
                         f"--consent-message-id={message.id}")

        self.assertEqual(
            mock_print.mock_calls,
            [
                call("\033[94mExporting realm\033[0m: zulip"),
            ],
        )
    def add_message_formatting_conversation(self) -> None:
        realm = get_realm("zulip")
        stream = ensure_stream(realm, "zulip features", acting_user=None)

        UserProfile.objects.filter(email__contains="stage").delete()
        starr = do_create_user("*****@*****.**",
                               "password",
                               realm,
                               "Ada Starr",
                               acting_user=None)
        self.set_avatar(starr, "static/images/characters/starr.png")
        fisher = do_create_user("*****@*****.**",
                                "password",
                                realm,
                                "Bel Fisher",
                                acting_user=None)
        self.set_avatar(fisher, "static/images/characters/fisher.png")
        twitter_bot = do_create_user(
            "*****@*****.**",
            "password",
            realm,
            "Twitter Bot",
            bot_type=UserProfile.DEFAULT_BOT,
            acting_user=None,
        )
        self.set_avatar(twitter_bot, "static/images/features/twitter.png")

        bulk_add_subscriptions(realm, [stream],
                               list(UserProfile.objects.filter(realm=realm)))

        staged_messages: List[Dict[str, Any]] = [
            {
                "sender":
                starr,
                "content":
                "Hey @**Bel Fisher**, check out Zulip's Markdown formatting! "
                "You can have:\n* bulleted lists\n  * with sub-bullets too\n"
                "* **bold**, *italic*, and ~~strikethrough~~ text\n"
                "* LaTeX for mathematical formulas, both inline -- $$O(n^2)$$ -- and displayed:\n"
                "```math\n\\int_a^b f(t)\\, dt=F(b)-F(a)\n```",
            },
            {
                "sender":
                fisher,
                "content":
                "My favorite is the syntax highlighting for code blocks\n"
                "```python\ndef fib(n: int) -> int:\n    # returns the n-th Fibonacci number\n"
                "    return fib(n-1) + fib(n-2)\n```",
            },
            {
                "sender":
                starr,
                "content":
                "I think you forgot your base case there, Bel :laughing:\n"
                "```quote\n```python\ndef fib(n: int) -> int:\n    # returns the n-th Fibonacci number\n"
                "    return fib(n-1) + fib(n-2)\n```\n```",
            },
            {
                "sender":
                fisher,
                "content":
                "I'm also a big fan of inline link, tweet, video, and image previews. "
                "Check out this picture of Çet Whalin[](/static/images/features/whale.png)!",
            },
            {
                "sender":
                starr,
                "content":
                "I just set up a custom linkifier, "
                "so `#1234` becomes [#1234](github.com/zulip/zulip/1234), "
                "a link to the corresponding GitHub issue.",
            },
            {
                "sender":
                twitter_bot,
                "content":
                "https://twitter.com/gvanrossum/status/786661035637772288",
            },
            {
                "sender":
                fisher,
                "content":
                "Oops, the Twitter bot I set up shouldn't be posting here. Let me go fix that.",
            },
        ]

        messages = [
            internal_prep_stream_message(
                message["sender"],
                stream,
                "message formatting",
                message["content"],
            ) for message in staged_messages
        ]

        message_ids = do_send_messages(messages)

        preview_message = Message.objects.get(
            id__in=message_ids, content__icontains="image previews")
        (emoji_code, reaction_type) = emoji_name_to_emoji_code(realm, "whale")
        do_add_reaction(starr, preview_message, "whale", emoji_code,
                        reaction_type)

        twitter_message = Message.objects.get(id__in=message_ids,
                                              content__icontains="gvanrossum")
        # Setting up a twitter integration in dev is a decent amount of work. If you need
        # to update this tweet, either copy the format below, or send the link to the tweet
        # to chat.zulip.org and ask an admin of that server to get you the rendered_content.
        twitter_message.rendered_content = (
            "<p><a>https://twitter.com/gvanrossum/status/786661035637772288</a></p>\n"
            '<div class="inline-preview-twitter"><div class="twitter-tweet">'
            '<a><img class="twitter-avatar" '
            'src="https://pbs.twimg.com/profile_images/424495004/GuidoAvatar_bigger.jpg"></a>'
            "<p>Great blog post about Zulip's use of mypy: "
            "<a>http://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/</a></p>"
            "<span>- Guido van Rossum (@gvanrossum)</span></div></div>")
        twitter_message.save(update_fields=["rendered_content"])

        # Put a short pause between the whale reaction and this, so that the
        # thumbs_up shows up second
        (emoji_code,
         reaction_type) = emoji_name_to_emoji_code(realm, "thumbs_up")
        do_add_reaction(starr, preview_message, "thumbs_up", emoji_code,
                        reaction_type)