Пример #1
0
    def test_emoji_name_to_emoji_code(self):
        # type: () -> None
        """
        An emoji name is mapped canonically to emoji code.
        """
        realm = get_realm('zulip')

        # Test active realm emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'green_tick')
        self.assertEqual(emoji_code, 'green_tick')
        self.assertEqual(reaction_type, 'realm_emoji')

        # Test deactivated realm emoji.
        emoji = RealmEmoji.objects.get(name="green_tick")
        emoji.deactivated = True
        emoji.save(update_fields=['deactivated'])
        with self.assertRaises(JsonableError) as exc:
            emoji_name_to_emoji_code(realm, 'green_tick')
        self.assertEqual(str(exc.exception), "Emoji 'green_tick' does not exist")

        # Test ':zulip:' emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'zulip')
        self.assertEqual(emoji_code, 'zulip')
        self.assertEqual(reaction_type, 'zulip_extra_emoji')

        # Test unicode emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'astonished')
        self.assertEqual(emoji_code, '1f632')
        self.assertEqual(reaction_type, 'unicode_emoji')

        # Test override unicode emoji.
        overriding_emoji = RealmEmoji.objects.create(
            name='astonished', realm=realm, file_name='astonished')
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'astonished')
        self.assertEqual(emoji_code, 'astonished')
        self.assertEqual(reaction_type, 'realm_emoji')

        # Test deactivate over-ridding realm emoji.
        overriding_emoji.deactivated = True
        overriding_emoji.save(update_fields=['deactivated'])
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'astonished')
        self.assertEqual(emoji_code, '1f632')
        self.assertEqual(reaction_type, 'unicode_emoji')

        # Test override `:zulip:` emoji.
        overriding_emoji = RealmEmoji.objects.create(
            name='zulip', realm=realm, file_name='zulip')
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'zulip')
        self.assertEqual(emoji_code, 'zulip')
        self.assertEqual(reaction_type, 'realm_emoji')

        # Test non-existent emoji.
        with self.assertRaises(JsonableError) as exc:
            emoji_name_to_emoji_code(realm, 'invalid_emoji')
        self.assertEqual(str(exc.exception), "Emoji 'invalid_emoji' does not exist")
Пример #2
0
    def test_emoji_name_to_emoji_code(self) -> None:
        """
        An emoji name is mapped canonically to emoji code.
        """
        realm = get_realm('zulip')

        # Test active realm emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'green_tick')
        self.assertEqual(emoji_code, 'green_tick')
        self.assertEqual(reaction_type, 'realm_emoji')

        # Test deactivated realm emoji.
        emoji = RealmEmoji.objects.get(name="green_tick")
        emoji.deactivated = True
        emoji.save(update_fields=['deactivated'])
        with self.assertRaises(JsonableError) as exc:
            emoji_name_to_emoji_code(realm, 'green_tick')
        self.assertEqual(str(exc.exception), "Emoji 'green_tick' does not exist")

        # Test ':zulip:' emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'zulip')
        self.assertEqual(emoji_code, 'zulip')
        self.assertEqual(reaction_type, 'zulip_extra_emoji')

        # Test unicode emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'astonished')
        self.assertEqual(emoji_code, '1f632')
        self.assertEqual(reaction_type, 'unicode_emoji')

        # Test override unicode emoji.
        overriding_emoji = RealmEmoji.objects.create(
            name='astonished', realm=realm, file_name='astonished')
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'astonished')
        self.assertEqual(emoji_code, 'astonished')
        self.assertEqual(reaction_type, 'realm_emoji')

        # Test deactivate over-ridding realm emoji.
        overriding_emoji.deactivated = True
        overriding_emoji.save(update_fields=['deactivated'])
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'astonished')
        self.assertEqual(emoji_code, '1f632')
        self.assertEqual(reaction_type, 'unicode_emoji')

        # Test override `:zulip:` emoji.
        overriding_emoji = RealmEmoji.objects.create(
            name='zulip', realm=realm, file_name='zulip')
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'zulip')
        self.assertEqual(emoji_code, 'zulip')
        self.assertEqual(reaction_type, 'realm_emoji')

        # Test non-existent emoji.
        with self.assertRaises(JsonableError) as exc:
            emoji_name_to_emoji_code(realm, 'invalid_emoji')
        self.assertEqual(str(exc.exception), "Emoji 'invalid_emoji' does not exist")
