Exemplo n.º 1
0
def do_convert(md, realm_domain=None, message=None):
    """Convert Markdown to HTML, with Zulip-specific settings and hacks."""
    from zerver.models import get_active_user_dicts_in_realm, UserProfile

    if message:
        maybe_update_realm_filters(message.get_realm().domain)

    if realm_domain in md_engines:
        _md_engine = md_engines[realm_domain]
    else:
        _md_engine = md_engines["default"]
    # Reset the parser; otherwise it will get slower over time.
    _md_engine.reset()

    global current_message
    current_message = message

    # Pre-fetch data from the DB that is used in the bugdown thread
    global db_data
    if message:
        realm_users = get_active_user_dicts_in_realm(message.get_realm())

        db_data = {
            'realm_alert_words':
            alert_words.alert_words_in_realm(message.get_realm()),
            'full_names':
            dict((user['full_name'].lower(), user) for user in realm_users),
            'short_names':
            dict((user['short_name'].lower(), user) for user in realm_users),
            'emoji':
            message.get_realm().get_emoji()
        }

    try:
        # Spend at most 5 seconds rendering.
        # Sometimes Python-Markdown is really slow; see
        # https://trac.zulip.net/ticket/345
        return timeout(5, _md_engine.convert, md)
    except:
        from zerver.lib.actions import internal_send_message

        cleaned = _sanitize_for_log(md)

        # Output error to log as well as sending a zulip and email
        logging.getLogger('').error(
            'Exception in Markdown parser: %sInput (sanitized) was: %s' %
            (traceback.format_exc(), cleaned))
        subject = "Markdown parser failure on %s" % (platform.node(), )
        if settings.ERROR_BOT is not None:
            internal_send_message(
                settings.ERROR_BOT, "stream", "errors", subject,
                "Markdown parser failed, email sent with details.")
        mail.mail_admins(subject,
                         "Failed message: %s\n\n%s\n\n" %
                         (cleaned, traceback.format_exc()),
                         fail_silently=False)
        return None
    finally:
        current_message = None
        db_data = None
Exemplo n.º 2
0
def get_realm_user_dicts(user_profile):
    # type: (UserProfile) -> List[Dict[str, Text]]
    return [{'email': userdict['email'],
             'user_id': userdict['id'],
             'is_admin': userdict['is_realm_admin'],
             'is_bot': userdict['is_bot'],
             'full_name': userdict['full_name']}
            for userdict in get_active_user_dicts_in_realm(user_profile.realm)]
Exemplo n.º 3
0
def get_realm_user_dicts(user_profile):
    # type: (UserProfile) -> List[Dict[str, Text]]
    return [{'email': userdict['email'],
             'user_id': userdict['id'],
             'avatar_url': avatar_url_from_dict(userdict),
             'is_admin': userdict['is_realm_admin'],
             'is_bot': userdict['is_bot'],
             'full_name': userdict['full_name'],
             'timezone': userdict['timezone']}
            for userdict in get_active_user_dicts_in_realm(user_profile.realm)]
