mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	zerver/lib: Change use of typing.Text to str.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							2f3b2fbf59
						
					
				
				
					commit
					1f9244e060
				
			@@ -1,5 +1,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from typing import Iterable, List, Optional, Sequence, Text
 | 
					from typing import Iterable, List, Optional, Sequence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.core.exceptions import ValidationError
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
@@ -11,7 +11,7 @@ from zerver.models import (
 | 
				
			|||||||
    get_user_including_cross_realm,
 | 
					    get_user_including_cross_realm,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_profiles_from_unvalidated_emails(emails: Iterable[Text], realm: Realm) -> List[UserProfile]:
 | 
					def user_profiles_from_unvalidated_emails(emails: Iterable[str], realm: Realm) -> List[UserProfile]:
 | 
				
			||||||
    user_profiles = []  # type: List[UserProfile]
 | 
					    user_profiles = []  # type: List[UserProfile]
 | 
				
			||||||
    for email in emails:
 | 
					    for email in emails:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -21,7 +21,7 @@ def user_profiles_from_unvalidated_emails(emails: Iterable[Text], realm: Realm)
 | 
				
			|||||||
        user_profiles.append(user_profile)
 | 
					        user_profiles.append(user_profile)
 | 
				
			||||||
    return user_profiles
 | 
					    return user_profiles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_user_profiles(emails: Iterable[Text], realm: Realm) -> List[UserProfile]:
 | 
					def get_user_profiles(emails: Iterable[str], realm: Realm) -> List[UserProfile]:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        return user_profiles_from_unvalidated_emails(emails, realm)
 | 
					        return user_profiles_from_unvalidated_emails(emails, realm)
 | 
				
			||||||
    except ValidationError as e:
 | 
					    except ValidationError as e:
 | 
				
			||||||
@@ -42,8 +42,8 @@ class Addressee:
 | 
				
			|||||||
    # This should be treated as an immutable class.
 | 
					    # This should be treated as an immutable class.
 | 
				
			||||||
    def __init__(self, msg_type: str,
 | 
					    def __init__(self, msg_type: str,
 | 
				
			||||||
                 user_profiles: Optional[Sequence[UserProfile]]=None,
 | 
					                 user_profiles: Optional[Sequence[UserProfile]]=None,
 | 
				
			||||||
                 stream_name: Optional[Text]=None,
 | 
					                 stream_name: Optional[str]=None,
 | 
				
			||||||
                 topic: Optional[Text]=None) -> None:
 | 
					                 topic: Optional[str]=None) -> None:
 | 
				
			||||||
        assert(msg_type in ['stream', 'private'])
 | 
					        assert(msg_type in ['stream', 'private'])
 | 
				
			||||||
        self._msg_type = msg_type
 | 
					        self._msg_type = msg_type
 | 
				
			||||||
        self._user_profiles = user_profiles
 | 
					        self._user_profiles = user_profiles
 | 
				
			||||||
@@ -63,21 +63,21 @@ class Addressee:
 | 
				
			|||||||
        assert(self.is_private())
 | 
					        assert(self.is_private())
 | 
				
			||||||
        return self._user_profiles  # type: ignore # assertion protects us
 | 
					        return self._user_profiles  # type: ignore # assertion protects us
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def stream_name(self) -> Text:
 | 
					    def stream_name(self) -> str:
 | 
				
			||||||
        assert(self.is_stream())
 | 
					        assert(self.is_stream())
 | 
				
			||||||
        assert(self._stream_name is not None)
 | 
					        assert(self._stream_name is not None)
 | 
				
			||||||
        return self._stream_name
 | 
					        return self._stream_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def topic(self) -> Text:
 | 
					    def topic(self) -> str:
 | 
				
			||||||
        assert(self.is_stream())
 | 
					        assert(self.is_stream())
 | 
				
			||||||
        assert(self._topic is not None)
 | 
					        assert(self._topic is not None)
 | 
				
			||||||
        return self._topic
 | 
					        return self._topic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def legacy_build(sender: UserProfile,
 | 
					    def legacy_build(sender: UserProfile,
 | 
				
			||||||
                     message_type_name: Text,
 | 
					                     message_type_name: str,
 | 
				
			||||||
                     message_to: Sequence[Text],
 | 
					                     message_to: Sequence[str],
 | 
				
			||||||
                     topic_name: Text,
 | 
					                     topic_name: str,
 | 
				
			||||||
                     realm: Optional[Realm]=None) -> 'Addressee':
 | 
					                     realm: Optional[Realm]=None) -> 'Addressee':
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # For legacy reason message_to used to be either a list of
 | 
					        # For legacy reason message_to used to be either a list of
 | 
				
			||||||
@@ -110,7 +110,7 @@ class Addressee:
 | 
				
			|||||||
            raise JsonableError(_("Invalid message type"))
 | 
					            raise JsonableError(_("Invalid message type"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def for_stream(stream_name: Text, topic: Text) -> 'Addressee':
 | 
					    def for_stream(stream_name: str, topic: str) -> 'Addressee':
 | 
				
			||||||
        if topic is None:
 | 
					        if topic is None:
 | 
				
			||||||
            raise JsonableError(_("Missing topic"))
 | 
					            raise JsonableError(_("Missing topic"))
 | 
				
			||||||
        topic = topic.strip()
 | 
					        topic = topic.strip()
 | 
				
			||||||
@@ -123,7 +123,7 @@ class Addressee:
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def for_private(emails: Sequence[Text], realm: Realm) -> 'Addressee':
 | 
					    def for_private(emails: Sequence[str], realm: Realm) -> 'Addressee':
 | 
				
			||||||
        user_profiles = get_user_profiles(emails, realm)
 | 
					        user_profiles = get_user_profiles(emails, realm)
 | 
				
			||||||
        return Addressee(
 | 
					        return Addressee(
 | 
				
			||||||
            msg_type='private',
 | 
					            msg_type='private',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,20 +3,20 @@ from django.db.models import Q
 | 
				
			|||||||
from zerver.models import UserProfile, Realm
 | 
					from zerver.models import UserProfile, Realm
 | 
				
			||||||
from zerver.lib.cache import cache_with_key, realm_alert_words_cache_key
 | 
					from zerver.lib.cache import cache_with_key, realm_alert_words_cache_key
 | 
				
			||||||
import ujson
 | 
					import ujson
 | 
				
			||||||
from typing import Dict, Iterable, List, Text
 | 
					from typing import Dict, Iterable, List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@cache_with_key(realm_alert_words_cache_key, timeout=3600*24)
 | 
					@cache_with_key(realm_alert_words_cache_key, timeout=3600*24)
 | 
				
			||||||
def alert_words_in_realm(realm: Realm) -> Dict[int, List[Text]]:
 | 
					def alert_words_in_realm(realm: Realm) -> Dict[int, List[str]]:
 | 
				
			||||||
    users_query = UserProfile.objects.filter(realm=realm, is_active=True)
 | 
					    users_query = UserProfile.objects.filter(realm=realm, is_active=True)
 | 
				
			||||||
    alert_word_data = users_query.filter(~Q(alert_words=ujson.dumps([]))).values('id', 'alert_words')
 | 
					    alert_word_data = users_query.filter(~Q(alert_words=ujson.dumps([]))).values('id', 'alert_words')
 | 
				
			||||||
    all_user_words = dict((elt['id'], ujson.loads(elt['alert_words'])) for elt in alert_word_data)
 | 
					    all_user_words = dict((elt['id'], ujson.loads(elt['alert_words'])) for elt in alert_word_data)
 | 
				
			||||||
    user_ids_with_words = dict((user_id, w) for (user_id, w) in all_user_words.items() if len(w))
 | 
					    user_ids_with_words = dict((user_id, w) for (user_id, w) in all_user_words.items() if len(w))
 | 
				
			||||||
    return user_ids_with_words
 | 
					    return user_ids_with_words
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_alert_words(user_profile: UserProfile) -> List[Text]:
 | 
					def user_alert_words(user_profile: UserProfile) -> List[str]:
 | 
				
			||||||
    return ujson.loads(user_profile.alert_words)
 | 
					    return ujson.loads(user_profile.alert_words)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def add_user_alert_words(user_profile: UserProfile, alert_words: Iterable[Text]) -> List[Text]:
 | 
					def add_user_alert_words(user_profile: UserProfile, alert_words: Iterable[str]) -> List[str]:
 | 
				
			||||||
    words = user_alert_words(user_profile)
 | 
					    words = user_alert_words(user_profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new_words = [w for w in alert_words if w not in words]
 | 
					    new_words = [w for w in alert_words if w not in words]
 | 
				
			||||||
@@ -26,7 +26,7 @@ def add_user_alert_words(user_profile: UserProfile, alert_words: Iterable[Text])
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return words
 | 
					    return words
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def remove_user_alert_words(user_profile: UserProfile, alert_words: Iterable[Text]) -> List[Text]:
 | 
					def remove_user_alert_words(user_profile: UserProfile, alert_words: Iterable[str]) -> List[str]:
 | 
				
			||||||
    words = user_alert_words(user_profile)
 | 
					    words = user_alert_words(user_profile)
 | 
				
			||||||
    words = [w for w in words if w not in alert_words]
 | 
					    words = [w for w in words if w not in alert_words]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,6 +34,6 @@ def remove_user_alert_words(user_profile: UserProfile, alert_words: Iterable[Tex
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return words
 | 
					    return words
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def set_user_alert_words(user_profile: UserProfile, alert_words: List[Text]) -> None:
 | 
					def set_user_alert_words(user_profile: UserProfile, alert_words: List[str]) -> None:
 | 
				
			||||||
    user_profile.alert_words = ujson.dumps(alert_words)
 | 
					    user_profile.alert_words = ujson.dumps(alert_words)
 | 
				
			||||||
    user_profile.save(update_fields=['alert_words'])
 | 
					    user_profile.save(update_fields=['alert_words'])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,14 +3,14 @@ from django.conf import settings
 | 
				
			|||||||
if False:
 | 
					if False:
 | 
				
			||||||
    from zerver.models import UserProfile
 | 
					    from zerver.models import UserProfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Dict, Optional, Text
 | 
					from typing import Any, Dict, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.avatar_hash import gravatar_hash, user_avatar_path_from_ids
 | 
					from zerver.lib.avatar_hash import gravatar_hash, user_avatar_path_from_ids
 | 
				
			||||||
from zerver.lib.upload import upload_backend, MEDIUM_AVATAR_SIZE
 | 
					from zerver.lib.upload import upload_backend, MEDIUM_AVATAR_SIZE
 | 
				
			||||||
from zerver.models import UserProfile
 | 
					from zerver.models import UserProfile
 | 
				
			||||||
import urllib
 | 
					import urllib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def avatar_url(user_profile: UserProfile, medium: bool=False, client_gravatar: bool=False) -> Text:
 | 
					def avatar_url(user_profile: UserProfile, medium: bool=False, client_gravatar: bool=False) -> str:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return get_avatar_field(
 | 
					    return get_avatar_field(
 | 
				
			||||||
        user_id=user_profile.id,
 | 
					        user_id=user_profile.id,
 | 
				
			||||||
@@ -22,7 +22,7 @@ def avatar_url(user_profile: UserProfile, medium: bool=False, client_gravatar: b
 | 
				
			|||||||
        client_gravatar=client_gravatar,
 | 
					        client_gravatar=client_gravatar,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def avatar_url_from_dict(userdict: Dict[str, Any], medium: bool=False) -> Text:
 | 
					def avatar_url_from_dict(userdict: Dict[str, Any], medium: bool=False) -> str:
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    DEPRECATED: We should start using
 | 
					    DEPRECATED: We should start using
 | 
				
			||||||
                get_avatar_field to populate users,
 | 
					                get_avatar_field to populate users,
 | 
				
			||||||
@@ -41,11 +41,11 @@ def avatar_url_from_dict(userdict: Dict[str, Any], medium: bool=False) -> Text:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def get_avatar_field(user_id: int,
 | 
					def get_avatar_field(user_id: int,
 | 
				
			||||||
                     realm_id: int,
 | 
					                     realm_id: int,
 | 
				
			||||||
                     email: Text,
 | 
					                     email: str,
 | 
				
			||||||
                     avatar_source: Text,
 | 
					                     avatar_source: str,
 | 
				
			||||||
                     avatar_version: int,
 | 
					                     avatar_version: int,
 | 
				
			||||||
                     medium: bool,
 | 
					                     medium: bool,
 | 
				
			||||||
                     client_gravatar: bool) -> Optional[Text]:
 | 
					                     client_gravatar: bool) -> Optional[str]:
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    Most of the parameters to this function map to fields
 | 
					    Most of the parameters to this function map to fields
 | 
				
			||||||
    by the same name in UserProfile (avatar_source, realm_id,
 | 
					    by the same name in UserProfile (avatar_source, realm_id,
 | 
				
			||||||
@@ -88,12 +88,12 @@ def get_avatar_field(user_id: int,
 | 
				
			|||||||
    url += '&version=%d' % (avatar_version,)
 | 
					    url += '&version=%d' % (avatar_version,)
 | 
				
			||||||
    return url
 | 
					    return url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_gravatar_url(email: Text, avatar_version: int, medium: bool=False) -> Text:
 | 
					def get_gravatar_url(email: str, avatar_version: int, medium: bool=False) -> str:
 | 
				
			||||||
    url = _get_unversioned_gravatar_url(email, medium)
 | 
					    url = _get_unversioned_gravatar_url(email, medium)
 | 
				
			||||||
    url += '&version=%d' % (avatar_version,)
 | 
					    url += '&version=%d' % (avatar_version,)
 | 
				
			||||||
    return url
 | 
					    return url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _get_unversioned_gravatar_url(email: Text, medium: bool) -> Text:
 | 
					def _get_unversioned_gravatar_url(email: str, medium: bool) -> str:
 | 
				
			||||||
    if settings.ENABLE_GRAVATAR:
 | 
					    if settings.ENABLE_GRAVATAR:
 | 
				
			||||||
        gravitar_query_suffix = "&s=%s" % (MEDIUM_AVATAR_SIZE,) if medium else ""
 | 
					        gravitar_query_suffix = "&s=%s" % (MEDIUM_AVATAR_SIZE,) if medium else ""
 | 
				
			||||||
        hash_key = gravatar_hash(email)
 | 
					        hash_key = gravatar_hash(email)
 | 
				
			||||||
@@ -101,17 +101,17 @@ def _get_unversioned_gravatar_url(email: Text, medium: bool) -> Text:
 | 
				
			|||||||
    return settings.DEFAULT_AVATAR_URI+'?x=x'
 | 
					    return settings.DEFAULT_AVATAR_URI+'?x=x'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _get_unversioned_avatar_url(user_profile_id: int,
 | 
					def _get_unversioned_avatar_url(user_profile_id: int,
 | 
				
			||||||
                                avatar_source: Text,
 | 
					                                avatar_source: str,
 | 
				
			||||||
                                realm_id: int,
 | 
					                                realm_id: int,
 | 
				
			||||||
                                email: Optional[Text]=None,
 | 
					                                email: Optional[str]=None,
 | 
				
			||||||
                                medium: bool=False) -> Text:
 | 
					                                medium: bool=False) -> str:
 | 
				
			||||||
    if avatar_source == 'U':
 | 
					    if avatar_source == 'U':
 | 
				
			||||||
        hash_key = user_avatar_path_from_ids(user_profile_id, realm_id)
 | 
					        hash_key = user_avatar_path_from_ids(user_profile_id, realm_id)
 | 
				
			||||||
        return upload_backend.get_avatar_url(hash_key, medium=medium)
 | 
					        return upload_backend.get_avatar_url(hash_key, medium=medium)
 | 
				
			||||||
    assert email is not None
 | 
					    assert email is not None
 | 
				
			||||||
    return _get_unversioned_gravatar_url(email, medium)
 | 
					    return _get_unversioned_gravatar_url(email, medium)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def absolute_avatar_url(user_profile: UserProfile) -> Text:
 | 
					def absolute_avatar_url(user_profile: UserProfile) -> str:
 | 
				
			||||||
    """Absolute URLs are used to simplify logic for applications that
 | 
					    """Absolute URLs are used to simplify logic for applications that
 | 
				
			||||||
    won't be served by browsers, such as rendering GCM notifications."""
 | 
					    won't be served by browsers, such as rendering GCM notifications."""
 | 
				
			||||||
    return urllib.parse.urljoin(user_profile.realm.uri, avatar_url(user_profile))
 | 
					    return urllib.parse.urljoin(user_profile.realm.uri, avatar_url(user_profile))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from typing import Text
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.utils import make_safe_digest
 | 
					from zerver.lib.utils import make_safe_digest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,7 +7,7 @@ from zerver.models import UserProfile
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def gravatar_hash(email: Text) -> Text:
 | 
					def gravatar_hash(email: str) -> str:
 | 
				
			||||||
    """Compute the Gravatar hash for an email address."""
 | 
					    """Compute the Gravatar hash for an email address."""
 | 
				
			||||||
    # Non-ASCII characters aren't permitted by the currently active e-mail
 | 
					    # Non-ASCII characters aren't permitted by the currently active e-mail
 | 
				
			||||||
    # RFCs. However, the IETF has published https://tools.ietf.org/html/rfc4952,
 | 
					    # RFCs. However, the IETF has published https://tools.ietf.org/html/rfc4952,
 | 
				
			||||||
@@ -17,7 +16,7 @@ def gravatar_hash(email: Text) -> Text:
 | 
				
			|||||||
    # not error out on it.
 | 
					    # not error out on it.
 | 
				
			||||||
    return make_safe_digest(email.lower(), hashlib.md5)
 | 
					    return make_safe_digest(email.lower(), hashlib.md5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_avatar_hash(uid: Text) -> Text:
 | 
					def user_avatar_hash(uid: str) -> str:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # WARNING: If this method is changed, you may need to do a migration
 | 
					    # WARNING: If this method is changed, you may need to do a migration
 | 
				
			||||||
    # similar to zerver/migrations/0060_move_avatars_to_be_uid_based.py .
 | 
					    # similar to zerver/migrations/0060_move_avatars_to_be_uid_based.py .
 | 
				
			||||||
@@ -28,12 +27,12 @@ def user_avatar_hash(uid: Text) -> Text:
 | 
				
			|||||||
    user_key = uid + settings.AVATAR_SALT
 | 
					    user_key = uid + settings.AVATAR_SALT
 | 
				
			||||||
    return make_safe_digest(user_key, hashlib.sha1)
 | 
					    return make_safe_digest(user_key, hashlib.sha1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_avatar_path(user_profile: UserProfile) -> Text:
 | 
					def user_avatar_path(user_profile: UserProfile) -> str:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # WARNING: If this method is changed, you may need to do a migration
 | 
					    # WARNING: If this method is changed, you may need to do a migration
 | 
				
			||||||
    # similar to zerver/migrations/0060_move_avatars_to_be_uid_based.py .
 | 
					    # similar to zerver/migrations/0060_move_avatars_to_be_uid_based.py .
 | 
				
			||||||
    return user_avatar_path_from_ids(user_profile.id, user_profile.realm_id)
 | 
					    return user_avatar_path_from_ids(user_profile.id, user_profile.realm_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_avatar_path_from_ids(user_profile_id: int, realm_id: int) -> Text:
 | 
					def user_avatar_path_from_ids(user_profile_id: int, realm_id: int) -> str:
 | 
				
			||||||
    user_id_hash = user_avatar_hash(str(user_profile_id))
 | 
					    user_id_hash = user_avatar_hash(str(user_profile_id))
 | 
				
			||||||
    return '%s/%s' % (str(realm_id), user_id_hash)
 | 
					    return '%s/%s' % (str(realm_id), user_id_hash)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ from django.db.models.query import F
 | 
				
			|||||||
from django.db.models.functions import Length
 | 
					from django.db.models.functions import Length
 | 
				
			||||||
from zerver.models import BotConfigData, UserProfile
 | 
					from zerver.models import BotConfigData, UserProfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Text, List, Dict, Optional
 | 
					from typing import List, Dict, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from collections import defaultdict
 | 
					from collections import defaultdict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,22 +16,22 @@ import importlib
 | 
				
			|||||||
class ConfigError(Exception):
 | 
					class ConfigError(Exception):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_bot_config(bot_profile: UserProfile) -> Dict[Text, Text]:
 | 
					def get_bot_config(bot_profile: UserProfile) -> Dict[str, str]:
 | 
				
			||||||
    entries = BotConfigData.objects.filter(bot_profile=bot_profile)
 | 
					    entries = BotConfigData.objects.filter(bot_profile=bot_profile)
 | 
				
			||||||
    if not entries:
 | 
					    if not entries:
 | 
				
			||||||
        raise ConfigError("No config data available.")
 | 
					        raise ConfigError("No config data available.")
 | 
				
			||||||
    return {entry.key: entry.value for entry in entries}
 | 
					    return {entry.key: entry.value for entry in entries}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_bot_configs(bot_profile_ids: List[int]) -> Dict[int, Dict[Text, Text]]:
 | 
					def get_bot_configs(bot_profile_ids: List[int]) -> Dict[int, Dict[str, str]]:
 | 
				
			||||||
    if not bot_profile_ids:
 | 
					    if not bot_profile_ids:
 | 
				
			||||||
        return {}
 | 
					        return {}
 | 
				
			||||||
    entries = BotConfigData.objects.filter(bot_profile_id__in=bot_profile_ids)
 | 
					    entries = BotConfigData.objects.filter(bot_profile_id__in=bot_profile_ids)
 | 
				
			||||||
    entries_by_uid = defaultdict(dict)  # type: Dict[int, Dict[Text, Text]]
 | 
					    entries_by_uid = defaultdict(dict)  # type: Dict[int, Dict[str, str]]
 | 
				
			||||||
    for entry in entries:
 | 
					    for entry in entries:
 | 
				
			||||||
        entries_by_uid[entry.bot_profile_id].update({entry.key: entry.value})
 | 
					        entries_by_uid[entry.bot_profile_id].update({entry.key: entry.value})
 | 
				
			||||||
    return entries_by_uid
 | 
					    return entries_by_uid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_bot_config_size(bot_profile: UserProfile, key: Optional[Text]=None) -> int:
 | 
					def get_bot_config_size(bot_profile: UserProfile, key: Optional[str]=None) -> int:
 | 
				
			||||||
    if key is None:
 | 
					    if key is None:
 | 
				
			||||||
        return BotConfigData.objects.filter(bot_profile=bot_profile) \
 | 
					        return BotConfigData.objects.filter(bot_profile=bot_profile) \
 | 
				
			||||||
                                    .annotate(key_size=Length('key'), value_size=Length('value')) \
 | 
					                                    .annotate(key_size=Length('key'), value_size=Length('value')) \
 | 
				
			||||||
@@ -42,7 +42,7 @@ def get_bot_config_size(bot_profile: UserProfile, key: Optional[Text]=None) -> i
 | 
				
			|||||||
        except BotConfigData.DoesNotExist:
 | 
					        except BotConfigData.DoesNotExist:
 | 
				
			||||||
            return 0
 | 
					            return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def set_bot_config(bot_profile: UserProfile, key: Text, value: Text) -> None:
 | 
					def set_bot_config(bot_profile: UserProfile, key: str, value: str) -> None:
 | 
				
			||||||
    config_size_limit = settings.BOT_CONFIG_SIZE_LIMIT
 | 
					    config_size_limit = settings.BOT_CONFIG_SIZE_LIMIT
 | 
				
			||||||
    old_entry_size = get_bot_config_size(bot_profile, key)
 | 
					    old_entry_size = get_bot_config_size(bot_profile, key)
 | 
				
			||||||
    new_entry_size = len(key) + len(value)
 | 
					    new_entry_size = len(key) + len(value)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ import configparser
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
if False:
 | 
					if False:
 | 
				
			||||||
    from mypy_extensions import NoReturn
 | 
					    from mypy_extensions import NoReturn
 | 
				
			||||||
from typing import Any, Optional, List, Dict, Text
 | 
					from typing import Any, Optional, List, Dict
 | 
				
			||||||
from types import ModuleType
 | 
					from types import ModuleType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
our_dir = os.path.dirname(os.path.abspath(__file__))
 | 
					our_dir = os.path.dirname(os.path.abspath(__file__))
 | 
				
			||||||
@@ -45,16 +45,16 @@ class StateHandler:
 | 
				
			|||||||
        self.marshal = lambda obj: json.dumps(obj)
 | 
					        self.marshal = lambda obj: json.dumps(obj)
 | 
				
			||||||
        self.demarshal = lambda obj: json.loads(obj)
 | 
					        self.demarshal = lambda obj: json.loads(obj)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, key: Text) -> Text:
 | 
					    def get(self, key: str) -> str:
 | 
				
			||||||
        return self.demarshal(get_bot_storage(self.user_profile, key))
 | 
					        return self.demarshal(get_bot_storage(self.user_profile, key))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def put(self, key: Text, value: Text) -> None:
 | 
					    def put(self, key: str, value: str) -> None:
 | 
				
			||||||
        set_bot_storage(self.user_profile, [(key, self.marshal(value))])
 | 
					        set_bot_storage(self.user_profile, [(key, self.marshal(value))])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def remove(self, key: Text) -> None:
 | 
					    def remove(self, key: str) -> None:
 | 
				
			||||||
        remove_bot_storage(self.user_profile, [key])
 | 
					        remove_bot_storage(self.user_profile, [key])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def contains(self, key: Text) -> bool:
 | 
					    def contains(self, key: str) -> bool:
 | 
				
			||||||
        return is_key_in_bot_storage(self.user_profile, key)
 | 
					        return is_key_in_bot_storage(self.user_profile, key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EmbeddedBotQuitException(Exception):
 | 
					class EmbeddedBotQuitException(Exception):
 | 
				
			||||||
@@ -109,7 +109,7 @@ class EmbeddedBotHandler:
 | 
				
			|||||||
            ))
 | 
					            ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # The bot_name argument exists only to comply with ExternalBotHandler.get_config_info().
 | 
					    # The bot_name argument exists only to comply with ExternalBotHandler.get_config_info().
 | 
				
			||||||
    def get_config_info(self, bot_name: str, optional: bool=False) -> Dict[Text, Text]:
 | 
					    def get_config_info(self, bot_name: str, optional: bool=False) -> Dict[str, str]:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            return get_bot_config(self.user_profile)
 | 
					            return get_bot_config(self.user_profile)
 | 
				
			||||||
        except ConfigError:
 | 
					        except ConfigError:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,18 +4,18 @@ from django.db.models.query import F
 | 
				
			|||||||
from django.db.models.functions import Length
 | 
					from django.db.models.functions import Length
 | 
				
			||||||
from zerver.models import BotStorageData, UserProfile, Length
 | 
					from zerver.models import BotStorageData, UserProfile, Length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Text, Optional, List, Tuple
 | 
					from typing import Optional, List, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StateError(Exception):
 | 
					class StateError(Exception):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_bot_storage(bot_profile: UserProfile, key: Text) -> Text:
 | 
					def get_bot_storage(bot_profile: UserProfile, key: str) -> str:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        return BotStorageData.objects.get(bot_profile=bot_profile, key=key).value
 | 
					        return BotStorageData.objects.get(bot_profile=bot_profile, key=key).value
 | 
				
			||||||
    except BotStorageData.DoesNotExist:
 | 
					    except BotStorageData.DoesNotExist:
 | 
				
			||||||
        raise StateError("Key does not exist.")
 | 
					        raise StateError("Key does not exist.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_bot_storage_size(bot_profile: UserProfile, key: Optional[Text]=None) -> int:
 | 
					def get_bot_storage_size(bot_profile: UserProfile, key: Optional[str]=None) -> int:
 | 
				
			||||||
    if key is None:
 | 
					    if key is None:
 | 
				
			||||||
        return BotStorageData.objects.filter(bot_profile=bot_profile) \
 | 
					        return BotStorageData.objects.filter(bot_profile=bot_profile) \
 | 
				
			||||||
                                     .annotate(key_size=Length('key'), value_size=Length('value')) \
 | 
					                                     .annotate(key_size=Length('key'), value_size=Length('value')) \
 | 
				
			||||||
@@ -44,14 +44,14 @@ def set_bot_storage(bot_profile: UserProfile, entries: List[Tuple[str, str]]) ->
 | 
				
			|||||||
            BotStorageData.objects.update_or_create(bot_profile=bot_profile, key=key,
 | 
					            BotStorageData.objects.update_or_create(bot_profile=bot_profile, key=key,
 | 
				
			||||||
                                                    defaults={'value': value})
 | 
					                                                    defaults={'value': value})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def remove_bot_storage(bot_profile: UserProfile, keys: List[Text]) -> None:
 | 
					def remove_bot_storage(bot_profile: UserProfile, keys: List[str]) -> None:
 | 
				
			||||||
    queryset = BotStorageData.objects.filter(bot_profile=bot_profile, key__in=keys)
 | 
					    queryset = BotStorageData.objects.filter(bot_profile=bot_profile, key__in=keys)
 | 
				
			||||||
    if len(queryset) < len(keys):
 | 
					    if len(queryset) < len(keys):
 | 
				
			||||||
        raise StateError("Key does not exist.")
 | 
					        raise StateError("Key does not exist.")
 | 
				
			||||||
    queryset.delete()
 | 
					    queryset.delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_key_in_bot_storage(bot_profile: UserProfile, key: Text) -> bool:
 | 
					def is_key_in_bot_storage(bot_profile: UserProfile, key: str) -> bool:
 | 
				
			||||||
    return BotStorageData.objects.filter(bot_profile=bot_profile, key=key).exists()
 | 
					    return BotStorageData.objects.filter(bot_profile=bot_profile, key=key).exists()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_keys_in_bot_storage(bot_profile: UserProfile) -> List[Text]:
 | 
					def get_keys_in_bot_storage(bot_profile: UserProfile) -> List[str]:
 | 
				
			||||||
    return list(BotStorageData.objects.filter(bot_profile=bot_profile).values_list('key', flat=True))
 | 
					    return list(BotStorageData.objects.filter(bot_profile=bot_profile).values_list('key', flat=True))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,7 +82,7 @@ import markdown
 | 
				
			|||||||
from django.utils.html import escape
 | 
					from django.utils.html import escape
 | 
				
			||||||
from markdown.extensions.codehilite import CodeHilite, CodeHiliteExtension
 | 
					from markdown.extensions.codehilite import CodeHilite, CodeHiliteExtension
 | 
				
			||||||
from zerver.lib.tex import render_tex
 | 
					from zerver.lib.tex import render_tex
 | 
				
			||||||
from typing import Any, Dict, Iterable, List, MutableSequence, Optional, Tuple, Union, Text
 | 
					from typing import Any, Dict, Iterable, List, MutableSequence, Optional, Tuple, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Global vars
 | 
					# Global vars
 | 
				
			||||||
FENCE_RE = re.compile("""
 | 
					FENCE_RE = re.compile("""
 | 
				
			||||||
@@ -132,13 +132,13 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
        self.checked_for_codehilite = False
 | 
					        self.checked_for_codehilite = False
 | 
				
			||||||
        self.codehilite_conf = {}  # type: Dict[str, List[Any]]
 | 
					        self.codehilite_conf = {}  # type: Dict[str, List[Any]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self, lines: Iterable[Text]) -> List[Text]:
 | 
					    def run(self, lines: Iterable[str]) -> List[str]:
 | 
				
			||||||
        """ Match and store Fenced Code Blocks in the HtmlStash. """
 | 
					        """ Match and store Fenced Code Blocks in the HtmlStash. """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        output = []  # type: List[Text]
 | 
					        output = []  # type: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class BaseHandler:
 | 
					        class BaseHandler:
 | 
				
			||||||
            def handle_line(self, line: Text) -> None:
 | 
					            def handle_line(self, line: str) -> None:
 | 
				
			||||||
                raise NotImplementedError()
 | 
					                raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def done(self) -> None:
 | 
					            def done(self) -> None:
 | 
				
			||||||
@@ -153,7 +153,7 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
        def pop() -> None:
 | 
					        def pop() -> None:
 | 
				
			||||||
            handlers.pop()
 | 
					            handlers.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def check_for_new_fence(output: MutableSequence[Text], line: Text) -> None:
 | 
					        def check_for_new_fence(output: MutableSequence[str], line: str) -> None:
 | 
				
			||||||
            m = FENCE_RE.match(line)
 | 
					            m = FENCE_RE.match(line)
 | 
				
			||||||
            if m:
 | 
					            if m:
 | 
				
			||||||
                fence = m.group('fence')
 | 
					                fence = m.group('fence')
 | 
				
			||||||
@@ -164,16 +164,16 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
                output.append(line)
 | 
					                output.append(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class OuterHandler(BaseHandler):
 | 
					        class OuterHandler(BaseHandler):
 | 
				
			||||||
            def __init__(self, output: MutableSequence[Text]) -> None:
 | 
					            def __init__(self, output: MutableSequence[str]) -> None:
 | 
				
			||||||
                self.output = output
 | 
					                self.output = output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def handle_line(self, line: Text) -> None:
 | 
					            def handle_line(self, line: str) -> None:
 | 
				
			||||||
                check_for_new_fence(self.output, line)
 | 
					                check_for_new_fence(self.output, line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def done(self) -> None:
 | 
					            def done(self) -> None:
 | 
				
			||||||
                pop()
 | 
					                pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def generic_handler(output: MutableSequence[Text], fence: Text, lang: Text) -> BaseHandler:
 | 
					        def generic_handler(output: MutableSequence[str], fence: str, lang: str) -> BaseHandler:
 | 
				
			||||||
            if lang in ('quote', 'quoted'):
 | 
					            if lang in ('quote', 'quoted'):
 | 
				
			||||||
                return QuoteHandler(output, fence)
 | 
					                return QuoteHandler(output, fence)
 | 
				
			||||||
            elif lang in ('math', 'tex', 'latex'):
 | 
					            elif lang in ('math', 'tex', 'latex'):
 | 
				
			||||||
@@ -182,13 +182,13 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
                return CodeHandler(output, fence, lang)
 | 
					                return CodeHandler(output, fence, lang)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class CodeHandler(BaseHandler):
 | 
					        class CodeHandler(BaseHandler):
 | 
				
			||||||
            def __init__(self, output: MutableSequence[Text], fence: Text, lang: Text) -> None:
 | 
					            def __init__(self, output: MutableSequence[str], fence: str, lang: str) -> None:
 | 
				
			||||||
                self.output = output
 | 
					                self.output = output
 | 
				
			||||||
                self.fence = fence
 | 
					                self.fence = fence
 | 
				
			||||||
                self.lang = lang
 | 
					                self.lang = lang
 | 
				
			||||||
                self.lines = []  # type: List[Text]
 | 
					                self.lines = []  # type: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def handle_line(self, line: Text) -> None:
 | 
					            def handle_line(self, line: str) -> None:
 | 
				
			||||||
                if line.rstrip() == self.fence:
 | 
					                if line.rstrip() == self.fence:
 | 
				
			||||||
                    self.done()
 | 
					                    self.done()
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
@@ -205,12 +205,12 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
                pop()
 | 
					                pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class QuoteHandler(BaseHandler):
 | 
					        class QuoteHandler(BaseHandler):
 | 
				
			||||||
            def __init__(self, output: MutableSequence[Text], fence: Text) -> None:
 | 
					            def __init__(self, output: MutableSequence[str], fence: str) -> None:
 | 
				
			||||||
                self.output = output
 | 
					                self.output = output
 | 
				
			||||||
                self.fence = fence
 | 
					                self.fence = fence
 | 
				
			||||||
                self.lines = []  # type: List[Text]
 | 
					                self.lines = []  # type: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def handle_line(self, line: Text) -> None:
 | 
					            def handle_line(self, line: str) -> None:
 | 
				
			||||||
                if line.rstrip() == self.fence:
 | 
					                if line.rstrip() == self.fence:
 | 
				
			||||||
                    self.done()
 | 
					                    self.done()
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
@@ -226,12 +226,12 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
                pop()
 | 
					                pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class TexHandler(BaseHandler):
 | 
					        class TexHandler(BaseHandler):
 | 
				
			||||||
            def __init__(self, output: MutableSequence[Text], fence: Text) -> None:
 | 
					            def __init__(self, output: MutableSequence[str], fence: str) -> None:
 | 
				
			||||||
                self.output = output
 | 
					                self.output = output
 | 
				
			||||||
                self.fence = fence
 | 
					                self.fence = fence
 | 
				
			||||||
                self.lines = []  # type: List[Text]
 | 
					                self.lines = []  # type: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def handle_line(self, line: Text) -> None:
 | 
					            def handle_line(self, line: str) -> None:
 | 
				
			||||||
                if line.rstrip() == self.fence:
 | 
					                if line.rstrip() == self.fence:
 | 
				
			||||||
                    self.done()
 | 
					                    self.done()
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
@@ -263,7 +263,7 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
            output.append('')
 | 
					            output.append('')
 | 
				
			||||||
        return output
 | 
					        return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def format_code(self, lang: Text, text: Text) -> Text:
 | 
					    def format_code(self, lang: str, text: str) -> str:
 | 
				
			||||||
        if lang:
 | 
					        if lang:
 | 
				
			||||||
            langclass = LANG_TAG % (lang,)
 | 
					            langclass = LANG_TAG % (lang,)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
@@ -296,7 +296,7 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return code
 | 
					        return code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def format_quote(self, text: Text) -> Text:
 | 
					    def format_quote(self, text: str) -> str:
 | 
				
			||||||
        paragraphs = text.split("\n\n")
 | 
					        paragraphs = text.split("\n\n")
 | 
				
			||||||
        quoted_paragraphs = []
 | 
					        quoted_paragraphs = []
 | 
				
			||||||
        for paragraph in paragraphs:
 | 
					        for paragraph in paragraphs:
 | 
				
			||||||
@@ -304,7 +304,7 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
            quoted_paragraphs.append("\n".join("> " + line for line in lines if line != ''))
 | 
					            quoted_paragraphs.append("\n".join("> " + line for line in lines if line != ''))
 | 
				
			||||||
        return "\n\n".join(quoted_paragraphs)
 | 
					        return "\n\n".join(quoted_paragraphs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def format_tex(self, text: Text) -> Text:
 | 
					    def format_tex(self, text: str) -> str:
 | 
				
			||||||
        paragraphs = text.split("\n\n")
 | 
					        paragraphs = text.split("\n\n")
 | 
				
			||||||
        tex_paragraphs = []
 | 
					        tex_paragraphs = []
 | 
				
			||||||
        for paragraph in paragraphs:
 | 
					        for paragraph in paragraphs:
 | 
				
			||||||
@@ -316,10 +316,10 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
 | 
				
			|||||||
                                      escape(paragraph) + '</span>')
 | 
					                                      escape(paragraph) + '</span>')
 | 
				
			||||||
        return "\n\n".join(tex_paragraphs)
 | 
					        return "\n\n".join(tex_paragraphs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def placeholder(self, code: Text) -> Text:
 | 
					    def placeholder(self, code: str) -> str:
 | 
				
			||||||
        return self.markdown.htmlStash.store(code, safe=True)
 | 
					        return self.markdown.htmlStash.store(code, safe=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _escape(self, txt: Text) -> Text:
 | 
					    def _escape(self, txt: str) -> str:
 | 
				
			||||||
        """ basic html escaping """
 | 
					        """ basic html escaping """
 | 
				
			||||||
        txt = txt.replace('&', '&')
 | 
					        txt = txt.replace('&', '&')
 | 
				
			||||||
        txt = txt.replace('<', '<')
 | 
					        txt = txt.replace('<', '<')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
import markdown
 | 
					import markdown
 | 
				
			||||||
from typing import Any, Dict, List, Optional, Union, Text
 | 
					from typing import Any, Dict, List, Optional, Union
 | 
				
			||||||
from typing.re import Match
 | 
					from typing.re import Match
 | 
				
			||||||
from markdown.preprocessors import Preprocessor
 | 
					from markdown.preprocessors import Preprocessor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -91,7 +91,7 @@ class Setting(Preprocessor):
 | 
				
			|||||||
                done = True
 | 
					                done = True
 | 
				
			||||||
        return lines
 | 
					        return lines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handleMatch(self, match: Match[Text]) -> Text:
 | 
					    def handleMatch(self, match: Match[str]) -> str:
 | 
				
			||||||
        setting_identifier = match.group('setting_identifier')
 | 
					        setting_identifier = match.group('setting_identifier')
 | 
				
			||||||
        setting_type_name = link_mapping[setting_identifier][0]
 | 
					        setting_type_name = link_mapping[setting_identifier][0]
 | 
				
			||||||
        setting_name = link_mapping[setting_identifier][1]
 | 
					        setting_name = link_mapping[setting_identifier][1]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Dict, Optional, Text
 | 
					from typing import Any, Dict, Optional
 | 
				
			||||||
import ujson
 | 
					import ujson
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -221,7 +221,7 @@ EMOJI_TWEET = """{
 | 
				
			|||||||
    ]
 | 
					    ]
 | 
				
			||||||
}"""
 | 
					}"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def twitter(tweet_id: Text) -> Optional[Dict[Text, Any]]:
 | 
					def twitter(tweet_id: str) -> Optional[Dict[str, Any]]:
 | 
				
			||||||
    if tweet_id in ["112652479837110273", "287977969287315456", "287977969287315457"]:
 | 
					    if tweet_id in ["112652479837110273", "287977969287315456", "287977969287315457"]:
 | 
				
			||||||
        return ujson.loads(NORMAL_TWEET)
 | 
					        return ujson.loads(NORMAL_TWEET)
 | 
				
			||||||
    elif tweet_id == "287977969287315458":
 | 
					    elif tweet_id == "287977969287315458":
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,9 @@ import codecs
 | 
				
			|||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
import hmac
 | 
					import hmac
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Text
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Encodes the provided URL using the same algorithm used by the camo
 | 
					# Encodes the provided URL using the same algorithm used by the camo
 | 
				
			||||||
# caching https image proxy
 | 
					# caching https image proxy
 | 
				
			||||||
def get_camo_url(url: Text) -> Text:
 | 
					def get_camo_url(url: str) -> str:
 | 
				
			||||||
    # Only encode the url if Camo is enabled
 | 
					    # Only encode the url if Camo is enabled
 | 
				
			||||||
    if settings.CAMO_URI == '':
 | 
					    if settings.CAMO_URI == '':
 | 
				
			||||||
        return url
 | 
					        return url
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Any, Dict, List, Optional, Text
 | 
					from typing import Any, Dict, List, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# This file is adapted from samples/shellinabox/ssh-krb-wrapper in
 | 
					# This file is adapted from samples/shellinabox/ssh-krb-wrapper in
 | 
				
			||||||
# https://github.com/davidben/webathena, which has the following
 | 
					# https://github.com/davidben/webathena, which has the following
 | 
				
			||||||
@@ -82,8 +82,8 @@ def der_encode_uint32(val: int) -> bytes:
 | 
				
			|||||||
        raise ValueError("Bad value")
 | 
					        raise ValueError("Bad value")
 | 
				
			||||||
    return der_encode_integer(val)
 | 
					    return der_encode_integer(val)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def der_encode_string(val: Text) -> bytes:
 | 
					def der_encode_string(val: str) -> bytes:
 | 
				
			||||||
    if not isinstance(val, Text):
 | 
					    if not isinstance(val, str):
 | 
				
			||||||
        raise TypeError("unicode")
 | 
					        raise TypeError("unicode")
 | 
				
			||||||
    return der_encode_tlv(0x1b, val.encode("utf-8"))
 | 
					    return der_encode_tlv(0x1b, val.encode("utf-8"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,11 @@ import time
 | 
				
			|||||||
from psycopg2.extensions import cursor, connection
 | 
					from psycopg2.extensions import cursor, connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Callable, Optional, Iterable, Any, Dict, List, Union, TypeVar, \
 | 
					from typing import Callable, Optional, Iterable, Any, Dict, List, Union, TypeVar, \
 | 
				
			||||||
    Mapping, Text
 | 
					    Mapping
 | 
				
			||||||
from zerver.lib.str_utils import NonBinaryStr
 | 
					from zerver.lib.str_utils import NonBinaryStr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CursorObj = TypeVar('CursorObj', bound=cursor)
 | 
					CursorObj = TypeVar('CursorObj', bound=cursor)
 | 
				
			||||||
ParamsT = Union[Iterable[Any], Mapping[Text, Any]]
 | 
					ParamsT = Union[Iterable[Any], Mapping[str, Any]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Similar to the tracking done in Django's CursorDebugWrapper, but done at the
 | 
					# Similar to the tracking done in Django's CursorDebugWrapper, but done at the
 | 
				
			||||||
# psycopg2 cursor level so it works with SQLAlchemy.
 | 
					# psycopg2 cursor level so it works with SQLAlchemy.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Any, Callable, Dict, Iterable, List, Set, Tuple, Text
 | 
					from typing import Any, Callable, Dict, Iterable, List, Set, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from collections import defaultdict
 | 
					from collections import defaultdict
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
@@ -88,8 +88,8 @@ def gather_hot_conversations(user_profile: UserProfile, stream_messages: QuerySe
 | 
				
			|||||||
    # Returns a list of dictionaries containing the templating
 | 
					    # Returns a list of dictionaries containing the templating
 | 
				
			||||||
    # information for each hot conversation.
 | 
					    # information for each hot conversation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    conversation_length = defaultdict(int)  # type: Dict[Tuple[int, Text], int]
 | 
					    conversation_length = defaultdict(int)  # type: Dict[Tuple[int, str], int]
 | 
				
			||||||
    conversation_diversity = defaultdict(set)  # type: Dict[Tuple[int, Text], Set[Text]]
 | 
					    conversation_diversity = defaultdict(set)  # type: Dict[Tuple[int, str], Set[str]]
 | 
				
			||||||
    for user_message in stream_messages:
 | 
					    for user_message in stream_messages:
 | 
				
			||||||
        if not user_message.message.sent_by_human():
 | 
					        if not user_message.message.sent_by_human():
 | 
				
			||||||
            # Don't include automated messages in the count.
 | 
					            # Don't include automated messages in the count.
 | 
				
			||||||
@@ -143,7 +143,7 @@ def gather_hot_conversations(user_profile: UserProfile, stream_messages: QuerySe
 | 
				
			|||||||
        hot_conversation_render_payloads.append(teaser_data)
 | 
					        hot_conversation_render_payloads.append(teaser_data)
 | 
				
			||||||
    return hot_conversation_render_payloads
 | 
					    return hot_conversation_render_payloads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def gather_new_users(user_profile: UserProfile, threshold: datetime.datetime) -> Tuple[int, List[Text]]:
 | 
					def gather_new_users(user_profile: UserProfile, threshold: datetime.datetime) -> Tuple[int, List[str]]:
 | 
				
			||||||
    # Gather information on users in the realm who have recently
 | 
					    # Gather information on users in the realm who have recently
 | 
				
			||||||
    # joined.
 | 
					    # joined.
 | 
				
			||||||
    if user_profile.realm.is_zephyr_mirror_realm:
 | 
					    if user_profile.realm.is_zephyr_mirror_realm:
 | 
				
			||||||
@@ -157,7 +157,7 @@ def gather_new_users(user_profile: UserProfile, threshold: datetime.datetime) ->
 | 
				
			|||||||
    return len(user_names), user_names
 | 
					    return len(user_names), user_names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def gather_new_streams(user_profile: UserProfile,
 | 
					def gather_new_streams(user_profile: UserProfile,
 | 
				
			||||||
                       threshold: datetime.datetime) -> Tuple[int, Dict[str, List[Text]]]:
 | 
					                       threshold: datetime.datetime) -> Tuple[int, Dict[str, List[str]]]:
 | 
				
			||||||
    if user_profile.can_access_public_streams():
 | 
					    if user_profile.can_access_public_streams():
 | 
				
			||||||
        new_streams = list(get_active_streams(user_profile.realm).filter(
 | 
					        new_streams = list(get_active_streams(user_profile.realm).filter(
 | 
				
			||||||
            invite_only=False, date_created__gt=threshold))
 | 
					            invite_only=False, date_created__gt=threshold))
 | 
				
			||||||
@@ -177,7 +177,7 @@ def gather_new_streams(user_profile: UserProfile,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return len(new_streams), {"html": streams_html, "plain": streams_plain}
 | 
					    return len(new_streams), {"html": streams_html, "plain": streams_plain}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def enough_traffic(unread_pms: Text, hot_conversations: Text, new_streams: int, new_users: int) -> bool:
 | 
					def enough_traffic(unread_pms: str, hot_conversations: str, new_streams: int, new_users: int) -> bool:
 | 
				
			||||||
    if unread_pms or hot_conversations:
 | 
					    if unread_pms or hot_conversations:
 | 
				
			||||||
        # If you have any unread traffic, good enough.
 | 
					        # If you have any unread traffic, good enough.
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,9 +2,8 @@ from django.core.exceptions import ValidationError
 | 
				
			|||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
from typing import Text
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_domain(domain: Text) -> None:
 | 
					def validate_domain(domain: str) -> None:
 | 
				
			||||||
    if domain is None or len(domain) == 0:
 | 
					    if domain is None or len(domain) == 0:
 | 
				
			||||||
        raise ValidationError(_("Domain can't be empty."))
 | 
					        raise ValidationError(_("Domain can't be empty."))
 | 
				
			||||||
    if '.' not in domain:
 | 
					    if '.' not in domain:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Any, Dict, List, Optional, Text, Union
 | 
					from typing import Any, Dict, List, Optional, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
@@ -28,7 +28,7 @@ talon.init()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def redact_stream(error_message: Text) -> Text:
 | 
					def redact_stream(error_message: str) -> str:
 | 
				
			||||||
    domain = settings.EMAIL_GATEWAY_PATTERN.rsplit('@')[-1]
 | 
					    domain = settings.EMAIL_GATEWAY_PATTERN.rsplit('@')[-1]
 | 
				
			||||||
    stream_match = re.search('\\b(.*?)@' + domain, error_message)
 | 
					    stream_match = re.search('\\b(.*?)@' + domain, error_message)
 | 
				
			||||||
    if stream_match:
 | 
					    if stream_match:
 | 
				
			||||||
@@ -36,7 +36,7 @@ def redact_stream(error_message: Text) -> Text:
 | 
				
			|||||||
        return error_message.replace(stream_name, "X" * len(stream_name))
 | 
					        return error_message.replace(stream_name, "X" * len(stream_name))
 | 
				
			||||||
    return error_message
 | 
					    return error_message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def report_to_zulip(error_message: Text) -> None:
 | 
					def report_to_zulip(error_message: str) -> None:
 | 
				
			||||||
    if settings.ERROR_BOT is None:
 | 
					    if settings.ERROR_BOT is None:
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    error_bot = get_system_bot(settings.ERROR_BOT)
 | 
					    error_bot = get_system_bot(settings.ERROR_BOT)
 | 
				
			||||||
@@ -44,7 +44,7 @@ def report_to_zulip(error_message: Text) -> None:
 | 
				
			|||||||
    send_zulip(settings.ERROR_BOT, error_stream, "email mirror error",
 | 
					    send_zulip(settings.ERROR_BOT, error_stream, "email mirror error",
 | 
				
			||||||
               """~~~\n%s\n~~~""" % (error_message,))
 | 
					               """~~~\n%s\n~~~""" % (error_message,))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def log_and_report(email_message: message.Message, error_message: Text, debug_info: Dict[str, Any]) -> None:
 | 
					def log_and_report(email_message: message.Message, error_message: str, debug_info: Dict[str, Any]) -> None:
 | 
				
			||||||
    scrubbed_error = u"Sender: %s\n%s" % (email_message.get("From"),
 | 
					    scrubbed_error = u"Sender: %s\n%s" % (email_message.get("From"),
 | 
				
			||||||
                                          redact_stream(error_message))
 | 
					                                          redact_stream(error_message))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -65,22 +65,22 @@ def log_and_report(email_message: message.Message, error_message: Text, debug_in
 | 
				
			|||||||
redis_client = get_redis_client()
 | 
					redis_client = get_redis_client()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def missed_message_redis_key(token: Text) -> Text:
 | 
					def missed_message_redis_key(token: str) -> str:
 | 
				
			||||||
    return 'missed_message:' + token
 | 
					    return 'missed_message:' + token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_missed_message_address(address: Text) -> bool:
 | 
					def is_missed_message_address(address: str) -> bool:
 | 
				
			||||||
    msg_string = get_email_gateway_message_string_from_address(address)
 | 
					    msg_string = get_email_gateway_message_string_from_address(address)
 | 
				
			||||||
    return is_mm_32_format(msg_string)
 | 
					    return is_mm_32_format(msg_string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_mm_32_format(msg_string: Optional[Text]) -> bool:
 | 
					def is_mm_32_format(msg_string: Optional[str]) -> bool:
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    Missed message strings are formatted with a little "mm" prefix
 | 
					    Missed message strings are formatted with a little "mm" prefix
 | 
				
			||||||
    followed by a randomly generated 32-character string.
 | 
					    followed by a randomly generated 32-character string.
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    return msg_string is not None and msg_string.startswith('mm') and len(msg_string) == 34
 | 
					    return msg_string is not None and msg_string.startswith('mm') and len(msg_string) == 34
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_missed_message_token_from_address(address: Text) -> Text:
 | 
					def get_missed_message_token_from_address(address: str) -> str:
 | 
				
			||||||
    msg_string = get_email_gateway_message_string_from_address(address)
 | 
					    msg_string = get_email_gateway_message_string_from_address(address)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if msg_string is None:
 | 
					    if msg_string is None:
 | 
				
			||||||
@@ -125,7 +125,7 @@ def create_missed_message_address(user_profile: UserProfile, message: Message) -
 | 
				
			|||||||
    return settings.EMAIL_GATEWAY_PATTERN % (address,)
 | 
					    return settings.EMAIL_GATEWAY_PATTERN % (address,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def mark_missed_message_address_as_used(address: Text) -> None:
 | 
					def mark_missed_message_address_as_used(address: str) -> None:
 | 
				
			||||||
    token = get_missed_message_token_from_address(address)
 | 
					    token = get_missed_message_token_from_address(address)
 | 
				
			||||||
    key = missed_message_redis_key(token)
 | 
					    key = missed_message_redis_key(token)
 | 
				
			||||||
    with redis_client.pipeline() as pipeline:
 | 
					    with redis_client.pipeline() as pipeline:
 | 
				
			||||||
@@ -136,7 +136,7 @@ def mark_missed_message_address_as_used(address: Text) -> None:
 | 
				
			|||||||
        redis_client.delete(key)
 | 
					        redis_client.delete(key)
 | 
				
			||||||
        raise ZulipEmailForwardError('Missed message address has already been used')
 | 
					        raise ZulipEmailForwardError('Missed message address has already been used')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def construct_zulip_body(message: message.Message, realm: Realm) -> Text:
 | 
					def construct_zulip_body(message: message.Message, realm: Realm) -> str:
 | 
				
			||||||
    body = extract_body(message)
 | 
					    body = extract_body(message)
 | 
				
			||||||
    # Remove null characters, since Zulip will reject
 | 
					    # Remove null characters, since Zulip will reject
 | 
				
			||||||
    body = body.replace("\x00", "")
 | 
					    body = body.replace("\x00", "")
 | 
				
			||||||
@@ -147,7 +147,7 @@ def construct_zulip_body(message: message.Message, realm: Realm) -> Text:
 | 
				
			|||||||
        body = '(No email body)'
 | 
					        body = '(No email body)'
 | 
				
			||||||
    return body
 | 
					    return body
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def send_to_missed_message_address(address: Text, message: message.Message) -> None:
 | 
					def send_to_missed_message_address(address: str, message: message.Message) -> None:
 | 
				
			||||||
    token = get_missed_message_token_from_address(address)
 | 
					    token = get_missed_message_token_from_address(address)
 | 
				
			||||||
    key = missed_message_redis_key(token)
 | 
					    key = missed_message_redis_key(token)
 | 
				
			||||||
    result = redis_client.hmget(key, 'user_profile_id', 'recipient_id', 'subject')
 | 
					    result = redis_client.hmget(key, 'user_profile_id', 'recipient_id', 'subject')
 | 
				
			||||||
@@ -189,7 +189,7 @@ def send_to_missed_message_address(address: Text, message: message.Message) -> N
 | 
				
			|||||||
class ZulipEmailForwardError(Exception):
 | 
					class ZulipEmailForwardError(Exception):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def send_zulip(sender: Text, stream: Stream, topic: Text, content: Text) -> None:
 | 
					def send_zulip(sender: str, stream: Stream, topic: str, content: str) -> None:
 | 
				
			||||||
    internal_send_message(
 | 
					    internal_send_message(
 | 
				
			||||||
        stream.realm,
 | 
					        stream.realm,
 | 
				
			||||||
        sender,
 | 
					        sender,
 | 
				
			||||||
@@ -199,14 +199,14 @@ def send_zulip(sender: Text, stream: Stream, topic: Text, content: Text) -> None
 | 
				
			|||||||
        content[:2000],
 | 
					        content[:2000],
 | 
				
			||||||
        email_gateway=True)
 | 
					        email_gateway=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def valid_stream(stream_name: Text, token: Text) -> bool:
 | 
					def valid_stream(stream_name: str, token: str) -> bool:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        stream = Stream.objects.get(email_token=token)
 | 
					        stream = Stream.objects.get(email_token=token)
 | 
				
			||||||
        return stream.name.lower() == stream_name.lower()
 | 
					        return stream.name.lower() == stream_name.lower()
 | 
				
			||||||
    except Stream.DoesNotExist:
 | 
					    except Stream.DoesNotExist:
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_message_part_by_type(message: message.Message, content_type: Text) -> Optional[Text]:
 | 
					def get_message_part_by_type(message: message.Message, content_type: str) -> Optional[str]:
 | 
				
			||||||
    charsets = message.get_charsets()
 | 
					    charsets = message.get_charsets()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for idx, part in enumerate(message.walk()):
 | 
					    for idx, part in enumerate(message.walk()):
 | 
				
			||||||
@@ -217,7 +217,7 @@ def get_message_part_by_type(message: message.Message, content_type: Text) -> Op
 | 
				
			|||||||
                return content.decode(charsets[idx], errors="ignore")
 | 
					                return content.decode(charsets[idx], errors="ignore")
 | 
				
			||||||
    return None
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def extract_body(message: message.Message) -> Text:
 | 
					def extract_body(message: message.Message) -> str:
 | 
				
			||||||
    # If the message contains a plaintext version of the body, use
 | 
					    # If the message contains a plaintext version of the body, use
 | 
				
			||||||
    # that.
 | 
					    # that.
 | 
				
			||||||
    plaintext_content = get_message_part_by_type(message, "text/plain")
 | 
					    plaintext_content = get_message_part_by_type(message, "text/plain")
 | 
				
			||||||
@@ -231,7 +231,7 @@ def extract_body(message: message.Message) -> Text:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    raise ZulipEmailForwardError("Unable to find plaintext or HTML message body")
 | 
					    raise ZulipEmailForwardError("Unable to find plaintext or HTML message body")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def filter_footer(text: Text) -> Text:
 | 
					def filter_footer(text: str) -> str:
 | 
				
			||||||
    # Try to filter out obvious footers.
 | 
					    # Try to filter out obvious footers.
 | 
				
			||||||
    possible_footers = [line for line in text.split("\n") if line.strip().startswith("--")]
 | 
					    possible_footers = [line for line in text.split("\n") if line.strip().startswith("--")]
 | 
				
			||||||
    if len(possible_footers) != 1:
 | 
					    if len(possible_footers) != 1:
 | 
				
			||||||
@@ -241,7 +241,7 @@ def filter_footer(text: Text) -> Text:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return text.partition("--")[0].strip()
 | 
					    return text.partition("--")[0].strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def extract_and_upload_attachments(message: message.Message, realm: Realm) -> Text:
 | 
					def extract_and_upload_attachments(message: message.Message, realm: Realm) -> str:
 | 
				
			||||||
    user_profile = get_system_bot(settings.EMAIL_GATEWAY_BOT)
 | 
					    user_profile = get_system_bot(settings.EMAIL_GATEWAY_BOT)
 | 
				
			||||||
    attachment_links = []
 | 
					    attachment_links = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -268,7 +268,7 @@ def extract_and_upload_attachments(message: message.Message, realm: Realm) -> Te
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return "\n".join(attachment_links)
 | 
					    return "\n".join(attachment_links)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def extract_and_validate(email: Text) -> Stream:
 | 
					def extract_and_validate(email: str) -> Stream:
 | 
				
			||||||
    temp = decode_email_address(email)
 | 
					    temp = decode_email_address(email)
 | 
				
			||||||
    if temp is None:
 | 
					    if temp is None:
 | 
				
			||||||
        raise ZulipEmailForwardError("Malformed email recipient " + email)
 | 
					        raise ZulipEmailForwardError("Malformed email recipient " + email)
 | 
				
			||||||
@@ -279,12 +279,12 @@ def extract_and_validate(email: Text) -> Stream:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return Stream.objects.get(email_token=token)
 | 
					    return Stream.objects.get(email_token=token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def find_emailgateway_recipient(message: message.Message) -> Text:
 | 
					def find_emailgateway_recipient(message: message.Message) -> str:
 | 
				
			||||||
    # We can't use Delivered-To; if there is a X-Gm-Original-To
 | 
					    # We can't use Delivered-To; if there is a X-Gm-Original-To
 | 
				
			||||||
    # it is more accurate, so try to find the most-accurate
 | 
					    # it is more accurate, so try to find the most-accurate
 | 
				
			||||||
    # recipient list in descending priority order
 | 
					    # recipient list in descending priority order
 | 
				
			||||||
    recipient_headers = ["X-Gm-Original-To", "Delivered-To", "To"]
 | 
					    recipient_headers = ["X-Gm-Original-To", "Delivered-To", "To"]
 | 
				
			||||||
    recipients = []  # type: List[Union[Text, Header]]
 | 
					    recipients = []  # type: List[Union[str, Header]]
 | 
				
			||||||
    for recipient_header in recipient_headers:
 | 
					    for recipient_header in recipient_headers:
 | 
				
			||||||
        r = message.get_all(recipient_header, None)
 | 
					        r = message.get_all(recipient_header, None)
 | 
				
			||||||
        if r:
 | 
					        if r:
 | 
				
			||||||
@@ -299,7 +299,7 @@ def find_emailgateway_recipient(message: message.Message) -> Text:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    raise ZulipEmailForwardError("Missing recipient in mirror email")
 | 
					    raise ZulipEmailForwardError("Missing recipient in mirror email")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def process_stream_message(to: Text, subject: Text, message: message.Message,
 | 
					def process_stream_message(to: str, subject: str, message: message.Message,
 | 
				
			||||||
                           debug_info: Dict[str, Any]) -> None:
 | 
					                           debug_info: Dict[str, Any]) -> None:
 | 
				
			||||||
    stream = extract_and_validate(to)
 | 
					    stream = extract_and_validate(to)
 | 
				
			||||||
    body = construct_zulip_body(message, stream.realm)
 | 
					    body = construct_zulip_body(message, stream.realm)
 | 
				
			||||||
@@ -308,12 +308,12 @@ def process_stream_message(to: Text, subject: Text, message: message.Message,
 | 
				
			|||||||
    logger.info("Successfully processed email to %s (%s)" % (
 | 
					    logger.info("Successfully processed email to %s (%s)" % (
 | 
				
			||||||
        stream.name, stream.realm.string_id))
 | 
					        stream.name, stream.realm.string_id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def process_missed_message(to: Text, message: message.Message, pre_checked: bool) -> None:
 | 
					def process_missed_message(to: str, message: message.Message, pre_checked: bool) -> None:
 | 
				
			||||||
    if not pre_checked:
 | 
					    if not pre_checked:
 | 
				
			||||||
        mark_missed_message_address_as_used(to)
 | 
					        mark_missed_message_address_as_used(to)
 | 
				
			||||||
    send_to_missed_message_address(to, message)
 | 
					    send_to_missed_message_address(to, message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def process_message(message: message.Message, rcpt_to: Optional[Text]=None, pre_checked: bool=False) -> None:
 | 
					def process_message(message: message.Message, rcpt_to: Optional[str]=None, pre_checked: bool=False) -> None:
 | 
				
			||||||
    subject_header = str(message.get("Subject", "")).strip()
 | 
					    subject_header = str(message.get("Subject", "")).strip()
 | 
				
			||||||
    if subject_header == "":
 | 
					    if subject_header == "":
 | 
				
			||||||
        subject_header = "(no topic)"
 | 
					        subject_header = "(no topic)"
 | 
				
			||||||
@@ -344,7 +344,7 @@ def process_message(message: message.Message, rcpt_to: Optional[Text]=None, pre_
 | 
				
			|||||||
        log_and_report(message, str(e), debug_info)
 | 
					        log_and_report(message, str(e), debug_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def mirror_email_message(data: Dict[Text, Text]) -> Dict[str, str]:
 | 
					def mirror_email_message(data: Dict[str, str]) -> Dict[str, str]:
 | 
				
			||||||
    rcpt_to = data['recipient']
 | 
					    rcpt_to = data['recipient']
 | 
				
			||||||
    if is_missed_message_address(rcpt_to):
 | 
					    if is_missed_message_address(rcpt_to):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ import ujson
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
from typing import Optional, Text, Tuple
 | 
					from typing import Optional, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.request import JsonableError
 | 
					from zerver.lib.request import JsonableError
 | 
				
			||||||
from zerver.lib.upload import upload_backend
 | 
					from zerver.lib.upload import upload_backend
 | 
				
			||||||
@@ -34,7 +34,7 @@ emoticon_regex = ('(?<![^{0}])(?P<emoticon>('.format(terminal_symbols)
 | 
				
			|||||||
                  + '))(?![^{0}])'.format(terminal_symbols))
 | 
					                  + '))(?![^{0}])'.format(terminal_symbols))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Translates emoticons to their colon syntax, e.g. `:smiley:`.
 | 
					# Translates emoticons to their colon syntax, e.g. `:smiley:`.
 | 
				
			||||||
def translate_emoticons(text: Text) -> Text:
 | 
					def translate_emoticons(text: str) -> str:
 | 
				
			||||||
    translated = text
 | 
					    translated = text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for emoticon in EMOTICON_CONVERSIONS:
 | 
					    for emoticon in EMOTICON_CONVERSIONS:
 | 
				
			||||||
@@ -48,7 +48,7 @@ with open(NAME_TO_CODEPOINT_PATH) as fp:
 | 
				
			|||||||
with open(CODEPOINT_TO_NAME_PATH) as fp:
 | 
					with open(CODEPOINT_TO_NAME_PATH) as fp:
 | 
				
			||||||
    codepoint_to_name = ujson.load(fp)
 | 
					    codepoint_to_name = ujson.load(fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def emoji_name_to_emoji_code(realm: Realm, emoji_name: Text) -> Tuple[Text, Text]:
 | 
					def emoji_name_to_emoji_code(realm: Realm, emoji_name: str) -> Tuple[str, str]:
 | 
				
			||||||
    realm_emojis = realm.get_active_emoji()
 | 
					    realm_emojis = realm.get_active_emoji()
 | 
				
			||||||
    realm_emoji = realm_emojis.get(emoji_name)
 | 
					    realm_emoji = realm_emojis.get(emoji_name)
 | 
				
			||||||
    if realm_emoji is not None:
 | 
					    if realm_emoji is not None:
 | 
				
			||||||
@@ -59,7 +59,7 @@ def emoji_name_to_emoji_code(realm: Realm, emoji_name: Text) -> Tuple[Text, Text
 | 
				
			|||||||
        return name_to_codepoint[emoji_name], Reaction.UNICODE_EMOJI
 | 
					        return name_to_codepoint[emoji_name], Reaction.UNICODE_EMOJI
 | 
				
			||||||
    raise JsonableError(_("Emoji '%s' does not exist" % (emoji_name,)))
 | 
					    raise JsonableError(_("Emoji '%s' does not exist" % (emoji_name,)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_valid_emoji(realm: Realm, emoji_name: Text) -> None:
 | 
					def check_valid_emoji(realm: Realm, emoji_name: str) -> None:
 | 
				
			||||||
    emoji_name_to_emoji_code(realm, emoji_name)
 | 
					    emoji_name_to_emoji_code(realm, emoji_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_emoji_request(realm: Realm, emoji_name: str, emoji_code: str,
 | 
					def check_emoji_request(realm: Realm, emoji_name: str, emoji_code: str,
 | 
				
			||||||
@@ -89,7 +89,7 @@ def check_emoji_request(realm: Realm, emoji_name: str, emoji_code: str,
 | 
				
			|||||||
        # The above are the only valid emoji types
 | 
					        # The above are the only valid emoji types
 | 
				
			||||||
        raise JsonableError(_("Invalid emoji type."))
 | 
					        raise JsonableError(_("Invalid emoji type."))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_emoji_admin(user_profile: UserProfile, emoji_name: Optional[Text]=None) -> None:
 | 
					def check_emoji_admin(user_profile: UserProfile, emoji_name: Optional[str]=None) -> None:
 | 
				
			||||||
    """Raises an exception if the user cannot administer the target realm
 | 
					    """Raises an exception if the user cannot administer the target realm
 | 
				
			||||||
    emoji name in their organization."""
 | 
					    emoji name in their organization."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -113,15 +113,15 @@ def check_emoji_admin(user_profile: UserProfile, emoji_name: Optional[Text]=None
 | 
				
			|||||||
    if not user_profile.is_realm_admin and not current_user_is_author:
 | 
					    if not user_profile.is_realm_admin and not current_user_is_author:
 | 
				
			||||||
        raise JsonableError(_("Must be an organization administrator or emoji author"))
 | 
					        raise JsonableError(_("Must be an organization administrator or emoji author"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_valid_emoji_name(emoji_name: Text) -> None:
 | 
					def check_valid_emoji_name(emoji_name: str) -> None:
 | 
				
			||||||
    if re.match('^[0-9a-z.\-_]+(?<![.\-_])$', emoji_name):
 | 
					    if re.match('^[0-9a-z.\-_]+(?<![.\-_])$', emoji_name):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    raise JsonableError(_("Invalid characters in emoji name"))
 | 
					    raise JsonableError(_("Invalid characters in emoji name"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_emoji_url(emoji_file_name: Text, realm_id: int) -> Text:
 | 
					def get_emoji_url(emoji_file_name: str, realm_id: int) -> str:
 | 
				
			||||||
    return upload_backend.get_emoji_url(emoji_file_name, realm_id)
 | 
					    return upload_backend.get_emoji_url(emoji_file_name, realm_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_emoji_file_name(emoji_file_name: Text, emoji_id: int) -> Text:
 | 
					def get_emoji_file_name(emoji_file_name: str, emoji_id: int) -> str:
 | 
				
			||||||
    _, image_ext = os.path.splitext(emoji_file_name)
 | 
					    _, image_ext = os.path.splitext(emoji_file_name)
 | 
				
			||||||
    return ''.join((str(emoji_id), image_ext))
 | 
					    return ''.join((str(emoji_id), image_ext))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ from django.conf import settings
 | 
				
			|||||||
from django.core.mail import mail_admins
 | 
					from django.core.mail import mail_admins
 | 
				
			||||||
from django.http import HttpResponse
 | 
					from django.http import HttpResponse
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
from typing import Any, Dict, Optional, Text
 | 
					from typing import Any, Dict, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.models import get_system_bot
 | 
					from zerver.models import get_system_bot
 | 
				
			||||||
from zerver.lib.actions import internal_send_message
 | 
					from zerver.lib.actions import internal_send_message
 | 
				
			||||||
@@ -136,7 +136,7 @@ def email_server_error(report: Dict[str, Any]) -> None:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    mail_admins(format_subject(subject), message, fail_silently=True)
 | 
					    mail_admins(format_subject(subject), message, fail_silently=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def do_report_error(deployment_name: Text, type: Text, report: Dict[str, Any]) -> HttpResponse:
 | 
					def do_report_error(deployment_name: str, type: str, report: Dict[str, Any]) -> HttpResponse:
 | 
				
			||||||
    report['deployment'] = deployment_name
 | 
					    report['deployment'] = deployment_name
 | 
				
			||||||
    if type == 'browser':
 | 
					    if type == 'browser':
 | 
				
			||||||
        notify_browser_error(report)
 | 
					        notify_browser_error(report)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
import time
 | 
					import time
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Callable, List, TypeVar, Text
 | 
					from typing import Callable, List, TypeVar
 | 
				
			||||||
from psycopg2.extensions import cursor
 | 
					from psycopg2.extensions import cursor
 | 
				
			||||||
CursorObj = TypeVar('CursorObj', bound=cursor)
 | 
					CursorObj = TypeVar('CursorObj', bound=cursor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,7 +20,7 @@ migration runs.
 | 
				
			|||||||
logger = logging.getLogger('zulip.fix_unreads')
 | 
					logger = logging.getLogger('zulip.fix_unreads')
 | 
				
			||||||
logger.setLevel(logging.WARNING)
 | 
					logger.setLevel(logging.WARNING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def build_topic_mute_checker(cursor: CursorObj, user_profile: UserProfile) -> Callable[[int, Text], bool]:
 | 
					def build_topic_mute_checker(cursor: CursorObj, user_profile: UserProfile) -> Callable[[int, str], bool]:
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    This function is similar to the function of the same name
 | 
					    This function is similar to the function of the same name
 | 
				
			||||||
    in zerver/lib/topic_mutes.py, but it works without the ORM,
 | 
					    in zerver/lib/topic_mutes.py, but it works without the ORM,
 | 
				
			||||||
@@ -43,7 +43,7 @@ def build_topic_mute_checker(cursor: CursorObj, user_profile: UserProfile) -> Ca
 | 
				
			|||||||
        for (recipient_id, topic_name) in rows
 | 
					        for (recipient_id, topic_name) in rows
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_muted(recipient_id: int, topic: Text) -> bool:
 | 
					    def is_muted(recipient_id: int, topic: str) -> bool:
 | 
				
			||||||
        return (recipient_id, topic.lower()) in tups
 | 
					        return (recipient_id, topic.lower()) in tups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return is_muted
 | 
					    return is_muted
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import itertools
 | 
					import itertools
 | 
				
			||||||
import ujson
 | 
					import ujson
 | 
				
			||||||
import random
 | 
					import random
 | 
				
			||||||
from typing import List, Dict, Any, Text, Optional
 | 
					from typing import List, Dict, Any, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def load_config() -> Dict[str, Any]:
 | 
					def load_config() -> Dict[str, Any]:
 | 
				
			||||||
    with open("zerver/tests/fixtures/config.generate_data.json", "r") as infile:
 | 
					    with open("zerver/tests/fixtures/config.generate_data.json", "r") as infile:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ from django.utils.translation import ugettext as _
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from zerver.models import UserProfile, UserHotspot
 | 
					from zerver.models import UserProfile, UserHotspot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import List, Text, Dict
 | 
					from typing import List, Dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ALL_HOTSPOTS = {
 | 
					ALL_HOTSPOTS = {
 | 
				
			||||||
    'intro_reply': {
 | 
					    'intro_reply': {
 | 
				
			||||||
@@ -26,7 +26,7 @@ ALL_HOTSPOTS = {
 | 
				
			|||||||
        'description': _('Click here to start a new conversation. Pick a topic '
 | 
					        'description': _('Click here to start a new conversation. Pick a topic '
 | 
				
			||||||
                         '(2-3 words is best), and give it a go!'),
 | 
					                         '(2-3 words is best), and give it a go!'),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}  # type: Dict[str, Dict[str, Text]]
 | 
					}  # type: Dict[str, Dict[str, str]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_next_hotspots(user: UserProfile) -> List[Dict[str, object]]:
 | 
					def get_next_hotspots(user: UserProfile) -> List[Dict[str, object]]:
 | 
				
			||||||
    # For manual testing, it can be convenient to set
 | 
					    # For manual testing, it can be convenient to set
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,10 +4,10 @@ from django.conf import settings
 | 
				
			|||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
import base64
 | 
					import base64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Optional, Text
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def initial_password(email: Text) -> Optional[Text]:
 | 
					def initial_password(email: str) -> Optional[str]:
 | 
				
			||||||
    """Given an email address, returns the initial password for that account, as
 | 
					    """Given an email address, returns the initial password for that account, as
 | 
				
			||||||
       created by populate_db."""
 | 
					       created by populate_db."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
from typing import Any, Callable, Dict, List, Tuple, Text
 | 
					from typing import Any, Callable, Dict, List, Tuple
 | 
				
			||||||
from django.db.models.query import QuerySet
 | 
					from django.db.models.query import QuerySet
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_index_if_not_exist(index_name: Text, table_name: Text, column_string: Text,
 | 
					def create_index_if_not_exist(index_name: str, table_name: str, column_string: str,
 | 
				
			||||||
                              where_clause: Text) -> Text:
 | 
					                              where_clause: str) -> str:
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
    # FUTURE TODO: When we no longer need to support postgres 9.3 for Trusty,
 | 
					    # FUTURE TODO: When we no longer need to support postgres 9.3 for Trusty,
 | 
				
			||||||
    #              we can use "IF NOT EXISTS", which is part of postgres 9.5
 | 
					    #              we can use "IF NOT EXISTS", which is part of postgres 9.5
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
from typing import Text
 | 
					 | 
				
			||||||
from disposable_email_domains import blacklist
 | 
					from disposable_email_domains import blacklist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_reserved_subdomain(subdomain: Text) -> bool:
 | 
					def is_reserved_subdomain(subdomain: str) -> bool:
 | 
				
			||||||
    if subdomain in ZULIP_RESERVED_SUBDOMAINS:
 | 
					    if subdomain in ZULIP_RESERVED_SUBDOMAINS:
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
    if subdomain[-1] == 's' and subdomain[:-1] in ZULIP_RESERVED_SUBDOMAINS:
 | 
					    if subdomain[-1] == 's' and subdomain[:-1] in ZULIP_RESERVED_SUBDOMAINS:
 | 
				
			||||||
@@ -12,7 +11,7 @@ def is_reserved_subdomain(subdomain: Text) -> bool:
 | 
				
			|||||||
        return True
 | 
					        return True
 | 
				
			||||||
    return False
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_disposable_domain(domain: Text) -> bool:
 | 
					def is_disposable_domain(domain: str) -> bool:
 | 
				
			||||||
    return domain.lower() in DISPOSABLE_DOMAINS
 | 
					    return domain.lower() in DISPOSABLE_DOMAINS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ZULIP_RESERVED_SUBDOMAINS = frozenset([
 | 
					ZULIP_RESERVED_SUBDOMAINS = frozenset([
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,16 @@
 | 
				
			|||||||
from zerver.lib.request import JsonableError
 | 
					from zerver.lib.request import JsonableError
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Callable, Iterable, Mapping, Sequence, Text
 | 
					from typing import Any, Callable, Iterable, Mapping, Sequence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_supported_events_narrow_filter(narrow: Iterable[Sequence[Text]]) -> None:
 | 
					def check_supported_events_narrow_filter(narrow: Iterable[Sequence[str]]) -> None:
 | 
				
			||||||
    for element in narrow:
 | 
					    for element in narrow:
 | 
				
			||||||
        operator = element[0]
 | 
					        operator = element[0]
 | 
				
			||||||
        if operator not in ["stream", "topic", "sender", "is"]:
 | 
					        if operator not in ["stream", "topic", "sender", "is"]:
 | 
				
			||||||
            raise JsonableError(_("Operator %s not supported.") % (operator,))
 | 
					            raise JsonableError(_("Operator %s not supported.") % (operator,))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def build_narrow_filter(narrow: Iterable[Sequence[Text]]) -> Callable[[Mapping[str, Any]], bool]:
 | 
					def build_narrow_filter(narrow: Iterable[Sequence[str]]) -> Callable[[Mapping[str, Any]], bool]:
 | 
				
			||||||
    """Changes to this function should come with corresponding changes to
 | 
					    """Changes to this function should come with corresponding changes to
 | 
				
			||||||
    BuildNarrowFilterTest."""
 | 
					    BuildNarrowFilterTest."""
 | 
				
			||||||
    check_supported_events_narrow_filter(narrow)
 | 
					    check_supported_events_narrow_filter(narrow)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,13 @@
 | 
				
			|||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Text
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from zerver.lib.avatar_hash import gravatar_hash, user_avatar_hash
 | 
					from zerver.lib.avatar_hash import gravatar_hash, user_avatar_hash
 | 
				
			||||||
from zerver.lib.upload import upload_backend
 | 
					from zerver.lib.upload import upload_backend
 | 
				
			||||||
from zerver.models import Realm
 | 
					from zerver.models import Realm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def realm_icon_url(realm: Realm) -> Text:
 | 
					def realm_icon_url(realm: Realm) -> str:
 | 
				
			||||||
    return get_realm_icon_url(realm)
 | 
					    return get_realm_icon_url(realm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_realm_icon_url(realm: Realm) -> Text:
 | 
					def get_realm_icon_url(realm: Realm) -> str:
 | 
				
			||||||
    if realm.icon_source == 'U':
 | 
					    if realm.icon_source == 'U':
 | 
				
			||||||
        return upload_backend.get_realm_icon_url(realm.id, realm.icon_version)
 | 
					        return upload_backend.get_realm_icon_url(realm.id, realm.icon_version)
 | 
				
			||||||
    elif settings.ENABLE_GRAVATAR:
 | 
					    elif settings.ENABLE_GRAVATAR:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,13 +2,13 @@
 | 
				
			|||||||
from django.http import HttpResponse, HttpResponseNotAllowed
 | 
					from django.http import HttpResponse, HttpResponseNotAllowed
 | 
				
			||||||
import ujson
 | 
					import ujson
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Optional, Any, Dict, List, Text
 | 
					from typing import Optional, Any, Dict, List
 | 
				
			||||||
from zerver.lib.exceptions import JsonableError
 | 
					from zerver.lib.exceptions import JsonableError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HttpResponseUnauthorized(HttpResponse):
 | 
					class HttpResponseUnauthorized(HttpResponse):
 | 
				
			||||||
    status_code = 401
 | 
					    status_code = 401
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, realm: Text, www_authenticate: Optional[Text]=None) -> None:
 | 
					    def __init__(self, realm: str, www_authenticate: Optional[str]=None) -> None:
 | 
				
			||||||
        HttpResponse.__init__(self)
 | 
					        HttpResponse.__init__(self)
 | 
				
			||||||
        if www_authenticate is None:
 | 
					        if www_authenticate is None:
 | 
				
			||||||
            self["WWW-Authenticate"] = 'Basic realm="%s"' % (realm,)
 | 
					            self["WWW-Authenticate"] = 'Basic realm="%s"' % (realm,)
 | 
				
			||||||
@@ -17,21 +17,21 @@ class HttpResponseUnauthorized(HttpResponse):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise AssertionError("Invalid www_authenticate value!")
 | 
					            raise AssertionError("Invalid www_authenticate value!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def json_unauthorized(message: Text, www_authenticate: Optional[Text]=None) -> HttpResponse:
 | 
					def json_unauthorized(message: str, www_authenticate: Optional[str]=None) -> HttpResponse:
 | 
				
			||||||
    resp = HttpResponseUnauthorized("zulip", www_authenticate=www_authenticate)
 | 
					    resp = HttpResponseUnauthorized("zulip", www_authenticate=www_authenticate)
 | 
				
			||||||
    resp.content = (ujson.dumps({"result": "error",
 | 
					    resp.content = (ujson.dumps({"result": "error",
 | 
				
			||||||
                                 "msg": message}) + "\n").encode()
 | 
					                                 "msg": message}) + "\n").encode()
 | 
				
			||||||
    return resp
 | 
					    return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def json_method_not_allowed(methods: List[Text]) -> HttpResponseNotAllowed:
 | 
					def json_method_not_allowed(methods: List[str]) -> HttpResponseNotAllowed:
 | 
				
			||||||
    resp = HttpResponseNotAllowed(methods)
 | 
					    resp = HttpResponseNotAllowed(methods)
 | 
				
			||||||
    resp.content = ujson.dumps({"result": "error",
 | 
					    resp.content = ujson.dumps({"result": "error",
 | 
				
			||||||
                                "msg": "Method Not Allowed",
 | 
					                                "msg": "Method Not Allowed",
 | 
				
			||||||
                                "allowed_methods": methods}).encode()
 | 
					                                "allowed_methods": methods}).encode()
 | 
				
			||||||
    return resp
 | 
					    return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def json_response(res_type: Text="success",
 | 
					def json_response(res_type: str="success",
 | 
				
			||||||
                  msg: Text="",
 | 
					                  msg: str="",
 | 
				
			||||||
                  data: Optional[Dict[str, Any]]=None,
 | 
					                  data: Optional[Dict[str, Any]]=None,
 | 
				
			||||||
                  status: int=200) -> HttpResponse:
 | 
					                  status: int=200) -> HttpResponse:
 | 
				
			||||||
    content = {"result": res_type, "msg": msg}
 | 
					    content = {"result": res_type, "msg": msg}
 | 
				
			||||||
@@ -56,5 +56,5 @@ def json_response_from_error(exception: JsonableError) -> HttpResponse:
 | 
				
			|||||||
                         data=exception.data,
 | 
					                         data=exception.data,
 | 
				
			||||||
                         status=exception.http_status_code)
 | 
					                         status=exception.http_status_code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def json_error(msg: Text, data: Optional[Dict[str, Any]]=None, status: int=400) -> HttpResponse:
 | 
					def json_error(msg: str, data: Optional[Dict[str, Any]]=None, status: int=400) -> HttpResponse:
 | 
				
			||||||
    return json_response(res_type="error", msg=msg, data=data, status=status)
 | 
					    return json_response(res_type="error", msg=msg, data=data, status=status)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,13 @@ from django.contrib.auth import SESSION_KEY, get_user_model
 | 
				
			|||||||
from django.contrib.sessions.models import Session
 | 
					from django.contrib.sessions.models import Session
 | 
				
			||||||
from django.utils.timezone import now as timezone_now
 | 
					from django.utils.timezone import now as timezone_now
 | 
				
			||||||
from importlib import import_module
 | 
					from importlib import import_module
 | 
				
			||||||
from typing import List, Mapping, Optional, Text
 | 
					from typing import List, Mapping, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.models import Realm, UserProfile, get_user_profile_by_id
 | 
					from zerver.models import Realm, UserProfile, get_user_profile_by_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
session_engine = import_module(settings.SESSION_ENGINE)
 | 
					session_engine = import_module(settings.SESSION_ENGINE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_session_dict_user(session_dict: Mapping[Text, int]) -> Optional[int]:
 | 
					def get_session_dict_user(session_dict: Mapping[str, int]) -> Optional[int]:
 | 
				
			||||||
    # Compare django.contrib.auth._get_user_session_key
 | 
					    # Compare django.contrib.auth._get_user_session_key
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        return get_user_model()._meta.pk.to_python(session_dict[SESSION_KEY])
 | 
					        return get_user_model()._meta.pk.to_python(session_dict[SESSION_KEY])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ Currently we have strings of 3 semantic types:
 | 
				
			|||||||
1.  text strings: These strings are used to represent all textual data,
 | 
					1.  text strings: These strings are used to represent all textual data,
 | 
				
			||||||
    like people's names, stream names, content of messages, etc.
 | 
					    like people's names, stream names, content of messages, etc.
 | 
				
			||||||
    These strings can contain non-ASCII characters, so its type should be
 | 
					    These strings can contain non-ASCII characters, so its type should be
 | 
				
			||||||
    typing.Text (which is `str` in python 3 and `unicode` in python 2).
 | 
					    typing.str (which is `str` in python 3 and `unicode` in python 2).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2.  binary strings: These strings are used to represent binary data.
 | 
					2.  binary strings: These strings are used to represent binary data.
 | 
				
			||||||
    This should be of type `bytes`
 | 
					    This should be of type `bytes`
 | 
				
			||||||
@@ -29,34 +29,34 @@ force_text and force_bytes.
 | 
				
			|||||||
It is recommended to use the utility functions for other string conversions.
 | 
					It is recommended to use the utility functions for other string conversions.
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Dict, Mapping, Union, TypeVar, Text
 | 
					from typing import Any, Dict, Mapping, Union, TypeVar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NonBinaryStr = TypeVar('NonBinaryStr', str, Text)
 | 
					NonBinaryStr = TypeVar('NonBinaryStr', str, str)
 | 
				
			||||||
# This is used to represent text or native strings
 | 
					# This is used to represent text or native strings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def force_text(s: Union[Text, bytes], encoding: str='utf-8') -> Text:
 | 
					def force_text(s: Union[str, bytes], encoding: str='utf-8') -> str:
 | 
				
			||||||
    """converts a string to a text string"""
 | 
					    """converts a string to a text string"""
 | 
				
			||||||
    if isinstance(s, Text):
 | 
					    if isinstance(s, str):
 | 
				
			||||||
        return s
 | 
					        return s
 | 
				
			||||||
    elif isinstance(s, bytes):
 | 
					    elif isinstance(s, bytes):
 | 
				
			||||||
        return s.decode(encoding)
 | 
					        return s.decode(encoding)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        raise TypeError("force_text expects a string type")
 | 
					        raise TypeError("force_text expects a string type")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def force_bytes(s: Union[Text, bytes], encoding: str='utf-8') -> bytes:
 | 
					def force_bytes(s: Union[str, bytes], encoding: str='utf-8') -> bytes:
 | 
				
			||||||
    """converts a string to binary string"""
 | 
					    """converts a string to binary string"""
 | 
				
			||||||
    if isinstance(s, bytes):
 | 
					    if isinstance(s, bytes):
 | 
				
			||||||
        return s
 | 
					        return s
 | 
				
			||||||
    elif isinstance(s, Text):
 | 
					    elif isinstance(s, str):
 | 
				
			||||||
        return s.encode(encoding)
 | 
					        return s.encode(encoding)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        raise TypeError("force_bytes expects a string type")
 | 
					        raise TypeError("force_bytes expects a string type")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def force_str(s: Union[Text, bytes], encoding: str='utf-8') -> str:
 | 
					def force_str(s: Union[str, bytes], encoding: str='utf-8') -> str:
 | 
				
			||||||
    """converts a string to a native string"""
 | 
					    """converts a string to a native string"""
 | 
				
			||||||
    if isinstance(s, str):
 | 
					    if isinstance(s, str):
 | 
				
			||||||
        return s
 | 
					        return s
 | 
				
			||||||
    elif isinstance(s, Text):
 | 
					    elif isinstance(s, str):
 | 
				
			||||||
        return s.encode(encoding)
 | 
					        return s.encode(encoding)
 | 
				
			||||||
    elif isinstance(s, bytes):
 | 
					    elif isinstance(s, bytes):
 | 
				
			||||||
        return s.decode(encoding)
 | 
					        return s.decode(encoding)
 | 
				
			||||||
@@ -67,11 +67,11 @@ class ModelReprMixin:
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    This mixin provides a python 2 and 3 compatible way of handling string representation of a model.
 | 
					    This mixin provides a python 2 and 3 compatible way of handling string representation of a model.
 | 
				
			||||||
    When declaring a model, inherit this mixin before django.db.models.Model.
 | 
					    When declaring a model, inherit this mixin before django.db.models.Model.
 | 
				
			||||||
    Define __unicode__ on your model which returns a typing.Text object.
 | 
					    Define __unicode__ on your model which returns a str object.
 | 
				
			||||||
    This mixin will automatically define __str__ and __repr__.
 | 
					    This mixin will automatically define __str__ and __repr__.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __unicode__(self) -> Text:
 | 
					    def __unicode__(self) -> str:
 | 
				
			||||||
        # Originally raised an exception, but Django (e.g. the ./manage.py shell)
 | 
					        # Originally raised an exception, but Django (e.g. the ./manage.py shell)
 | 
				
			||||||
        # was catching the exception and not displaying any sort of error
 | 
					        # was catching the exception and not displaying any sort of error
 | 
				
			||||||
        return "Implement __unicode__ in your subclass of ModelReprMixin!"
 | 
					        return "Implement __unicode__ in your subclass of ModelReprMixin!"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,11 @@
 | 
				
			|||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.http import HttpRequest
 | 
					from django.http import HttpRequest
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
from typing import Optional, Text
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.models import get_realm, Realm, UserProfile
 | 
					from zerver.models import get_realm, Realm, UserProfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_subdomain(request: HttpRequest) -> Text:
 | 
					def get_subdomain(request: HttpRequest) -> str:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # The HTTP spec allows, but doesn't require, a client to omit the
 | 
					    # The HTTP spec allows, but doesn't require, a client to omit the
 | 
				
			||||||
    # port in the `Host` header if it's "the default port for the
 | 
					    # port in the `Host` header if it's "the default port for the
 | 
				
			||||||
@@ -41,7 +41,7 @@ def get_subdomain(request: HttpRequest) -> Text:
 | 
				
			|||||||
def is_subdomain_root_or_alias(request: HttpRequest) -> bool:
 | 
					def is_subdomain_root_or_alias(request: HttpRequest) -> bool:
 | 
				
			||||||
    return get_subdomain(request) == Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
 | 
					    return get_subdomain(request) == Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_matches_subdomain(realm_subdomain: Optional[Text], user_profile: UserProfile) -> bool:
 | 
					def user_matches_subdomain(realm_subdomain: Optional[str], user_profile: UserProfile) -> bool:
 | 
				
			||||||
    if realm_subdomain is None:
 | 
					    if realm_subdomain is None:
 | 
				
			||||||
        return True  # nocoverage # This state may no longer be possible.
 | 
					        return True  # nocoverage # This state may no longer be possible.
 | 
				
			||||||
    return user_profile.realm.subdomain == realm_subdomain
 | 
					    return user_profile.realm.subdomain == realm_subdomain
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ import os
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
from typing import Any, List, Optional, Text
 | 
					from typing import Any, List, Optional
 | 
				
			||||||
from importlib import import_module
 | 
					from importlib import import_module
 | 
				
			||||||
from io import StringIO
 | 
					from io import StringIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,7 +20,7 @@ from scripts.lib.zulip_tools import get_dev_uuid_var_path
 | 
				
			|||||||
UUID_VAR_DIR = get_dev_uuid_var_path()
 | 
					UUID_VAR_DIR = get_dev_uuid_var_path()
 | 
				
			||||||
FILENAME_SPLITTER = re.compile('[\W\-_]')
 | 
					FILENAME_SPLITTER = re.compile('[\W\-_]')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def database_exists(database_name: Text, **options: Any) -> bool:
 | 
					def database_exists(database_name: str, **options: Any) -> bool:
 | 
				
			||||||
    db = options.get('database', DEFAULT_DB_ALIAS)
 | 
					    db = options.get('database', DEFAULT_DB_ALIAS)
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        connection = connections[db]
 | 
					        connection = connections[db]
 | 
				
			||||||
@@ -59,7 +59,7 @@ def get_migration_status(**options: Any) -> str:
 | 
				
			|||||||
    output = out.read()
 | 
					    output = out.read()
 | 
				
			||||||
    return re.sub('\x1b\[(1|0)m', '', output)
 | 
					    return re.sub('\x1b\[(1|0)m', '', output)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def are_migrations_the_same(migration_file: Text, **options: Any) -> bool:
 | 
					def are_migrations_the_same(migration_file: str, **options: Any) -> bool:
 | 
				
			||||||
    if not os.path.exists(migration_file):
 | 
					    if not os.path.exists(migration_file):
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,9 +3,9 @@ import logging
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from typing import Optional, Text
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def render_tex(tex: Text, is_inline: bool=True) -> Optional[Text]:
 | 
					def render_tex(tex: str, is_inline: bool=True) -> Optional[str]:
 | 
				
			||||||
    """Render a TeX string into HTML using KaTeX
 | 
					    """Render a TeX string into HTML using KaTeX
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Returns the HTML string, or None if there was some error in the TeX syntax
 | 
					    Returns the HTML string, or None if there was some error in the TeX syntax
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from typing import Text, List
 | 
					from typing import List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytz
 | 
					import pytz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_all_timezones() -> List[Text]:
 | 
					def get_all_timezones() -> List[str]:
 | 
				
			||||||
    return sorted(pytz.all_timezones)
 | 
					    return sorted(pytz.all_timezones)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_timezone(tz: Text) -> pytz.datetime.tzinfo:
 | 
					def get_timezone(tz: str) -> pytz.datetime.tzinfo:
 | 
				
			||||||
    return pytz.timezone(tz)
 | 
					    return pytz.timezone(tz)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
import sys
 | 
					import sys
 | 
				
			||||||
import functools
 | 
					import functools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Any, Callable, IO, Mapping, Sequence, TypeVar, Text
 | 
					from typing import Any, Callable, IO, Mapping, Sequence, TypeVar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_mapping_type_str(x: Mapping[Any, Any]) -> str:
 | 
					def get_mapping_type_str(x: Mapping[Any, Any]) -> str:
 | 
				
			||||||
    container_type = type(x).__name__
 | 
					    container_type = type(x).__name__
 | 
				
			||||||
@@ -44,7 +44,7 @@ def get_sequence_type_str(x: Sequence[Any]) -> str:
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return '%s([%s, ...])' % (container_type, elem_type)
 | 
					            return '%s([%s, ...])' % (container_type, elem_type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
expansion_blacklist = [Text, bytes]
 | 
					expansion_blacklist = [str, bytes]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_type_str(x: Any) -> str:
 | 
					def get_type_str(x: Any) -> str:
 | 
				
			||||||
    if x is None:
 | 
					    if x is None:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,17 +3,17 @@ import re
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import sourcemap
 | 
					import sourcemap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Dict, List, Text
 | 
					from typing import Dict, List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SourceMap:
 | 
					class SourceMap:
 | 
				
			||||||
    '''Map (line, column) pairs from generated to source file.'''
 | 
					    '''Map (line, column) pairs from generated to source file.'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, sourcemap_dirs: List[Text]) -> None:
 | 
					    def __init__(self, sourcemap_dirs: List[str]) -> None:
 | 
				
			||||||
        self._dirs = sourcemap_dirs
 | 
					        self._dirs = sourcemap_dirs
 | 
				
			||||||
        self._indices = {}  # type: Dict[Text, sourcemap.SourceMapDecoder]
 | 
					        self._indices = {}  # type: Dict[str, sourcemap.SourceMapDecoder]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _index_for(self, minified_src: Text) -> sourcemap.SourceMapDecoder:
 | 
					    def _index_for(self, minified_src: str) -> sourcemap.SourceMapDecoder:
 | 
				
			||||||
        '''Return the source map index for minified_src, loading it if not
 | 
					        '''Return the source map index for minified_src, loading it if not
 | 
				
			||||||
           already loaded.'''
 | 
					           already loaded.'''
 | 
				
			||||||
        if minified_src not in self._indices:
 | 
					        if minified_src not in self._indices:
 | 
				
			||||||
@@ -26,8 +26,8 @@ class SourceMap:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return self._indices[minified_src]
 | 
					        return self._indices[minified_src]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def annotate_stacktrace(self, stacktrace: Text) -> Text:
 | 
					    def annotate_stacktrace(self, stacktrace: str) -> str:
 | 
				
			||||||
        out = ''  # type: Text
 | 
					        out = ''  # type: str
 | 
				
			||||||
        for ln in stacktrace.splitlines():
 | 
					        for ln in stacktrace.splitlines():
 | 
				
			||||||
            out += ln + '\n'
 | 
					            out += ln + '\n'
 | 
				
			||||||
            match = re.search(r'/static/(?:webpack-bundles|min)/(.+)(\.[\.0-9a-f]+)\.js:(\d+):(\d+)', ln)
 | 
					            match = re.search(r'/static/(?:webpack-bundles|min)/(.+)(\.[\.0-9a-f]+)\.js:(\d+):(\d+)', ln)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
from typing import Optional, Text, Dict, Any
 | 
					from typing import Optional, Dict, Any
 | 
				
			||||||
from pyoembed import oEmbed, PyOembedException
 | 
					from pyoembed import oEmbed, PyOembedException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_oembed_data(url: Text,
 | 
					def get_oembed_data(url: str,
 | 
				
			||||||
                    maxwidth: Optional[int]=640,
 | 
					                    maxwidth: Optional[int]=640,
 | 
				
			||||||
                    maxheight: Optional[int]=480) -> Optional[Dict[Any, Any]]:
 | 
					                    maxheight: Optional[int]=480) -> Optional[Dict[Any, Any]]:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
from typing import Any, Text
 | 
					from typing import Any
 | 
				
			||||||
from bs4 import BeautifulSoup
 | 
					from bs4 import BeautifulSoup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseParser:
 | 
					class BaseParser:
 | 
				
			||||||
    def __init__(self, html_source: Text) -> None:
 | 
					    def __init__(self, html_source: str) -> None:
 | 
				
			||||||
        self._soup = BeautifulSoup(html_source, "lxml")
 | 
					        self._soup = BeautifulSoup(html_source, "lxml")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def extract_data(self) -> Any:
 | 
					    def extract_data(self) -> Any:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,15 @@
 | 
				
			|||||||
from typing import Dict, Optional, Text
 | 
					from typing import Dict, Optional
 | 
				
			||||||
from zerver.lib.url_preview.parsers.base import BaseParser
 | 
					from zerver.lib.url_preview.parsers.base import BaseParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GenericParser(BaseParser):
 | 
					class GenericParser(BaseParser):
 | 
				
			||||||
    def extract_data(self) -> Dict[str, Optional[Text]]:
 | 
					    def extract_data(self) -> Dict[str, Optional[str]]:
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'title': self._get_title(),
 | 
					            'title': self._get_title(),
 | 
				
			||||||
            'description': self._get_description(),
 | 
					            'description': self._get_description(),
 | 
				
			||||||
            'image': self._get_image()}
 | 
					            'image': self._get_image()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_title(self) -> Optional[Text]:
 | 
					    def _get_title(self) -> Optional[str]:
 | 
				
			||||||
        soup = self._soup
 | 
					        soup = self._soup
 | 
				
			||||||
        if (soup.title and soup.title.text != ''):
 | 
					        if (soup.title and soup.title.text != ''):
 | 
				
			||||||
            return soup.title.text
 | 
					            return soup.title.text
 | 
				
			||||||
@@ -17,7 +17,7 @@ class GenericParser(BaseParser):
 | 
				
			|||||||
            return soup.h1.text
 | 
					            return soup.h1.text
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_description(self) -> Optional[Text]:
 | 
					    def _get_description(self) -> Optional[str]:
 | 
				
			||||||
        soup = self._soup
 | 
					        soup = self._soup
 | 
				
			||||||
        meta_description = soup.find('meta', attrs={'name': 'description'})
 | 
					        meta_description = soup.find('meta', attrs={'name': 'description'})
 | 
				
			||||||
        if (meta_description and meta_description['content'] != ''):
 | 
					        if (meta_description and meta_description['content'] != ''):
 | 
				
			||||||
@@ -32,7 +32,7 @@ class GenericParser(BaseParser):
 | 
				
			|||||||
            return first_p.string
 | 
					            return first_p.string
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_image(self) -> Optional[Text]:
 | 
					    def _get_image(self) -> Optional[str]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Finding a first image after the h1 header.
 | 
					        Finding a first image after the h1 header.
 | 
				
			||||||
        Presumably it will be the main image.
 | 
					        Presumably it will be the main image.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
from typing import Dict, Text
 | 
					from typing import Dict
 | 
				
			||||||
from .base import BaseParser
 | 
					from .base import BaseParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OpenGraphParser(BaseParser):
 | 
					class OpenGraphParser(BaseParser):
 | 
				
			||||||
    def extract_data(self) -> Dict[str, Text]:
 | 
					    def extract_data(self) -> Dict[str, str]:
 | 
				
			||||||
        meta = self._soup.findAll('meta')
 | 
					        meta = self._soup.findAll('meta')
 | 
				
			||||||
        content = {}
 | 
					        content = {}
 | 
				
			||||||
        for tag in meta:
 | 
					        for tag in meta:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import traceback
 | 
					import traceback
 | 
				
			||||||
from typing import Any, Optional, Text, Dict
 | 
					from typing import Any, Optional, Dict
 | 
				
			||||||
from typing.re import Match
 | 
					from typing.re import Match
 | 
				
			||||||
import requests
 | 
					import requests
 | 
				
			||||||
from zerver.lib.cache import cache_with_key, get_cache_with_key
 | 
					from zerver.lib.cache import cache_with_key, get_cache_with_key
 | 
				
			||||||
@@ -20,16 +20,16 @@ link_regex = re.compile(
 | 
				
			|||||||
    r'(?:/?|[/?]\S+)$', re.IGNORECASE)
 | 
					    r'(?:/?|[/?]\S+)$', re.IGNORECASE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_link(url: Text) -> Match[Text]:
 | 
					def is_link(url: str) -> Match[str]:
 | 
				
			||||||
    return link_regex.match(smart_text(url))
 | 
					    return link_regex.match(smart_text(url))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_key_func(url: Text) -> Text:
 | 
					def cache_key_func(url: str) -> str:
 | 
				
			||||||
    return url
 | 
					    return url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@cache_with_key(cache_key_func, cache_name=CACHE_NAME, with_statsd_key="urlpreview_data")
 | 
					@cache_with_key(cache_key_func, cache_name=CACHE_NAME, with_statsd_key="urlpreview_data")
 | 
				
			||||||
def get_link_embed_data(url: Text,
 | 
					def get_link_embed_data(url: str,
 | 
				
			||||||
                        maxwidth: Optional[int]=640,
 | 
					                        maxwidth: Optional[int]=640,
 | 
				
			||||||
                        maxheight: Optional[int]=480) -> Optional[Dict[Any, Any]]:
 | 
					                        maxheight: Optional[int]=480) -> Optional[Dict[Any, Any]]:
 | 
				
			||||||
    if not is_link(url):
 | 
					    if not is_link(url):
 | 
				
			||||||
@@ -59,5 +59,5 @@ def get_link_embed_data(url: Text,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@get_cache_with_key(cache_key_func, cache_name=CACHE_NAME)
 | 
					@get_cache_with_key(cache_key_func, cache_name=CACHE_NAME)
 | 
				
			||||||
def link_embed_data_from_cache(url: Text, maxwidth: Optional[int]=640, maxheight: Optional[int]=480) -> Any:
 | 
					def link_embed_data_from_cache(url: str, maxwidth: Optional[int]=640, maxheight: Optional[int]=480) -> Any:
 | 
				
			||||||
    return
 | 
					    return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ from django.db import transaction
 | 
				
			|||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
from zerver.lib.exceptions import JsonableError
 | 
					from zerver.lib.exceptions import JsonableError
 | 
				
			||||||
from zerver.models import UserProfile, Realm, UserGroupMembership, UserGroup
 | 
					from zerver.models import UserProfile, Realm, UserGroupMembership, UserGroup
 | 
				
			||||||
from typing import Dict, Iterable, List, Text, Tuple, Any
 | 
					from typing import Dict, Iterable, List, Tuple, Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def access_user_group_by_id(user_group_id: int, user_profile: UserProfile) -> UserGroup:
 | 
					def access_user_group_by_id(user_group_id: int, user_profile: UserProfile) -> UserGroup:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
@@ -22,7 +22,7 @@ def user_groups_in_realm(realm: Realm) -> List[UserGroup]:
 | 
				
			|||||||
    user_groups = UserGroup.objects.filter(realm=realm)
 | 
					    user_groups = UserGroup.objects.filter(realm=realm)
 | 
				
			||||||
    return list(user_groups)
 | 
					    return list(user_groups)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_groups_in_realm_serialized(realm: Realm) -> List[Dict[Text, Any]]:
 | 
					def user_groups_in_realm_serialized(realm: Realm) -> List[Dict[str, Any]]:
 | 
				
			||||||
    """This function is used in do_events_register code path so this code
 | 
					    """This function is used in do_events_register code path so this code
 | 
				
			||||||
    should be performant.  We need to do 2 database queries because
 | 
					    should be performant.  We need to do 2 database queries because
 | 
				
			||||||
    Django's ORM doesn't properly support the left join between
 | 
					    Django's ORM doesn't properly support the left join between
 | 
				
			||||||
@@ -67,8 +67,8 @@ def check_remove_user_from_user_group(user_profile: UserProfile, user_group: Use
 | 
				
			|||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_user_group(name: Text, members: List[UserProfile], realm: Realm,
 | 
					def create_user_group(name: str, members: List[UserProfile], realm: Realm,
 | 
				
			||||||
                      description: Text='') -> UserGroup:
 | 
					                      description: str='') -> UserGroup:
 | 
				
			||||||
    with transaction.atomic():
 | 
					    with transaction.atomic():
 | 
				
			||||||
        user_group = UserGroup.objects.create(name=name, realm=realm,
 | 
					        user_group = UserGroup.objects.create(name=name, realm=realm,
 | 
				
			||||||
                                              description=description)
 | 
					                                              description=description)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Optional, Any, Dict, List, Text, Tuple
 | 
					from typing import Optional, Any, Dict, List, Tuple
 | 
				
			||||||
from collections import defaultdict
 | 
					from collections import defaultdict
 | 
				
			||||||
SUBJECT_WITH_BRANCH_TEMPLATE = '{repo} / {branch}'
 | 
					SUBJECT_WITH_BRANCH_TEMPLATE = '{repo} / {branch}'
 | 
				
			||||||
SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE = '{repo} / {type} #{id} {title}'
 | 
					SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE = '{repo} / {type} #{id} {title}'
 | 
				
			||||||
@@ -49,10 +49,10 @@ TAG_WITH_URL_TEMPLATE = "[{tag_name}]({tag_url})"
 | 
				
			|||||||
TAG_WITHOUT_URL_TEMPLATE = "{tag_name}"
 | 
					TAG_WITHOUT_URL_TEMPLATE = "{tag_name}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_push_commits_event_message(user_name: Text, compare_url: Optional[Text],
 | 
					def get_push_commits_event_message(user_name: str, compare_url: Optional[str],
 | 
				
			||||||
                                   branch_name: Text, commits_data: List[Dict[str, Any]],
 | 
					                                   branch_name: str, commits_data: List[Dict[str, Any]],
 | 
				
			||||||
                                   is_truncated: Optional[bool]=False,
 | 
					                                   is_truncated: Optional[bool]=False,
 | 
				
			||||||
                                   deleted: Optional[bool]=False) -> Text:
 | 
					                                   deleted: Optional[bool]=False) -> str:
 | 
				
			||||||
    if not commits_data and deleted:
 | 
					    if not commits_data and deleted:
 | 
				
			||||||
        return PUSH_DELETE_BRANCH_MESSAGE_TEMPLATE.format(
 | 
					        return PUSH_DELETE_BRANCH_MESSAGE_TEMPLATE.format(
 | 
				
			||||||
            user_name=user_name,
 | 
					            user_name=user_name,
 | 
				
			||||||
@@ -99,7 +99,7 @@ def get_push_commits_event_message(user_name: Text, compare_url: Optional[Text],
 | 
				
			|||||||
            commits_data=get_commits_content(commits_data, is_truncated),
 | 
					            commits_data=get_commits_content(commits_data, is_truncated),
 | 
				
			||||||
        ).rstrip()
 | 
					        ).rstrip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_force_push_commits_event_message(user_name: Text, url: Text, branch_name: Text, head: Text) -> Text:
 | 
					def get_force_push_commits_event_message(user_name: str, url: str, branch_name: str, head: str) -> str:
 | 
				
			||||||
    return FORCE_PUSH_COMMITS_MESSAGE_TEMPLATE.format(
 | 
					    return FORCE_PUSH_COMMITS_MESSAGE_TEMPLATE.format(
 | 
				
			||||||
        user_name=user_name,
 | 
					        user_name=user_name,
 | 
				
			||||||
        url=url,
 | 
					        url=url,
 | 
				
			||||||
@@ -107,23 +107,23 @@ def get_force_push_commits_event_message(user_name: Text, url: Text, branch_name
 | 
				
			|||||||
        head=head
 | 
					        head=head
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_create_branch_event_message(user_name: Text, url: Text, branch_name: Text) -> Text:
 | 
					def get_create_branch_event_message(user_name: str, url: str, branch_name: str) -> str:
 | 
				
			||||||
    return CREATE_BRANCH_MESSAGE_TEMPLATE.format(
 | 
					    return CREATE_BRANCH_MESSAGE_TEMPLATE.format(
 | 
				
			||||||
        user_name=user_name,
 | 
					        user_name=user_name,
 | 
				
			||||||
        url=url,
 | 
					        url=url,
 | 
				
			||||||
        branch_name=branch_name,
 | 
					        branch_name=branch_name,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_remove_branch_event_message(user_name: Text, branch_name: Text) -> Text:
 | 
					def get_remove_branch_event_message(user_name: str, branch_name: str) -> str:
 | 
				
			||||||
    return REMOVE_BRANCH_MESSAGE_TEMPLATE.format(
 | 
					    return REMOVE_BRANCH_MESSAGE_TEMPLATE.format(
 | 
				
			||||||
        user_name=user_name,
 | 
					        user_name=user_name,
 | 
				
			||||||
        branch_name=branch_name,
 | 
					        branch_name=branch_name,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_pull_request_event_message(user_name: Text, action: Text, url: Text, number: Optional[int]=None,
 | 
					def get_pull_request_event_message(user_name: str, action: str, url: str, number: Optional[int]=None,
 | 
				
			||||||
                                   target_branch: Optional[Text]=None, base_branch: Optional[Text]=None,
 | 
					                                   target_branch: Optional[str]=None, base_branch: Optional[str]=None,
 | 
				
			||||||
                                   message: Optional[Text]=None, assignee: Optional[Text]=None,
 | 
					                                   message: Optional[str]=None, assignee: Optional[str]=None,
 | 
				
			||||||
                                   type: Optional[Text]='PR') -> Text:
 | 
					                                   type: Optional[str]='PR') -> str:
 | 
				
			||||||
    main_message = PULL_REQUEST_OR_ISSUE_MESSAGE_TEMPLATE.format(
 | 
					    main_message = PULL_REQUEST_OR_ISSUE_MESSAGE_TEMPLATE.format(
 | 
				
			||||||
        user_name=user_name,
 | 
					        user_name=user_name,
 | 
				
			||||||
        action=action,
 | 
					        action=action,
 | 
				
			||||||
@@ -143,18 +143,18 @@ def get_pull_request_event_message(user_name: Text, action: Text, url: Text, num
 | 
				
			|||||||
        main_message += '\n' + CONTENT_MESSAGE_TEMPLATE.format(message=message)
 | 
					        main_message += '\n' + CONTENT_MESSAGE_TEMPLATE.format(message=message)
 | 
				
			||||||
    return main_message.rstrip()
 | 
					    return main_message.rstrip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_setup_webhook_message(integration: Text, user_name: Optional[Text]=None) -> Text:
 | 
					def get_setup_webhook_message(integration: str, user_name: Optional[str]=None) -> str:
 | 
				
			||||||
    content = SETUP_MESSAGE_TEMPLATE.format(integration=integration)
 | 
					    content = SETUP_MESSAGE_TEMPLATE.format(integration=integration)
 | 
				
			||||||
    if user_name:
 | 
					    if user_name:
 | 
				
			||||||
        content += SETUP_MESSAGE_USER_PART.format(user_name=user_name)
 | 
					        content += SETUP_MESSAGE_USER_PART.format(user_name=user_name)
 | 
				
			||||||
    return content
 | 
					    return content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_issue_event_message(user_name: Text,
 | 
					def get_issue_event_message(user_name: str,
 | 
				
			||||||
                            action: Text,
 | 
					                            action: str,
 | 
				
			||||||
                            url: Text,
 | 
					                            url: str,
 | 
				
			||||||
                            number: Optional[int]=None,
 | 
					                            number: Optional[int]=None,
 | 
				
			||||||
                            message: Optional[Text]=None,
 | 
					                            message: Optional[str]=None,
 | 
				
			||||||
                            assignee: Optional[Text]=None) -> Text:
 | 
					                            assignee: Optional[str]=None) -> str:
 | 
				
			||||||
    return get_pull_request_event_message(
 | 
					    return get_pull_request_event_message(
 | 
				
			||||||
        user_name,
 | 
					        user_name,
 | 
				
			||||||
        action,
 | 
					        action,
 | 
				
			||||||
@@ -165,10 +165,10 @@ def get_issue_event_message(user_name: Text,
 | 
				
			|||||||
        type='Issue'
 | 
					        type='Issue'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_push_tag_event_message(user_name: Text,
 | 
					def get_push_tag_event_message(user_name: str,
 | 
				
			||||||
                               tag_name: Text,
 | 
					                               tag_name: str,
 | 
				
			||||||
                               tag_url: Optional[Text]=None,
 | 
					                               tag_url: Optional[str]=None,
 | 
				
			||||||
                               action: Optional[Text]='pushed') -> Text:
 | 
					                               action: Optional[str]='pushed') -> str:
 | 
				
			||||||
    if tag_url:
 | 
					    if tag_url:
 | 
				
			||||||
        tag_part = TAG_WITH_URL_TEMPLATE.format(tag_name=tag_name, tag_url=tag_url)
 | 
					        tag_part = TAG_WITH_URL_TEMPLATE.format(tag_name=tag_name, tag_url=tag_url)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
@@ -179,11 +179,11 @@ def get_push_tag_event_message(user_name: Text,
 | 
				
			|||||||
        tag=tag_part
 | 
					        tag=tag_part
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_commits_comment_action_message(user_name: Text,
 | 
					def get_commits_comment_action_message(user_name: str,
 | 
				
			||||||
                                       action: Text,
 | 
					                                       action: str,
 | 
				
			||||||
                                       commit_url: Text,
 | 
					                                       commit_url: str,
 | 
				
			||||||
                                       sha: Text,
 | 
					                                       sha: str,
 | 
				
			||||||
                                       message: Optional[Text]=None) -> Text:
 | 
					                                       message: Optional[str]=None) -> str:
 | 
				
			||||||
    content = COMMITS_COMMENT_MESSAGE_TEMPLATE.format(
 | 
					    content = COMMITS_COMMENT_MESSAGE_TEMPLATE.format(
 | 
				
			||||||
        user_name=user_name,
 | 
					        user_name=user_name,
 | 
				
			||||||
        action=action,
 | 
					        action=action,
 | 
				
			||||||
@@ -196,7 +196,7 @@ def get_commits_comment_action_message(user_name: Text,
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
    return content
 | 
					    return content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_commits_content(commits_data: List[Dict[str, Any]], is_truncated: Optional[bool]=False) -> Text:
 | 
					def get_commits_content(commits_data: List[Dict[str, Any]], is_truncated: Optional[bool]=False) -> str:
 | 
				
			||||||
    commits_content = ''
 | 
					    commits_content = ''
 | 
				
			||||||
    for commit in commits_data[:COMMITS_LIMIT]:
 | 
					    for commit in commits_data[:COMMITS_LIMIT]:
 | 
				
			||||||
        commits_content += COMMIT_ROW_TEMPLATE.format(
 | 
					        commits_content += COMMIT_ROW_TEMPLATE.format(
 | 
				
			||||||
@@ -215,7 +215,7 @@ def get_commits_content(commits_data: List[Dict[str, Any]], is_truncated: Option
 | 
				
			|||||||
        ).replace('  ', ' ')
 | 
					        ).replace('  ', ' ')
 | 
				
			||||||
    return commits_content.rstrip()
 | 
					    return commits_content.rstrip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_short_sha(sha: Text) -> Text:
 | 
					def get_short_sha(sha: str) -> str:
 | 
				
			||||||
    return sha[:7]
 | 
					    return sha[:7]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_all_committers(commits_data: List[Dict[str, Any]]) -> List[Tuple[str, int]]:
 | 
					def get_all_committers(commits_data: List[Dict[str, Any]]) -> List[Tuple[str, int]]:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
from typing import Text
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,9 +7,9 @@ class BadEventQueueIdError(JsonableError):
 | 
				
			|||||||
    code = ErrorCode.BAD_EVENT_QUEUE_ID
 | 
					    code = ErrorCode.BAD_EVENT_QUEUE_ID
 | 
				
			||||||
    data_fields = ['queue_id']
 | 
					    data_fields = ['queue_id']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, queue_id: Text) -> None:
 | 
					    def __init__(self, queue_id: str) -> None:
 | 
				
			||||||
        self.queue_id = queue_id  # type: Text
 | 
					        self.queue_id = queue_id  # type: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def msg_format() -> Text:
 | 
					    def msg_format() -> str:
 | 
				
			||||||
        return _("Bad event queue id: {queue_id}")
 | 
					        return _("Bad event queue id: {queue_id}")
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user