Пример #3
0
    def test_emoji_name_to_emoji_code(self) -> None:
        """
        An emoji name is mapped canonically to emoji code.
        """
        realm = get_realm("zulip")
        realm_emoji = RealmEmoji.objects.get(name="green_tick")

        # Test active realm emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "green_tick")
        self.assertEqual(emoji_code, str(realm_emoji.id))
        self.assertEqual(reaction_type, "realm_emoji")

        # Test deactivated realm emoji.
        realm_emoji.deactivated = True
        realm_emoji.save(update_fields=["deactivated"])
        with self.assertRaises(JsonableError) as exc:
            emoji_name_to_emoji_code(realm, "green_tick")
        self.assertEqual(str(exc.exception), "Emoji 'green_tick' does not exist")

        # Test ':zulip:' emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "zulip")
        self.assertEqual(emoji_code, "zulip")
        self.assertEqual(reaction_type, "zulip_extra_emoji")

        # Test Unicode emoji.
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "astonished")
        self.assertEqual(emoji_code, "1f632")
        self.assertEqual(reaction_type, "unicode_emoji")

        # Test override Unicode emoji.
        overriding_emoji = RealmEmoji.objects.create(
            name="astonished", realm=realm, file_name="astonished"
        )
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "astonished")
        self.assertEqual(emoji_code, str(overriding_emoji.id))
        self.assertEqual(reaction_type, "realm_emoji")

        # Test deactivate over-ridding realm emoji.
        overriding_emoji.deactivated = True
        overriding_emoji.save(update_fields=["deactivated"])
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "astonished")
        self.assertEqual(emoji_code, "1f632")
        self.assertEqual(reaction_type, "unicode_emoji")

        # Test override `:zulip:` emoji.
        overriding_emoji = RealmEmoji.objects.create(name="zulip", realm=realm, file_name="zulip")
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "zulip")
        self.assertEqual(emoji_code, str(overriding_emoji.id))
        self.assertEqual(reaction_type, "realm_emoji")

        # Test non-existent emoji.
        with self.assertRaises(JsonableError) as exc:
            emoji_name_to_emoji_code(realm, "invalid_emoji")
        self.assertEqual(str(exc.exception), "Emoji 'invalid_emoji' does not exist")
Пример #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()
Пример #5
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()
Пример #6
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)
Пример #7
0
def remove_reaction(
    request: HttpRequest,
    user_profile: UserProfile,
    message_id: int,
    emoji_name: Optional[str] = REQ(default=None),
    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:
        if emoji_name is None:
            raise JsonableError(
                _(
                    "At least one of the following arguments "
                    "must be present: emoji_name, emoji_code"
                )
            )
        # A correct full Zulip client implementation should always
        # pass an emoji_code, because of the corner cases discussed in
        # the long block comments elsewhere in this file.  However, to
        # make it easy for simple API clients to use the reactions API
        # without needing the mapping between emoji names and codes,
        # we allow instead passing the emoji_name and looking up the
        # corresponding code using the current data.
        emoji_code = emoji_name_to_emoji_code(message.sender.realm, emoji_name)[0]

    if not Reaction.objects.filter(
        user_profile=user_profile,
        message=message,
        emoji_code=emoji_code,
        reaction_type=reaction_type,
    ).exists():
        raise JsonableError(_("Reaction doesn't exist."))

    # Unlike adding reactions, while deleting a reaction, we don't
    # check whether the provided (emoji_type, emoji_code) pair is
    # valid in this realm.  Since there's a row in the database, we
    # know it was valid when the user added their reaction in the
    # first place, so it is safe to just remove the reaction if it
    # exists.  And the (reaction_type, emoji_code) pair may no longer be
    # valid in legitimate situations (e.g. if a realm emoji was
    # deactivated by an administrator in the meantime).
    do_remove_reaction(user_profile, message, emoji_code, reaction_type)

    return json_success()
Пример #8
0
    def test_remove_existing_reaction_with_renamed_emoji(self) -> None:
        """
        Removes an old existing reaction but the name of emoji got changed during
        various emoji infra changes.
        """
        realm = get_realm("zulip")
        sender = self.example_user("hamlet")
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "smile")
        reaction_info = {
            "emoji_name": "smile",
            "emoji_code": emoji_code,
            "reaction_type": reaction_type,
        }

        result = self.api_post(sender, "/api/v1/messages/1/reactions", reaction_info)
        self.assert_json_success(result)

        with mock.patch("zerver.lib.emoji.name_to_codepoint", name_to_codepoint={}):
            result = self.api_delete(sender, "/api/v1/messages/1/reactions", reaction_info)
            self.assert_json_success(result)