Exemplo n.º 4
0
def do_convert(md, realm_domain=None, message=None, possible_words=None):
    # type: (markdown.Markdown, Optional[text_type], Optional[Message], Optional[Set[text_type]]) -> Optional[text_type]
    """Convert Markdown to HTML, with Zulip-specific settings and hacks."""
    from zerver.models import get_active_user_dicts_in_realm, UserProfile

    if message:
        maybe_update_realm_filters(message.get_realm().domain)

    if realm_domain in md_engines:
        _md_engine = md_engines[realm_domain]
    else:
        _md_engine = md_engines["default"]
    # Reset the parser; otherwise it will get slower over time.
    _md_engine.reset()

    global current_message
    current_message = message

    # Pre-fetch data from the DB that is used in the bugdown thread
    global db_data
    if message:
        realm_users = get_active_user_dicts_in_realm(message.get_realm())

        if possible_words is None:
            possible_words = set() # Set[text_type]

        db_data = {'possible_words':    possible_words,
                   'full_names':        dict((user['full_name'].lower(), user) for user in realm_users),
                   'short_names':       dict((user['short_name'].lower(), user) for user in realm_users),
                   'emoji':             message.get_realm().get_emoji()}

    try:
        # Spend at most 5 seconds rendering.
        # Sometimes Python-Markdown is really slow; see
        # https://trac.zulip.net/ticket/345
        return timeout(5, _md_engine.convert, md)
    except:
        from zerver.lib.actions import internal_send_message

        cleaned = _sanitize_for_log(md)

        # Output error to log as well as sending a zulip and email
        log_bugdown_error('Exception in Markdown parser: %sInput (sanitized) was: %s'
            % (traceback.format_exc(), cleaned))
        subject = "Markdown parser failure on %s" % (platform.node(),)
        if settings.ERROR_BOT is not None:
            internal_send_message(settings.ERROR_BOT, "stream",
                    "errors", subject, "Markdown parser failed, email sent with details.")
        mail.mail_admins(subject, "Failed message: %s\n\n%s\n\n" % (
                                    cleaned, traceback.format_exc()),
                         fail_silently=False)
        raise BugdownRenderingException()
    finally:
        current_message = None
        db_data = None
Exemplo n.º 5
0
def get_realm_user_dicts(user_profile):
    # type: (UserProfile) -> List[Dict[str, Text]]
    def avatar_url(userdict):
        # type: (Dict[str, Any]) -> Text
        return get_avatar_url(userdict['avatar_source'],
                              userdict['email'],
                              userdict['avatar_version'],
                              )

    return [{'email': userdict['email'],
             'user_id': userdict['id'],
             'avatar_url': avatar_url(userdict),
             'is_admin': userdict['is_realm_admin'],
             'is_bot': userdict['is_bot'],
             'full_name': userdict['full_name']}
            for userdict in get_active_user_dicts_in_realm(user_profile.realm)]
Exemplo n.º 6
0
def get_realm_user_dicts(user_profile):
    # type: (UserProfile) -> List[Dict[str, Text]]
    def avatar_url(userdict):
        # type: (Dict[str, Any]) -> Text
        return get_avatar_url(userdict['avatar_source'],
                              userdict['email'],
                              userdict['avatar_version'],
                              )

    return [{'email': userdict['email'],
             'user_id': userdict['id'],
             'avatar_url': avatar_url(userdict),
             'is_admin': userdict['is_realm_admin'],
             'is_bot': userdict['is_bot'],
             'full_name': userdict['full_name']}
            for userdict in get_active_user_dicts_in_realm(user_profile.realm)]
