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_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()
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()
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 {}
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 {}
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 {}
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')
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)
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()
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'), ])
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')
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)
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')
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)