Пример #9
0
    def test_remove_existing_reaction_with_renamed_emoji(self) -> None:
        """
        Removes an old existing reaction but the name of emoji got changed during
        various emoji infra changes.
        """
        realm = get_realm('zulip')
        sender = self.example_user("hamlet")
        emoji_code, reaction_type = emoji_name_to_emoji_code(realm, 'smile')
        reaction_info = {
            'emoji_name': 'smile',
            'emoji_code': emoji_code,
            'reaction_type': reaction_type
        }

        result = self.api_post(sender, '/api/v1/messages/1/reactions', reaction_info)
        self.assert_json_success(result)

        with mock.patch('zerver.lib.emoji.name_to_codepoint', name_to_codepoint={}):
            result = self.api_delete(sender, '/api/v1/messages/1/reactions', reaction_info)
            self.assert_json_success(result)
Пример #10
0
def remove_reaction(request: HttpRequest, user_profile: UserProfile, message_id: int,
                    emoji_name: Optional[str]=REQ(default=None),
                    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:
        if emoji_name is None:
            raise JsonableError(_('At least one of the following arguments '
                                  'must be present: emoji_name, emoji_code'))
        # A correct full Zulip client implementation should always
        # pass an emoji_code, because of the corner cases discussed in
        # the long block comments elsewhere in this file.  However, to
        # make it easy for simple API clients to use the reactions API
        # without needing the mapping between emoji names and codes,
        # we allow instead passing the emoji_name and looking up the
        # corresponding code using the current data.
        emoji_code = emoji_name_to_emoji_code(message.sender.realm, emoji_name)[0]

    if not Reaction.objects.filter(user_profile=user_profile,
                                   message=message,
                                   emoji_code=emoji_code,
                                   reaction_type=reaction_type).exists():
        raise JsonableError(_("Reaction doesn't exist."))

    # Unlike adding reactions, while deleting a reaction, we don't
    # check whether the provided (emoji_type, emoji_code) pair is
    # valid in this realm.  Since there's a row in the database, we
    # know it was valid when the user added their reaction in the
    # first place, so it is safe to just remove the reaction if it
    # exists.  And the (reaction_type, emoji_code) pair may no longer be
    # valid in legitimate situations (e.g. if a realm emoji was
    # deactivated by an administrator in the meantime).
    do_remove_reaction(user_profile, message, emoji_code, reaction_type)

    return json_success()
Пример #11
0
def update_user_status_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    away: Optional[bool] = REQ(json_validator=check_bool, default=None),
    status_text: Optional[str] = REQ(str_validator=check_capped_string(60), default=None),
    emoji_name: Optional[str] = REQ(default=None),
    emoji_code: Optional[str] = REQ(default=None),
    # TODO: emoji_type is the more appropriate name for this parameter, but changing
    # that requires nontrivial work on the API documentation, since it's not clear
    # that the reactions endpoint would prefer such a change.
    emoji_type: Optional[str] = REQ("reaction_type", default=None),
) -> HttpResponse:

    if status_text is not None:
        status_text = status_text.strip()

    if (away is None) and (status_text is None) and (emoji_name is None):
        raise JsonableError(_("Client did not pass any new values."))

    if emoji_name == "":
        # Reset the emoji_code and reaction_type if emoji_name is empty.
        # This should clear the user's configured emoji.
        emoji_code = ""
        emoji_type = UserStatus.UNICODE_EMOJI

    elif emoji_name is not None:
        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(user_profile.realm, emoji_name)[0]

        if emoji_type is None:
            emoji_type = emoji_name_to_emoji_code(user_profile.realm, emoji_name)[1]

    elif emoji_type or emoji_code:
        raise JsonableError(
            _("Client must pass emoji_name if they pass either emoji_code or reaction_type.")
        )

    # If we're asking to set an emoji (not clear it ("") or not adjust
    # it (None)), we need to verify the emoji is valid.
    if emoji_name not in ["", None]:
        assert emoji_name is not None
        assert emoji_code is not None
        assert emoji_type is not None
        check_emoji_request(user_profile.realm, emoji_name, emoji_code, emoji_type)

    client = RequestNotes.get_notes(request).client
    assert client is not None
    do_update_user_status(
        user_profile=user_profile,
        away=away,
        status_text=status_text,
        client_id=client.id,
        emoji_name=emoji_name,
        emoji_code=emoji_code,
        reaction_type=emoji_type,
    )

    return json_success(request)
Пример #12
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)
Пример #13
0
    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)),
                               acting_user=None)

        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)