Exemplo n.º 7
0
def add_subscriptions_backend(
    request,
    user_profile,
    streams_raw=REQ("subscriptions",
                    validator=check_list(check_dict([('name', check_string)
                                                     ]))),
    invite_only=REQ(validator=check_bool, default=False),
    announce=REQ(validator=check_bool, default=False),
    principals=REQ(validator=check_list(check_string), default=None),
    authorization_errors_fatal=REQ(validator=check_bool, default=True)):
    # type: (HttpRequest, UserProfile, Iterable[Mapping[str, text_type]], bool, bool, Optional[List[text_type]], bool) -> HttpResponse
    stream_names = []
    for stream_dict in streams_raw:
        stream_name = stream_dict["name"].strip()
        if len(stream_name) > Stream.MAX_NAME_LENGTH:
            return json_error(
                _("Stream name (%s) too long.") % (stream_name, ))
        if not valid_stream_name(stream_name):
            return json_error(_("Invalid stream name (%s).") % (stream_name, ))
        stream_names.append(stream_name)

    # Enforcement of can_create_streams policy is inside list_to_streams.
    existing_streams, created_streams = \
        list_to_streams(stream_names, user_profile, autocreate=True, invite_only=invite_only)
    authorized_streams, unauthorized_streams = \
        filter_stream_authorization(user_profile, existing_streams)
    if len(unauthorized_streams) > 0 and authorization_errors_fatal:
        return json_error(
            _("Unable to access stream (%s).") % unauthorized_streams[0].name)
    # Newly created streams are also authorized for the creator
    streams = authorized_streams + created_streams

    if principals is not None:
        if user_profile.realm.is_zephyr_mirror_realm and not all(
                stream.invite_only for stream in streams):
            return json_error(
                _("You can only invite other Zephyr mirroring users to invite-only streams."
                  ))
        subscribers = set(
            principal_to_user_profile(user_profile, principal)
            for principal in principals)
    else:
        subscribers = set([user_profile])

    (subscribed,
     already_subscribed) = bulk_add_subscriptions(streams, subscribers)

    result = dict(subscribed=defaultdict(list),
                  already_subscribed=defaultdict(list))  # type: Dict[str, Any]
    for (subscriber, stream) in subscribed:
        result["subscribed"][subscriber.email].append(stream.name)
    for (subscriber, stream) in already_subscribed:
        result["already_subscribed"][subscriber.email].append(stream.name)

    private_streams = dict(
        (stream.name, stream.invite_only) for stream in streams)
    bots = dict(
        (subscriber.email, subscriber.is_bot) for subscriber in subscribers)

    # Inform the user if someone else subscribed them to stuff,
    # or if a new stream was created with the "announce" option.
    notifications = []
    if principals and result["subscribed"]:
        for email, subscriptions in six.iteritems(result["subscribed"]):
            if email == user_profile.email:
                # Don't send a Zulip if you invited yourself.
                continue
            if bots[email]:
                # Don't send invitation Zulips to bots
                continue

            if len(subscriptions) == 1:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the%s stream [%s](%s)." % (
                           user_profile.full_name,
                           " **invite-only**"
                           if private_streams[subscriptions[0]] else "",
                           subscriptions[0],
                           stream_link(subscriptions[0]),
                       ))
            else:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the following streams: \n\n" %
                       (user_profile.full_name, ))
                for stream in subscriptions:
                    msg += "* [%s](%s)%s\n" % (stream, stream_link(stream),
                                               " (**invite-only**)" if
                                               private_streams[stream] else "")

            if len([s for s in subscriptions if not private_streams[s]]) > 0:
                msg += "\nYou can see historical content on a non-invite-only stream by narrowing to it."
            notifications.append(
                internal_prep_message(settings.NOTIFICATION_BOT, "private",
                                      email, "", msg))

    if announce and len(created_streams) > 0:
        notifications_stream = user_profile.realm.notifications_stream
        if notifications_stream is not None:
            if len(created_streams) > 1:
                stream_msg = "the following streams: %s" % \
                              (", ".join('`%s`' % (s.name,) for s in created_streams),)
            else:
                stream_msg = "a new stream `%s`" % (created_streams[0].name)

            stream_buttons = ' '.join(
                stream_button(s.name) for s in created_streams)
            msg = ("%s just created %s. %s" %
                   (user_profile.full_name, stream_msg, stream_buttons))
            notifications.append(
                internal_prep_message(settings.NOTIFICATION_BOT,
                                      "stream",
                                      notifications_stream.name,
                                      "Streams",
                                      msg,
                                      realm=notifications_stream.realm))
        else:
            msg = ("Hi there!  %s just created a new stream '%s'. %s" %
                   (user_profile.full_name, created_streams[0].name,
                    stream_button(created_streams[0].name)))
            for realm_user_dict in get_active_user_dicts_in_realm(
                    user_profile.realm):
                # Don't announce to yourself or to people you explicitly added
                # (who will get the notification above instead).
                if realm_user_dict['email'] in principals or realm_user_dict[
                        'email'] == user_profile.email:
                    continue
                notifications.append(
                    internal_prep_message(settings.NOTIFICATION_BOT, "private",
                                          realm_user_dict['email'], "", msg))

    if len(notifications) > 0:
        do_send_messages(notifications)

    result["subscribed"] = dict(result["subscribed"])
    result["already_subscribed"] = dict(result["already_subscribed"])
    if not authorization_errors_fatal:
        result["unauthorized"] = [
            stream.name for stream in unauthorized_streams
        ]
    return json_success(result)
Exemplo n.º 8
0
def add_subscriptions_backend(request, user_profile,
                              streams_raw = REQ("subscriptions",
                              validator=check_list(check_dict([('name', check_string)]))),
                              invite_only = REQ(validator=check_bool, default=False),
                              announce = REQ(validator=check_bool, default=False),
                              principals = REQ(validator=check_list(check_string), default=None),
                              authorization_errors_fatal = REQ(validator=check_bool, default=True)):
    # type: (HttpRequest, UserProfile, List[Dict[str, str]], bool, bool, Optional[List[str]], bool) -> HttpResponse
    stream_names = []
    for stream_dict in streams_raw:
        stream_name = stream_dict["name"].strip()
        if len(stream_name) > Stream.MAX_NAME_LENGTH:
            return json_error(_("Stream name (%s) too long.") % (stream_name,))
        if not valid_stream_name(stream_name):
            return json_error(_("Invalid stream name (%s).") % (stream_name,))
        stream_names.append(stream_name)

    # Enforcement of can_create_streams policy is inside list_to_streams.
    existing_streams, created_streams = \
        list_to_streams(stream_names, user_profile, autocreate=True, invite_only=invite_only)
    authorized_streams, unauthorized_streams = \
        filter_stream_authorization(user_profile, existing_streams)
    if len(unauthorized_streams) > 0 and authorization_errors_fatal:
        return json_error(_("Unable to access stream (%s).") % unauthorized_streams[0].name)
    # Newly created streams are also authorized for the creator
    streams = authorized_streams + created_streams

    if principals is not None:
        if user_profile.realm.domain == 'mit.edu' and not all(stream.invite_only for stream in streams):
            return json_error(_("You can only invite other mit.edu users to invite-only streams."))
        subscribers = set(principal_to_user_profile(user_profile, principal) for principal in principals)
    else:
        subscribers = set([user_profile])

    (subscribed, already_subscribed) = bulk_add_subscriptions(streams, subscribers)

    result = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list)) # type: Dict[str, Any]
    for (subscriber, stream) in subscribed:
        result["subscribed"][subscriber.email].append(stream.name)
    for (subscriber, stream) in already_subscribed:
        result["already_subscribed"][subscriber.email].append(stream.name)

    private_streams = dict((stream.name, stream.invite_only) for stream in streams)
    bots = dict((subscriber.email, subscriber.is_bot) for subscriber in subscribers)

    # Inform the user if someone else subscribed them to stuff,
    # or if a new stream was created with the "announce" option.
    notifications = []
    if principals and result["subscribed"]:
        for email, subscriptions in six.iteritems(result["subscribed"]):
            if email == user_profile.email:
                # Don't send a Zulip if you invited yourself.
                continue
            if bots[email]:
                # Don't send invitation Zulips to bots
                continue

            if len(subscriptions) == 1:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the%s stream [%s](%s)."
                       % (user_profile.full_name,
                          " **invite-only**" if private_streams[subscriptions[0]] else "",
                          subscriptions[0],
                          stream_link(subscriptions[0]),
                        ))
            else:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the following streams: \n\n"
                       % (user_profile.full_name,))
                for stream in subscriptions:
                    msg += "* [%s](%s)%s\n" % (
                        stream,
                        stream_link(stream),
                        " (**invite-only**)" if private_streams[stream] else "")

            if len([s for s in subscriptions if not private_streams[s]]) > 0:
                msg += "\nYou can see historical content on a non-invite-only stream by narrowing to it."
            notifications.append(internal_prep_message(settings.NOTIFICATION_BOT,
                                                       "private", email, "", msg))

    if announce and len(created_streams) > 0:
        notifications_stream = user_profile.realm.notifications_stream
        if notifications_stream is not None:
            if len(created_streams) > 1:
                stream_msg = "the following streams: %s" % \
                              (", ".join('`%s`' % (s.name,) for s in created_streams),)
            else:
                stream_msg = "a new stream `%s`" % (created_streams[0].name)

            stream_buttons = ' '.join(stream_button(s.name) for s in created_streams)
            msg = ("%s just created %s. %s" % (user_profile.full_name,
                                                stream_msg, stream_buttons))
            notifications.append(internal_prep_message(settings.NOTIFICATION_BOT,
                                   "stream",
                                   notifications_stream.name, "Streams", msg,
                                   realm=notifications_stream.realm))
        else:
            msg = ("Hi there!  %s just created a new stream '%s'. %s"
                       % (user_profile.full_name, created_streams[0].name, stream_button(created_streams[0].name)))
            for realm_user_dict in get_active_user_dicts_in_realm(user_profile.realm):
                # Don't announce to yourself or to people you explicitly added
                # (who will get the notification above instead).
                if realm_user_dict['email'] in principals or realm_user_dict['email'] == user_profile.email:
                    continue
                notifications.append(internal_prep_message(settings.NOTIFICATION_BOT,
                                                           "private",
                                                           realm_user_dict['email'], "", msg))

    if len(notifications) > 0:
        do_send_messages(notifications)

    result["subscribed"] = dict(result["subscribed"])
    result["already_subscribed"] = dict(result["already_subscribed"])
    if not authorization_errors_fatal:
        result["unauthorized"] = [stream.name for stream in unauthorized_streams]
    return json_success(result)
Exemplo n.º 9
0
def add_subscriptions_backend(request, user_profile,
                              streams_raw = REQ("subscriptions",
                                                validator=check_list(check_dict([('name', check_string)]))),
                              invite_only = REQ(validator=check_bool, default=False),
                              announce = REQ(validator=check_bool, default=False),
                              principals = REQ(validator=check_list(check_string), default=[]),
                              authorization_errors_fatal = REQ(validator=check_bool, default=True)):
    # type: (HttpRequest, UserProfile, Iterable[Mapping[str, Text]], bool, bool, List[Text], bool) -> HttpResponse
    stream_dicts = []
    for stream_dict in streams_raw:
        stream_dict_copy = {} # type: Dict[str, Any]
        for field in stream_dict:
            stream_dict_copy[field] = stream_dict[field]
        # Strip the stream name here.
        stream_dict_copy['name'] = stream_dict_copy['name'].strip()
        stream_dict_copy["invite_only"] = invite_only
        stream_dicts.append(stream_dict_copy)

    # Validation of the streams arguments, including enforcement of
    # can_create_streams policy and check_stream_name policy is inside
    # list_to_streams.
    existing_streams, created_streams = \
        list_to_streams(stream_dicts, user_profile, autocreate=True)
    authorized_streams, unauthorized_streams = \
        filter_stream_authorization(user_profile, existing_streams)
    if len(unauthorized_streams) > 0 and authorization_errors_fatal:
        return json_error(_("Unable to access stream (%s).") % unauthorized_streams[0].name)
    # Newly created streams are also authorized for the creator
    streams = authorized_streams + created_streams

    if len(principals) > 0:
        if user_profile.realm.is_zephyr_mirror_realm and not all(stream.invite_only for stream in streams):
            return json_error(_("You can only invite other Zephyr mirroring users to invite-only streams."))
        subscribers = set(principal_to_user_profile(user_profile, principal) for principal in principals)
    else:
        subscribers = set([user_profile])

    (subscribed, already_subscribed) = bulk_add_subscriptions(streams, subscribers)

    result = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list)) # type: Dict[str, Any]
    for (subscriber, stream) in subscribed:
        result["subscribed"][subscriber.email].append(stream.name)
    for (subscriber, stream) in already_subscribed:
        result["already_subscribed"][subscriber.email].append(stream.name)

    private_streams = dict((stream.name, stream.invite_only) for stream in streams)
    bots = dict((subscriber.email, subscriber.is_bot) for subscriber in subscribers)

    # Inform the user if someone else subscribed them to stuff,
    # or if a new stream was created with the "announce" option.
    notifications = []
    if len(principals) > 0 and result["subscribed"]:
        for email, subscriptions in six.iteritems(result["subscribed"]):
            if email == user_profile.email:
                # Don't send a Zulip if you invited yourself.
                continue
            if bots[email]:
                # Don't send invitation Zulips to bots
                continue

            if len(subscriptions) == 1:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the%s stream #**%s**."
                       % (user_profile.full_name,
                          " **invite-only**" if private_streams[subscriptions[0]] else "",
                          subscriptions[0],
                          ))
            else:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the following streams: \n\n"
                       % (user_profile.full_name,))
                for stream in subscriptions:
                    msg += "* #**%s**%s\n" % (
                        stream,
                        " (**invite-only**)" if private_streams[stream] else "")

            if len([s for s in subscriptions if not private_streams[s]]) > 0:
                msg += "\nYou can see historical content on a non-invite-only stream by narrowing to it."
            notifications.append(internal_prep_message(
                user_profile.realm, settings.NOTIFICATION_BOT,
                "private", email, "", msg))

    if announce and len(created_streams) > 0:
        notifications_stream = user_profile.realm.notifications_stream  # type: Optional[Stream]
        if notifications_stream is not None:
            if len(created_streams) > 1:
                stream_msg = "the following streams: %s" % (", ".join('#**%s**' % s.name for s in created_streams))
            else:
                stream_msg = "a new stream #**%s**." % created_streams[0].name
            msg = ("%s just created %s" % (user_profile.full_name, stream_msg))
            notifications.append(
                internal_prep_message(user_profile.realm, settings.NOTIFICATION_BOT,
                                      "stream",
                                      notifications_stream.name, "Streams", msg))
        else:
            msg = ("Hi there!  %s just created a new stream #**%s**."
                   % (user_profile.full_name, created_streams[0].name))
            for realm_user_dict in get_active_user_dicts_in_realm(user_profile.realm):
                # Don't announce to yourself or to people you explicitly added
                # (who will get the notification above instead).
                if realm_user_dict['email'] in principals or realm_user_dict['email'] == user_profile.email:
                    continue
                notifications.append(internal_prep_message(
                    user_profile.realm, settings.NOTIFICATION_BOT,
                    "private",
                    realm_user_dict['email'], "", msg))

    if len(notifications) > 0:
        do_send_messages(notifications)

    result["subscribed"] = dict(result["subscribed"])
    result["already_subscribed"] = dict(result["already_subscribed"])
    if not authorization_errors_fatal:
        result["unauthorized"] = [stream.name for stream in unauthorized_streams]
    return json_success(result)
Exemplo n.º 10
0
def add_subscriptions_backend(
    request,
    user_profile,
    streams_raw=REQ("subscriptions",
                    validator=check_list(check_dict([('name', check_string)
                                                     ]))),
    invite_only=REQ(validator=check_bool, default=False),
    announce=REQ(validator=check_bool, default=False),
    principals=REQ(validator=check_list(check_string), default=[]),
    authorization_errors_fatal=REQ(validator=check_bool, default=True)):
    # type: (HttpRequest, UserProfile, Iterable[Mapping[str, Text]], bool, bool, List[Text], bool) -> HttpResponse
    stream_dicts = []
    for stream_dict in streams_raw:
        stream_dict_copy = {}  # type: Dict[str, Any]
        for field in stream_dict:
            stream_dict_copy[field] = stream_dict[field]
        # Strip the stream name here.
        stream_dict_copy['name'] = stream_dict_copy['name'].strip()
        stream_dict_copy["invite_only"] = invite_only
        stream_dicts.append(stream_dict_copy)

    # Validation of the streams arguments, including enforcement of
    # can_create_streams policy and check_stream_name policy is inside
    # list_to_streams.
    existing_streams, created_streams = \
        list_to_streams(stream_dicts, user_profile, autocreate=True)
    authorized_streams, unauthorized_streams = \
        filter_stream_authorization(user_profile, existing_streams)
    if len(unauthorized_streams) > 0 and authorization_errors_fatal:
        return json_error(
            _("Unable to access stream (%s).") % unauthorized_streams[0].name)
    # Newly created streams are also authorized for the creator
    streams = authorized_streams + created_streams

    if len(principals) > 0:
        if user_profile.realm.is_zephyr_mirror_realm and not all(
                stream.invite_only for stream in streams):
            return json_error(
                _("You can only invite other Zephyr mirroring users to invite-only streams."
                  ))
        subscribers = set(
            principal_to_user_profile(user_profile, principal)
            for principal in principals)
    else:
        subscribers = set([user_profile])

    (subscribed,
     already_subscribed) = bulk_add_subscriptions(streams, subscribers)

    result = dict(subscribed=defaultdict(list),
                  already_subscribed=defaultdict(list))  # type: Dict[str, Any]
    for (subscriber, stream) in subscribed:
        result["subscribed"][subscriber.email].append(stream.name)
    for (subscriber, stream) in already_subscribed:
        result["already_subscribed"][subscriber.email].append(stream.name)

    private_streams = dict(
        (stream.name, stream.invite_only) for stream in streams)
    bots = dict(
        (subscriber.email, subscriber.is_bot) for subscriber in subscribers)

    # Inform the user if someone else subscribed them to stuff,
    # or if a new stream was created with the "announce" option.
    notifications = []
    if len(principals) > 0 and result["subscribed"]:
        for email, subscriptions in six.iteritems(result["subscribed"]):
            if email == user_profile.email:
                # Don't send a Zulip if you invited yourself.
                continue
            if bots[email]:
                # Don't send invitation Zulips to bots
                continue

            if len(subscriptions) == 1:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the%s stream #**%s**." % (
                           user_profile.full_name,
                           " **invite-only**"
                           if private_streams[subscriptions[0]] else "",
                           subscriptions[0],
                       ))
            else:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the following streams: \n\n" %
                       (user_profile.full_name, ))
                for stream in subscriptions:
                    msg += "* #**%s**%s\n" % (stream, " (**invite-only**)" if
                                              private_streams[stream] else "")

            if len([s for s in subscriptions if not private_streams[s]]) > 0:
                msg += "\nYou can see historical content on a non-invite-only stream by narrowing to it."

            sender = get_user_profile_by_email(settings.NOTIFICATION_BOT)
            notifications.append(
                internal_prep_private_message(realm=user_profile.realm,
                                              sender=sender,
                                              recipient_email=email,
                                              content=msg))

    if announce and len(created_streams) > 0:
        notifications_stream = user_profile.realm.notifications_stream  # type: Optional[Stream]
        if notifications_stream is not None:
            if len(created_streams) > 1:
                stream_msg = "the following streams: %s" % (", ".join(
                    '#**%s**' % s.name for s in created_streams))
            else:
                stream_msg = "a new stream #**%s**." % created_streams[0].name
            msg = ("%s just created %s" % (user_profile.full_name, stream_msg))

            sender = get_user_profile_by_email(settings.NOTIFICATION_BOT)
            stream_name = notifications_stream.name
            topic = 'Streams'

            notifications.append(
                internal_prep_stream_message(realm=user_profile.realm,
                                             sender=sender,
                                             stream_name=stream_name,
                                             topic=topic,
                                             content=msg))

        else:
            msg = ("Hi there!  %s just created a new stream #**%s**." %
                   (user_profile.full_name, created_streams[0].name))

            sender = get_user_profile_by_email(settings.NOTIFICATION_BOT)
            for realm_user_dict in get_active_user_dicts_in_realm(
                    user_profile.realm):
                # Don't announce to yourself or to people you explicitly added
                # (who will get the notification above instead).
                if realm_user_dict['email'] in principals or realm_user_dict[
                        'email'] == user_profile.email:
                    continue

                recipient_email = realm_user_dict['email']

                notifications.append(
                    internal_prep_private_message(
                        realm=user_profile.realm,
                        sender=sender,
                        recipient_email=recipient_email,
                        content=msg))

    if not user_profile.realm.is_zephyr_mirror_realm:
        for stream in created_streams:
            notifications.append(prep_stream_welcome_message(stream))

    if len(notifications) > 0:
        do_send_messages(notifications)

    result["subscribed"] = dict(result["subscribed"])
    result["already_subscribed"] = dict(result["already_subscribed"])
    if not authorization_errors_fatal:
        result["unauthorized"] = [
            stream.name for stream in unauthorized_streams
        ]
    return json_success(result)