class JID:
    localpart: Optional[str] = None
    domain: Optional[str] = None
    resource: Optional[str] = None

    def __init__(self,
                 localpart: Optional[str] = None,
                 domain: Optional[str] = None,
                 resource: Optional[str] = None):

        if localpart is not None:
            localpart = validate_localpart(localpart)
            object.__setattr__(self, "localpart", localpart)

        domain = validate_domainpart(domain)
        object.__setattr__(self, "domain", domain)

        if resource is not None:
            resource = validate_resourcepart(resource)
            object.__setattr__(self, "resource", resource)

    @classmethod
    @functools.lru_cache(maxsize=None)
    def from_string(cls, jid_string: str, force_bare: bool = False) -> JID:
        # https://tools.ietf.org/html/rfc7622#section-3.2

        # Remove any portion from the first '/' character to the end of the
        # string (if there is a '/' character present).

        # Remove any portion from the beginning of the string to the first
        # '@' character (if there is an '@' character present).

        if jid_string.find('/') != -1:
            rest, resourcepart = jid_string.split('/', 1)
        else:
            rest, resourcepart = jid_string, None

        if rest.find('@') != -1:
            localpart, domainpart = rest.split('@', 1)
        else:
            localpart, domainpart = None, rest

        if force_bare:
            if localpart is None:
                raise LocalpartByteLimit
            resourcepart = None

        return cls(localpart=localpart,
                   domain=domainpart,
                   resource=resourcepart)

    @classmethod
    @functools.lru_cache(maxsize=None)
    def from_user_input(cls, user_input: str, escaped: bool = False) -> JID:
        # Use this if we want JIDs to be escaped according to XEP-0106
        # The standard JID parsing cannot be applied because user_input is
        # not a valid JID.

        # Only user_input which after escaping result in a bare JID can be
        # successfully parsed.

        # The assumpution is user_input is a bare JID so we start with an
        # rsplit on @ because we assume there is no resource, so the char @
        # in the localpart can later be escaped.

        if escaped:
            # for convenience
            return cls.from_string(user_input)

        if '@' in user_input:
            localpart, domainpart = user_input.rsplit('@', 1)
            if localpart.startswith(' ') or localpart.endswith(' '):
                raise LocalpartNotAllowedChar

            localpart = escape_localpart(localpart)

        else:
            localpart = None
            domainpart = user_input

        return cls(localpart=localpart,
                   domain=domainpart,
                   resource=None)

    def __str__(self) -> str:
        if self.localpart:
            jid = f'{self.localpart}@{self.domain}'
        else:
            jid = cast(str, self.domain)

        if self.resource is not None:
            return f'{jid}/{self.resource}'
        return jid

    def __hash__(self):
        return hash(str(self))

    def __eq__(self, other: Union[str, JID]) -> bool:
        if isinstance(other, str):
            try:
                return JID.from_string(other) == self
            except Exception:
                return False

        return (self.localpart == other.localpart and
                self.domain == other.domain and
                self.resource == other.resource)

    def __ne__(self, other: Union[str, JID]) -> bool:
        return not self.__eq__(other)

    def domain_to_ascii(self) -> str:
        return idna_encode(self.domain)

    @property
    def bare(self) -> str:
        if self.localpart is not None:
            return f'{self.localpart}@{self.domain}'
        return self.domain

    @property
    def is_bare(self) -> bool:
        return self.resource is None

    def new_as_bare(self) -> JID:
        if self.resource is None:
            return self
        new = asdict(self)
        new.pop('resource')
        return JID(**new)

    def bare_match(self, other: Union[str, JID]) -> bool:
        if isinstance(other, str):
            other = JID.from_string(other)
        return self.bare == other.bare

    @property
    def is_domain(self) -> bool:
        return self.localpart is None and self.resource is None

    @property
    def is_full(self) -> bool:
        return (self.localpart is not None and
                self.domain is not None and
                self.resource is not None)

    def new_with(self, **kwargs: dict[str, str]) -> JID:
        new = asdict(self)
        new.update(kwargs)
        return JID(**new)

    def to_user_string(self, show_punycode: bool = True) -> str:
        domain = self.domain_to_ascii()
        if domain.startswith('xn--') and show_punycode:
            domain_encoded = f' ({domain})'
        else:
            domain_encoded = ''

        if self.localpart is None:
            return f'{self}{domain_encoded}'

        localpart = unescape_localpart(self.localpart)

        if self.resource is None:
            return f'{localpart}@{self.domain}{domain_encoded}'
        return f'{localpart}@{self.domain}/{self.resource}{domain_encoded}'

    def copy(self) -> JID:
        deprecation_warning('copy() is not needed, JID is immutable')
        return self class Iq(Protocol):
    """
    XMPP Iq object - get/set dialog mechanism
    """

    def __init__(self,
                 typ: Optional[str] = None,
                 queryNS: Optional[str] = None,
                 attrs: Optional[dict[str, str]] = None,
                 to: Optional[Union[JID, str]] = None,
                 frm: Optional[Union[JID, str]] = None,
                 payload: Optional[list[Union[Node, str]]] = None,
                 xmlns: str = Namespace.CLIENT,
                 node: Optional[Node] = None):
        """
        You can specify type, query namespace any additional attributes,
        recipient of the iq, sender of the iq, any additional payload (f.e.
        jabber:x:data node) and namespace in one go.

        Alternatively you can pass in the other XML object as the 'node'
        parameted to replicate it as an iq
        """
        Protocol.__init__(self,
                          'iq',
                          to=to,
                          typ=typ,
                          attrs=attrs,
                          frm=frm,
                          xmlns=xmlns,
                          node=node)
        if payload:
            self.setQueryPayload(payload)
        if queryNS:
            self.setQueryNS(queryNS)

    def getQuery(self) -> Optional[Node]:
        """
        Return the IQ's child element if it exists, None otherwise.
        """
        children = self.getChildren()
        if children and self.getType() != 'error' and \
        children[0].getName() != 'error':
            return children[0]
        return None

    def getQueryNS(self) -> Optional[str]:
        """
        Return the namespace of the 'query' child element
        """
        tag = self.getQuery()
        if tag:
            return tag.getNamespace()
        return None

    def getQuerynode(self) -> Optional[str]:
        """
        Return the 'node' attribute value of the 'query' child element
        """
        tag = self.getQuery()
        if tag:
            return tag.getAttr('node')
        return None

    def getQueryChildren(self) -> Optional[list[Node]]:
        """
        Return the 'query' child element child nodes
        """
        tag = self.getQuery()
        if tag:
            return tag.getChildren()
        return None

    def getQueryChild(self, name: Optional[str] = None) -> Optional[Node]:
        """
        Return the 'query' child element with name, or the first element
        which is not an error element
        """
        query = self.getQuery()
        if not query:
            return None
        for node in query.getChildren():
            if name is not None:
                if node.getName() == name:
                    return node
            else:
                if node.getName() != 'error':
                    return node
        return None

    def setQuery(self, name: Optional[str] = None) -> Node:
        """
        Change the name of the query node, creating it if needed. Keep the
        existing name if none is given (use 'query' if it's a creation).
        Return the query node.
        """
        query = self.getQuery()
        if query is None:
            query = self.addChild('query')
        if name is not None:
            query.setName(name)
        return query

    def setQueryNS(self, namespace: str) -> None:
        """
        Set the namespace of the 'query' child element
        """
        self.setQuery().setNamespace(namespace)

    def setQueryPayload(self, payload: list[Union[Node, str]]):
        """
        Set the 'query' child element payload
        """
        self.setQuery().setPayload(payload)

    def setQuerynode(self, node: str) -> None:
        """
        Set the 'node' attribute value of the 'query' child element
        """
        self.setQuery().setAttr('node', node)

    def buildReply(self, typ: str) -> Iq:
        """
        Build and return another Iq object of specified type. The to, from and
        query child node of new Iq are pre-set as reply to this Iq.
        """
        iq = Iq(typ,
                to=self.getFrom(),
                frm=self.getTo(),
                attrs={'id': self.getID()})
        iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
        return iq

    def buildSimpleReply(self, typ: str) -> Iq:
        return Iq(typ,
                  to=self.getFrom(),
                  attrs={'id': self.getID()}) def ascii_upper(s):
def isResultNode(node):
def isErrorNode(node):
def isMucPM(message):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
def deprecation_warning(message):
def validate_localpart(localpart: str) -> str:
def validate_resourcepart(resourcepart: str) -> str:
def validate_domainpart(domainpart: str) -> str:
def idna_encode(domain: str) -> str:
def escape_localpart(localpart: str) -> str:
def unescape_localpart(localpart: str) -> str:
    def __init__(self,
                 localpart: Optional[str] = None,
                 domain: Optional[str] = None,
                 resource: Optional[str] = None):
    def from_string(cls, jid_string: str, force_bare: bool = False) -> JID:
    def from_user_input(cls, user_input: str, escaped: bool = False) -> JID:
    def __str__(self) -> str:
    def __hash__(self):
    def __eq__(self, other: Union[str, JID]) -> bool:
    def __ne__(self, other: Union[str, JID]) -> bool:
    def domain_to_ascii(self) -> str:
    def bare(self) -> str:
    def is_bare(self) -> bool:
    def new_as_bare(self) -> JID:
    def bare_match(self, other: Union[str, JID]) -> bool:
    def is_domain(self) -> bool:
    def is_full(self) -> bool:
    def new_with(self, **kwargs: dict[str, str]) -> JID:
    def to_user_string(self, show_punycode: bool = True) -> str:
    def copy(self) -> JID:
    def __init__(self, node):
    def get_condition(self) -> Optional[str]:
    def get_text(self, pref_lang: Optional[str] = None) -> str:
    def __init__(self,
                 name=None,
                 to=None,
                 typ=None,
                 frm=None,
                 attrs=None,
                 payload=None,
                 timestamp=None,
                 xmlns=None,
                 node=None):
    def isError(self):
    def isResult(self):
    def getTo(self):
    def getFrom(self):
    def getTimestamp(self):
    def getTimestamp2(self):
    def getJid(self):
    def getID(self) -> Optional[str]:
    def setTo(self, val: Union[str, JID]):
    def getType(self) -> Optional[str]:
    def setFrom(self, val: Union[str, JID]):
    def setType(self, val: str):
    def setID(self, val: str):
    def getError(self) -> Optional[str]:
    def getAppError(self):
    def getAppErrorNamespace(self):
    def getErrorMsg(self):
    def getErrorCode(self):
    def getErrorType(self):
    def getStatusConditions(self, as_code=False):
    def setError(self, error, code=None):
    def setTimestamp(self, val=None):
    def getProperties(self):
    def getTag(self,
               name: str,
               attrs: Optional[dict[str, Any]] = None,
               namespace: Optional[str] = None,
               protocol: bool = False) -> Optional[Node]:
    def __setitem__(self, item: str, val: Union[str, JID]):
    def __init__(self,
                 to=None,
                 body=None,
                 xhtml=None,
                 typ=None,
                 subject=None,
                 attrs=None,
                 frm=None,
                 payload=None,
                 timestamp=None,
                 xmlns=Namespace.CLIENT,
                 node=None):
    def getBody(self):
    def getXHTML(self):
    def getSubject(self):
    def getThread(self):
    def getOriginID(self):
    def getStanzaIDAttrs(self):
    def setBody(self, val):
    def setXHTML(self, body, add=False):
    def setSubject(self, val):
    def setThread(self, val):
    def setOriginID(self, val):
    def buildReply(self, text=None):
    def getStatusCode(self):
    def setMarker(self, type_, id_):
    def setMarkable(self):
    def setReceiptRequest(self):
    def setReceiptReceived(self, id_):
    def setOOB(self, url, desc=None):
    def setCorrection(self, id_):
    def setAttention(self):
    def setHint(self, hint):
    def __init__(self,
                 to=None,
                 typ=None,
                 priority=None,
                 show=None,
                 status=None,
                 attrs=None,
                 frm=None,
                 timestamp=None,
                 payload=None,
                 xmlns=Namespace.CLIENT,
                 node=None):
    def getPriority(self):
    def getShow(self):
    def getStatus(self):
    def setPriority(self, val):
    def setShow(self, val):
    def setStatus(self, val):
    def _muc_getItemAttr(self, tag, attr):
    def _muc_getSubTagDataAttr(self, tag, attr):
    def getRole(self):
    def getAffiliation(self):
    def getNewNick(self):
    def getJid(self):
    def getReason(self):
    def getActor(self):
    def getStatusCode(self):
    def __init__(self,
                 typ: Optional[str] = None,
                 queryNS: Optional[str] = None,
                 attrs: Optional[dict[str, str]] = None,
                 to: Optional[Union[JID, str]] = None,
                 frm: Optional[Union[JID, str]] = None,
                 payload: Optional[list[Union[Node, str]]] = None,
                 xmlns: str = Namespace.CLIENT,
                 node: Optional[Node] = None):
    def getQuery(self) -> Optional[Node]:
    def getQueryNS(self) -> Optional[str]:
    def getQuerynode(self) -> Optional[str]:
    def getQueryChildren(self) -> Optional[list[Node]]:
    def getQueryChild(self, name: Optional[str] = None) -> Optional[Node]:
    def setQuery(self, name: Optional[str] = None) -> Node:
    def setQueryNS(self, namespace: str) -> None:
    def setQueryPayload(self, payload: list[Union[Node, str]]):
    def setQuerynode(self, node: str) -> None:
    def buildReply(self, typ: str) -> Iq:
    def buildSimpleReply(self, typ: str) -> Iq:
    def __init__(self, nsp=Namespace.HASHES):
    def calculateHash(self, algo, file_string):
    def addHash(self, hash_, algo):
    def __init__(self, nsp=Namespace.HASHES_2):
    def calculateHash(self, algo, file_string):
    def addHash(self, hash_, algo):
    def __init__(self, resource: Optional[str]):
    def __init__(self):
    def __init__(self):
    def __init__(self, domain: str, lang: Optional[str] = None):
    def __init__(self, domain: str, lang: Optional[str] = None):
    def __init__(self):
    def __init__(self, node: Node):
    def has_starttls(self):
    def has_sasl(self):
    def get_mechs(self) -> set[str]:
    def get_domain_based_name(self):
    def has_bind(self):
    def session_required(self):
    def has_sm(self):
    def has_roster_version(self):
    def has_register(self):
    def has_anonymous(self):
    def __init__(self, name, code=None, typ=None, text=None):
    def __init__(self, node, error, reply=1):
    def __init__(self,
                 name=None,
                 value=None,
                 typ=None,
                 required=0,
                 desc=None,
                 options=None,
                 node=None):
    def setRequired(self, req=1):
    def isRequired(self):
    def setDesc(self, desc):
    def getDesc(self):
    def setValue(self, val):
    def getValue(self):
    def setValues(self, lst):
    def addValue(self, val):
    def getValues(self):
    def getOptions(self):
    def setOptions(self, lst):
    def addOption(self, opt):
    def getType(self):
    def setType(self, val):
    def getVar(self):
    def setVar(self, val):
    def __init__(self, typ=None, data=None, title=None, node=None):
    def getType(self):
    def setType(self, typ):
    def getTitle(self):
    def setTitle(self, text):
    def getInstructions(self):
    def setInstructions(self, text):
    def addInstructions(self, text):
    def getField(self, name):
    def setField(self, name):
    def asDict(self):
    def __getitem__(self, name):
    def __setitem__(self, name, val):
SASL_AUTH_MECHS = [
    'SCRAM-SHA-256-PLUS',
    'SCRAM-SHA-256',
    'SCRAM-SHA-1-PLUS',
    'SCRAM-SHA-1',
    'GSSAPI',
    'PLAIN',
    'EXTERNAL',
    'ANONYMOUS',
]
SASL_ERROR_CONDITIONS = [
    'aborted',
    'account-disabled',
    'credentials-expired',
    'encryption-required',
    'incorrect-encoding',
    'invalid-authzid',
    'invalid-mechanism',
    'mechanism-too-weak',
    'malformed-request',
    'not-authorized',
    'temporary-auth-failure',
]
ERRORS = {
    'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['',
        '',
        'The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],
    'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['',
        '',
        'The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data.'],
    'urn:ietf:params:xml:ns:xmpp-sasl invalid-authzid': ['',
        '',
        'The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data.'],
    'urn:ietf:params:xml:ns:xmpp-sasl invalid-mechanism': ['',
        '',
        'The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element.'],
    'urn:ietf:params:xml:ns:xmpp-sasl mechanism-too-weak': ['',
        '',
        'The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data.'],
    'urn:ietf:params:xml:ns:xmpp-sasl not-authorized': ['',
        '',
        'The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data.'],
    'urn:ietf:params:xml:ns:xmpp-sasl temporary-auth-failure': ['',
        '',
        'The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas bad-request': ['400',
        'modify',
        'The sender has sent XML that is malformed or that cannot be processed.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas conflict': ['409',
        'cancel',
        'Access cannot be granted because an existing resource or session exists with the same name or address.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas feature-not-implemented': ['501',
        'cancel',
        'The feature requested is not implemented by the recipient or server and therefore cannot be processed.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas forbidden': ['403',
        'auth',
        'The requesting entity does not possess the required permissions to perform the action.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas gone': ['302',
        'modify',
        'The recipient or server can no longer be contacted at this address.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas internal-server-error': ['500',
        'wait',
        'The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas item-not-found': ['404',
        'cancel',
        'The addressed JID or item requested cannot be found.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas jid-malformed': ['400',
        'modify',
        "The value of the        'to' attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme."],
    'urn:ietf:params:xml:ns:xmpp-stanzas not-acceptable': ['406',
        'cancel',
        'The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas not-allowed': ['405',
        'cancel',
        'The recipient or server does not allow any entity to perform the action.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas not-authorized': ['401',
        'auth',
        'The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas payment-required': ['402',
        'auth',
        'The requesting entity is not authorized to access the requested service because payment is required.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas recipient-unavailable': ['404',
        'wait',
        'The intended recipient is temporarily unavailable.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas redirect': ['302',
        'modify',
        'The recipient or server is redirecting requests for this information to another entity.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas registration-required': ['407',
        'auth',
        'The requesting entity is not authorized to access the requested service because registration is required.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas remote-server-not-found': ['404',
        'cancel',
        'A remote server or service specified as part or all of the JID of the intended recipient does not exist.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas remote-server-timeout': ['504',
        'wait',
        'A remote server or service specified as part or all of the JID of the intended recipient could not be contacted within a reasonable amount of time.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas resource-constraint': ['500',
        'wait',
        'The server or recipient lacks the system resources necessary to service the request.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas service-unavailable': ['503',
        'cancel',
        'The server or recipient does not currently provide the requested service.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas subscription-required': ['407',
        'auth',
        'The requesting entity is not authorized to access the requested service because a subscription is required.'],
    'urn:ietf:params:xml:ns:xmpp-stanzas undefined-condition': ['500',
        '',
        'Undefined Condition'],
    'urn:ietf:params:xml:ns:xmpp-stanzas unexpected-request': ['400',
        'wait',
        'The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).'],
    'urn:ietf:params:xml:ns:xmpp-streams bad-format': ['',
        '',
        'The entity has sent XML that cannot be processed.'],
    'urn:ietf:params:xml:ns:xmpp-streams bad-namespace-prefix': ['',
        '',
        'The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.'],
    'urn:ietf:params:xml:ns:xmpp-streams conflict': ['',
        '',
        'The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.'],
    'urn:ietf:params:xml:ns:xmpp-streams connection-timeout': ['',
        '',
        'The entity has not generated any traffic over the stream for some period of time.'],
    'urn:ietf:params:xml:ns:xmpp-streams host-gone': ['',
        '',
        "The value of the        'to' attribute provided by the initiating entity in the stream header corresponds to a hostname that is no longer hosted by the server."],
    'urn:ietf:params:xml:ns:xmpp-streams host-unknown': ['',
        '',
        "The value of the        'to' attribute provided by the initiating entity in the stream header does not correspond to a hostname that is hosted by the server."],
    'urn:ietf:params:xml:ns:xmpp-streams improper-addressing': ['',
        '',
        "A stanza sent between two servers lacks a        'to' or 'from' attribute (or the attribute has no value)."],
    'urn:ietf:params:xml:ns:xmpp-streams internal-server-error': ['',
        '',
        'The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream.'],
    'urn:ietf:params:xml:ns:xmpp-streams invalid-from': ['cancel',
        '',
        "The JID or hostname provided in a        'from' address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization."],
    'urn:ietf:params:xml:ns:xmpp-streams invalid-id': ['',
        '',
        'The stream ID or dialback ID is invalid or does not match an ID previously provided.'],
    'urn:ietf:params:xml:ns:xmpp-streams invalid-namespace': ['',
        '',
        'The streams namespace name is something other than        "http://etherx.jabber.org/streams" or the dialback namespace name is something other than "jabber:server:dialback".'],
    'urn:ietf:params:xml:ns:xmpp-streams invalid-xml': ['',
        '',
        'The entity has sent invalid XML over the stream to a server that performs validation.'],
    'urn:ietf:params:xml:ns:xmpp-streams not-authorized': ['',
        '',
        'The entity has attempted to send data before the stream has been authenticated, or otherwise is not authorized to perform an action related to stream negotiation.'],
    'urn:ietf:params:xml:ns:xmpp-streams policy-violation': ['',
        '',
        'The entity has violated some local service policy.'],
    'urn:ietf:params:xml:ns:xmpp-streams remote-connection-failed': ['',
        '',
        'The server is unable to properly connect to a remote resource that is required for authentication or authorization.'],
    'urn:ietf:params:xml:ns:xmpp-streams resource-constraint': ['',
        '',
        'The server lacks the system resources necessary to service the stream.'],
    'urn:ietf:params:xml:ns:xmpp-streams restricted-xml': ['',
        '',
        'The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character.'],
    'urn:ietf:params:xml:ns:xmpp-streams see-other-host': ['',
        '',
        'The server will not provide service to the initiating entity but is redirecting traffic to another host.'],
    'urn:ietf:params:xml:ns:xmpp-streams system-shutdown': ['',
        '',
        'The server is being shut down and all active streams are being closed.'],
    'urn:ietf:params:xml:ns:xmpp-streams undefined-condition': ['',
        '',
        'The error condition is not one of those defined by the other conditions in this list.'],
    'urn:ietf:params:xml:ns:xmpp-streams unsupported-encoding': ['',
        '',
        'The initiating entity has encoded the stream in an encoding that is not supported by the server.'],
    'urn:ietf:params:xml:ns:xmpp-streams unsupported-stanza-type': ['',
        '',
        'The initiating entity has sent a first-level child of the stream that is not supported by the server.'],
    'urn:ietf:params:xml:ns:xmpp-streams unsupported-version': ['',
        '',
        "The value of the        'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server."],
    'urn:ietf:params:xml:ns:xmpp-streams xml-not-well-formed': ['',
        '',
        'The initiating entity has sent XML that is not well-formed.']
}
STREAM_NOT_AUTHORIZED = 'urn:ietf:params:xml:ns:xmpp-streams not-authorized'
STREAM_REMOTE_CONNECTION_FAILED = 'urn:ietf:params:xml:ns:xmpp-streams remote-connection-failed'
SASL_MECHANISM_TOO_WEAK = 'urn:ietf:params:xml:ns:xmpp-sasl mechanism-too-weak'
STREAM_XML_NOT_WELL_FORMED = 'urn:ietf:params:xml:ns:xmpp-streams xml-not-well-formed'
ERR_JID_MALFORMED = 'urn:ietf:params:xml:ns:xmpp-stanzas jid-malformed'
STREAM_SEE_OTHER_HOST = 'urn:ietf:params:xml:ns:xmpp-streams see-other-host'
STREAM_BAD_NAMESPACE_PREFIX = 'urn:ietf:params:xml:ns:xmpp-streams bad-namespace-prefix'
ERR_SERVICE_UNAVAILABLE = 'urn:ietf:params:xml:ns:xmpp-stanzas service-unavailable'
STREAM_CONNECTION_TIMEOUT = 'urn:ietf:params:xml:ns:xmpp-streams connection-timeout'
STREAM_UNSUPPORTED_VERSION = 'urn:ietf:params:xml:ns:xmpp-streams unsupported-version'
STREAM_IMPROPER_ADDRESSING = 'urn:ietf:params:xml:ns:xmpp-streams improper-addressing'
STREAM_UNDEFINED_CONDITION = 'urn:ietf:params:xml:ns:xmpp-streams undefined-condition'
SASL_NOT_AUTHORIZED = 'urn:ietf:params:xml:ns:xmpp-sasl not-authorized'
ERR_GONE = 'urn:ietf:params:xml:ns:xmpp-stanzas gone'
SASL_TEMPORARY_AUTH_FAILURE = 'urn:ietf:params:xml:ns:xmpp-sasl temporary-auth-failure'
ERR_REMOTE_SERVER_NOT_FOUND = 'urn:ietf:params:xml:ns:xmpp-stanzas remote-server-not-found'
ERR_UNEXPECTED_REQUEST = 'urn:ietf:params:xml:ns:xmpp-stanzas unexpected-request'
ERR_RECIPIENT_UNAVAILABLE = 'urn:ietf:params:xml:ns:xmpp-stanzas recipient-unavailable'
ERR_CONFLICT = 'urn:ietf:params:xml:ns:xmpp-stanzas conflict'
STREAM_SYSTEM_SHUTDOWN = 'urn:ietf:params:xml:ns:xmpp-streams system-shutdown'
STREAM_BAD_FORMAT = 'urn:ietf:params:xml:ns:xmpp-streams bad-format'
ERR_SUBSCRIPTION_REQUIRED = 'urn:ietf:params:xml:ns:xmpp-stanzas subscription-required'
STREAM_INTERNAL_SERVER_ERROR = 'urn:ietf:params:xml:ns:xmpp-streams internal-server-error'
ERR_NOT_AUTHORIZED = 'urn:ietf:params:xml:ns:xmpp-stanzas not-authorized'
SASL_ABORTED = 'urn:ietf:params:xml:ns:xmpp-sasl aborted'
ERR_REGISTRATION_REQUIRED = 'urn:ietf:params:xml:ns:xmpp-stanzas registration-required'
ERR_INTERNAL_SERVER_ERROR = 'urn:ietf:params:xml:ns:xmpp-stanzas internal-server-error'
SASL_INCORRECT_ENCODING = 'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding'
STREAM_HOST_GONE = 'urn:ietf:params:xml:ns:xmpp-streams host-gone'
STREAM_POLICY_VIOLATION = 'urn:ietf:params:xml:ns:xmpp-streams policy-violation'
STREAM_INVALID_XML = 'urn:ietf:params:xml:ns:xmpp-streams invalid-xml'
STREAM_CONFLICT = 'urn:ietf:params:xml:ns:xmpp-streams conflict'
STREAM_RESOURCE_CONSTRAINT = 'urn:ietf:params:xml:ns:xmpp-streams resource-constraint'
STREAM_UNSUPPORTED_ENCODING = 'urn:ietf:params:xml:ns:xmpp-streams unsupported-encoding'
ERR_NOT_ALLOWED = 'urn:ietf:params:xml:ns:xmpp-stanzas not-allowed'
ERR_ITEM_NOT_FOUND = 'urn:ietf:params:xml:ns:xmpp-stanzas item-not-found'
ERR_NOT_ACCEPTABLE = 'urn:ietf:params:xml:ns:xmpp-stanzas not-acceptable'
STREAM_INVALID_FROM = 'urn:ietf:params:xml:ns:xmpp-streams invalid-from'
ERR_FEATURE_NOT_IMPLEMENTED = 'urn:ietf:params:xml:ns:xmpp-stanzas feature-not-implemented'
ERR_BAD_REQUEST = 'urn:ietf:params:xml:ns:xmpp-stanzas bad-request'
STREAM_INVALID_ID = 'urn:ietf:params:xml:ns:xmpp-streams invalid-id'
STREAM_HOST_UNKNOWN = 'urn:ietf:params:xml:ns:xmpp-streams host-unknown'
ERR_UNDEFINED_CONDITION = 'urn:ietf:params:xml:ns:xmpp-stanzas undefined-condition'
SASL_INVALID_MECHANISM = 'urn:ietf:params:xml:ns:xmpp-sasl invalid-mechanism'
STREAM_RESTRICTED_XML = 'urn:ietf:params:xml:ns:xmpp-streams restricted-xml'
ERR_RESOURCE_CONSTRAINT = 'urn:ietf:params:xml:ns:xmpp-stanzas resource-constraint'
ERR_REMOTE_SERVER_TIMEOUT = 'urn:ietf:params:xml:ns:xmpp-stanzas remote-server-timeout'
SASL_INVALID_AUTHZID = 'urn:ietf:params:xml:ns:xmpp-sasl invalid-authzid'
ERR_PAYMENT_REQUIRED = 'urn:ietf:params:xml:ns:xmpp-stanzas payment-required'
STREAM_INVALID_NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-streams invalid-namespace'
ERR_REDIRECT = 'urn:ietf:params:xml:ns:xmpp-stanzas redirect'
STREAM_UNSUPPORTED_STANZA_TYPE = 'urn:ietf:params:xml:ns:xmpp-streams unsupported-stanza-type'
ERR_FORBIDDEN = 'urn:ietf:params:xml:ns:xmpp-stanzas forbidden'
class NodeProcessed(Exception):
class StreamError(Exception):
class BadFormat(StreamError):
class BadNamespacePrefix(StreamError):
class Conflict(StreamError):
class ConnectionTimeout(StreamError):
class HostGone(StreamError):
class HostUnknown(StreamError):
class ImproperAddressing(StreamError):
class InternalServerError(StreamError):
class InvalidFrom(StreamError):
class InvalidID(StreamError):
class InvalidNamespace(StreamError):
class InvalidXML(StreamError):
class NotAuthorized(StreamError):
class PolicyViolation(StreamError):
class RemoteConnectionFailed(StreamError):
class ResourceConstraint(StreamError):
class RestrictedXML(StreamError):
class SeeOtherHost(StreamError):
class SystemShutdown(StreamError):
class UndefinedCondition(StreamError):
class UnsupportedEncoding(StreamError):
class UnsupportedStanzaType(StreamError):
class UnsupportedVersion(StreamError):
class XMLNotWellFormed(StreamError):
class InvalidStanza(Exception):
class InvalidJid(Exception):
class LocalpartByteLimit(InvalidJid):
class LocalpartNotAllowedChar(InvalidJid):
class ResourcepartByteLimit(InvalidJid):
class ResourcepartNotAllowedChar(InvalidJid):
class DomainpartByteLimit(InvalidJid):
class DomainpartNotAllowedChar(InvalidJid):
class StanzaMalformed(Exception):
class DiscoInfoMalformed(Exception):
class JID:
class StreamErrorNode(Node):
class Protocol(Node):
class Message(Protocol):
class Presence(Protocol):
class Iq(Protocol):
class Hashes(Node):
class Hashes2(Node):
class BindRequest(Iq):
class TLSRequest(Node):
class SessionRequest(Iq):
class StreamHeader(Node):
class WebsocketOpenHeader(Node):
class WebsocketCloseHeader(Node):
class Features(Node):
class ErrorNode(Node):
class Error(Protocol):
class DataField(Node):
class DataForm(Node): class _Namespaces:
    ACTIVITY: str = 'http://jabber.org/protocol/activity'
    ADDRESS: str = 'http://jabber.org/protocol/address'
    AGENTS: str = 'jabber:iq:agents'
    ATTENTION: str = 'urn:xmpp:attention:0'
    AUTH: str = 'jabber:iq:auth'
    AVATAR_METADATA: str = 'urn:xmpp:avatar:metadata'
    AVATAR_DATA: str = 'urn:xmpp:avatar:data'
    BIND: str = 'urn:ietf:params:xml:ns:xmpp-bind'
    BLOCKING: str = 'urn:xmpp:blocking'
    BOB: str = 'urn:xmpp:bob'
    BOOKMARKS: str = 'storage:bookmarks'
    BOOKMARKS_1: str = 'urn:xmpp:bookmarks:1'
    BOOKMARKS_COMPAT: str = 'urn:xmpp:bookmarks:1#compat'
    BOOKMARKS_COMPAT_PEP: str = 'urn:xmpp:bookmarks:1#compat-pep'
    BOOKMARK_CONVERSION: str = 'urn:xmpp:bookmarks-conversion:0'
    BROWSE: str = 'jabber:iq:browse'
    BYTESTREAM: str = 'http://jabber.org/protocol/bytestreams'
    CAPS: str = 'http://jabber.org/protocol/caps'
    CAPTCHA: str = 'urn:xmpp:captcha'
    CARBONS: str = 'urn:xmpp:carbons:2'
    CHATMARKERS: str = 'urn:xmpp:chat-markers:0'
    CHATSTATES: str = 'http://jabber.org/protocol/chatstates'
    CLIENT: str = 'jabber:client'
    COMMANDS: str = 'http://jabber.org/protocol/commands'
    CONFERENCE: str = 'jabber:x:conference'
    CORRECT: str = 'urn:xmpp:message-correct:0'
    DATA: str = 'jabber:x:data'
    DATA_LAYOUT: str = 'http://jabber.org/protocol/xdata-layout'
    DATA_MEDIA: str = 'urn:xmpp:media-element'
    DATA_VALIDATE: str = 'http://jabber.org/protocol/xdata-validate'
    DELAY: str = 'jabber:x:delay'
    DELAY2: str = 'urn:xmpp:delay'
    DELIMITER: str = 'roster:delimiter'
    DISCO: str = 'http://jabber.org/protocol/disco'
    DISCO_INFO: str = 'http://jabber.org/protocol/disco#info'
    DISCO_ITEMS: str = 'http://jabber.org/protocol/disco#items'
    DOMAIN_BASED_NAME: str = 'urn:xmpp:domain-based-name:1'
    EME: str = 'urn:xmpp:eme:0'
    ENCRYPTED: str = 'jabber:x:encrypted'
    FASTEN: str = 'urn:xmpp:fasten:0'
    FILE_METADATA: str = 'urn:xmpp:file:metadata:0'
    FORWARD: str = 'urn:xmpp:forward:0'
    FRAMING: str = 'urn:ietf:params:xml:ns:xmpp-framing'
    GATEWAY: str = 'jabber:iq:gateway'
    GEOLOC: str = 'http://jabber.org/protocol/geoloc'
    HASHES: str = 'urn:xmpp:hashes:1'
    HASHES_2: str = 'urn:xmpp:hashes:2'
    HASHES_BLAKE2B_256: str = 'urn:xmpp:hash-function-text-names:id-blake2b256'
    HASHES_BLAKE2B_512: str = 'urn:xmpp:hash-function-text-names:id-blake2b512'
    HASHES_MD5: str = 'urn:xmpp:hash-function-text-names:md5'
    HASHES_SHA1: str = 'urn:xmpp:hash-function-text-names:sha-1'
    HASHES_SHA256: str = 'urn:xmpp:hash-function-text-names:sha-256'
    HASHES_SHA512: str = 'urn:xmpp:hash-function-text-names:sha-512'
    HASHES_SHA3_256: str = 'urn:xmpp:hash-function-text-names:sha3-256'
    HASHES_SHA3_512: str = 'urn:xmpp:hash-function-text-names:sha3-512'
    HINTS: str = 'urn:xmpp:hints'
    HTTPUPLOAD_0: str = 'urn:xmpp:http:upload:0'
    HTTP_AUTH: str = 'http://jabber.org/protocol/http-auth'
    IBB: str = 'http://jabber.org/protocol/ibb'
    IDLE: str = 'urn:xmpp:idle:1'
    JINGLE: str = 'urn:xmpp:jingle:1'
    JINGLE_BYTESTREAM: str = 'urn:xmpp:jingle:transports:s5b:1'
    JINGLE_DTLS: str = 'urn:xmpp:jingle:apps:dtls:0'
    JINGLE_ERRORS: str = 'urn:xmpp:jingle:errors:1'
    JINGLE_FILE_TRANSFER: str = 'urn:xmpp:jingle:apps:file-transfer:3'
    JINGLE_FILE_TRANSFER_5: str = 'urn:xmpp:jingle:apps:file-transfer:5'
    JINGLE_IBB: str = 'urn:xmpp:jingle:transports:ibb:1'
    JINGLE_ICE_UDP: str = 'urn:xmpp:jingle:transports:ice-udp:1'
    JINGLE_RAW_UDP: str = 'urn:xmpp:jingle:transports:raw-udp:1'
    JINGLE_RTP: str = 'urn:xmpp:jingle:apps:rtp:1'
    JINGLE_RTP_AUDIO: str = 'urn:xmpp:jingle:apps:rtp:audio'
    JINGLE_RTP_VIDEO: str = 'urn:xmpp:jingle:apps:rtp:video'
    JINGLE_XTLS: str = 'urn:xmpp:jingle:security:xtls:0'
    LAST: str = 'jabber:iq:last'
    LOCATION: str = 'http://jabber.org/protocol/geoloc'
    MAM_1: str = 'urn:xmpp:mam:1'
    MAM_2: str = 'urn:xmpp:mam:2'
    MESSAGE_MODERATE: str = 'urn:xmpp:message-moderate:0'
    MESSAGE_RETRACT: str = 'urn:xmpp:message-retract:0'
    MOOD: str = 'http://jabber.org/protocol/mood'
    MSG_HINTS: str = 'urn:xmpp:hints'
    MUCLUMBUS: str = 'https://xmlns.zombofant.net/muclumbus/search/1.0'
    MUC: str = 'http://jabber.org/protocol/muc'
    MUC_USER: str = 'http://jabber.org/protocol/muc#user'
    MUC_ADMIN: str = 'http://jabber.org/protocol/muc#admin'
    MUC_OWNER: str = 'http://jabber.org/protocol/muc#owner'
    MUC_UNIQUE: str = 'http://jabber.org/protocol/muc#unique'
    MUC_CONFIG: str = 'http://jabber.org/protocol/muc#roomconfig'
    MUC_REQUEST: str = 'http://jabber.org/protocol/muc#request'
    MUC_INFO: str = 'http://jabber.org/protocol/muc#roominfo'
    NICK: str = 'http://jabber.org/protocol/nick'
    OMEMO_TEMP: str = 'eu.siacs.conversations.axolotl'
    OMEMO_TEMP_BUNDLE: str = 'eu.siacs.conversations.axolotl.bundles'
    OMEMO_TEMP_DL: str = 'eu.siacs.conversations.axolotl.devicelist'
    OPENPGP: str = 'urn:xmpp:openpgp:0'
    OPENPGP_PK: str = 'urn:xmpp:openpgp:0:public-keys'
    OPENPGP_SK: str = 'urn:xmpp:openpgp:0:secret-key'
    PING: str = 'urn:xmpp:ping'
    PRIVACY: str = 'jabber:iq:privacy'
    PRIVATE: str = 'jabber:iq:private'
    PUBKEY_ATTEST: str = 'urn:xmpp:attest:2'
    PUBKEY_PUBKEY: str = 'urn:xmpp:pubkey:2'
    PUBKEY_REVOKE: str = 'urn:xmpp:revoke:2'
    PUBSUB: str = 'http://jabber.org/protocol/pubsub'
    PUBSUB_ERROR: str = 'http://jabber.org/protocol/pubsub#errors'
    PUBSUB_CONFIG: str = 'http://jabber.org/protocol/pubsub#node_config'
    PUBSUB_EVENT: str = 'http://jabber.org/protocol/pubsub#event'
    PUBSUB_OWNER: str = 'http://jabber.org/protocol/pubsub#owner'
    PUBSUB_PUBLISH_OPTIONS: str = 'http://jabber.org/protocol/pubsub#publish-options'
    PUBSUB_NODE_MAX: str = 'http://jabber.org/protocol/pubsub#config-node-max'
    RECEIPTS: str = 'urn:xmpp:receipts'
    REGISTER: str = 'jabber:iq:register'
    REGISTER_FEATURE: str = 'http://jabber.org/features/iq-register'
    REPORTING: str = 'urn:xmpp:reporting:0'
    ROSTER: str = 'jabber:iq:roster'
    ROSTERNOTES: str = 'storage:rosternotes'
    ROSTERX: str = 'http://jabber.org/protocol/rosterx'
    ROSTER_VER: str = 'urn:xmpp:features:rosterver'
    RSM: str = 'http://jabber.org/protocol/rsm'
    SASL: str = 'urn:ietf:params:xml:ns:xmpp-sasl'
    SEARCH: str = 'jabber:iq:search'
    SECLABEL: str = 'urn:xmpp:sec-label:0'
    SECLABEL_CATALOG: str = 'urn:xmpp:sec-label:catalog:2'
    SESSION: str = 'urn:ietf:params:xml:ns:xmpp-session'
    SFS: str = 'urn:xmpp:sfs:0'
    SID: str = 'urn:xmpp:sid:0'
    SIGNED: str = 'jabber:x:signed'
    SIMS: str = 'urn:xmpp:sims:1'
    STANZAS: str = 'urn:ietf:params:xml:ns:xmpp-stanzas'
    STICKERS: str = 'urn:xmpp:stickers:0'
    STREAM: str = 'http://affinix.com/jabber/stream'
    STREAMS: str = 'http://etherx.jabber.org/streams'
    STREAM_MGMT: str = 'urn:xmpp:sm:3'
    STYLING: str = 'urn:xmpp:styling:0'
    TIME_REVISED: str = 'urn:xmpp:time'
    TIME: str = 'urn:xmpp:time'
    TLS: str = 'urn:ietf:params:xml:ns:xmpp-tls'
    TUNE: str = 'http://jabber.org/protocol/tune'
    URL_DATA: str = 'http://jabber.org/protocol/url-data'
    VCARD: str = 'vcard-temp'
    VCARD_UPDATE: str = 'vcard-temp:x:update'
    VCARD_CONVERSION: str = 'urn:xmpp:pep-vcard-conversion:0'
    VCARD4: str = 'urn:ietf:params:xml:ns:vcard-4.0'
    VCARD4_PUBSUB: str = 'urn:xmpp:vcard4'
    VERSION: str = 'jabber:iq:version'
    XHTML_IM: str = 'http://jabber.org/protocol/xhtml-im'
    XHTML: str = 'http://www.w3.org/1999/xhtml'
    XMPP_STREAMS: str = 'urn:ietf:params:xml:ns:xmpp-streams'
    X_OOB: str = 'jabber:x:oob' class MAMQueryData(NamedTuple):
    jid: JID
    rsm: RSMData
    complete: bool class MAMPreferencesData(NamedTuple):
    default: str
    always: list[JID]
    never: list[JID] def iq_request_task(func):
    @wraps(func)
    def func_wrapper(self: BaseModule,
                     *args,
                     timeout: Optional[int] = None,
                     callback: Optional[Callable[..., Any]] = None,
                     user_data: Any = None,
                     **kwargs):
        if self._log.isEnabledFor(logging.INFO):
            self._log.info(make_func_arguments_string(func, self, args, kwargs))
        task = IqRequestTask(func(self, *args, **kwargs),
                             self._log,
                             self._client)
        task.set_timeout(timeout)
        return _setup_task(task, self._client, callback, user_data)
    return func_wrapper class StanzaError(BaseError):

    log_level = logging.INFO
    app_namespace = None

    def __init__(self, stanza: Protocol):
        BaseError.__init__(self)
        self.stanza = stanza
        self._stanza_name = stanza.getName()
        self._error_node = stanza.getTag('error')
        self.condition: Optional[str] = stanza.getError()
        self.condition_data = self._error_node.getTagData(self.condition)
        self.app_condition = self._get_app_condition()
        self.type = stanza.getErrorType()
        self.jid = stanza.getFrom()
        self.id = stanza.getID()
        self._text: dict[str, str] = {}

        text_elements = self._error_node.getTags('text',
                                                 namespace=Namespace.STANZAS)
        for element in text_elements:
            lang = element.getXmlLang()
            text = element.getData()
            self._text[lang] = text

    def _get_app_condition(self) -> Optional[str]:
        if self.app_namespace is None:
            return None

        for node in self._error_node.getChildren():
            if node.getNamespace() == self.app_namespace:
                return node.getName()
        return None

    def get_text(self, pref_lang: Optional[str] = None) -> str:
        if pref_lang is not None:
            text = self._text.get(pref_lang)
            if text is not None:
                return text

        if self._text:
            text = self._text.get('en')
            if text is not None:
                return text

            text = self._text.get(None)
            if text is not None:
                return text
            return self._text.popitem()[1]
        return ''

    def set_text(self, lang: str, text: str) -> None:
        self._text[lang] = text

    def __str__(self) -> str:
        condition = self.condition
        if self.app_condition is not None:
            condition = '%s (%s)' % (self.condition, self.app_condition)
        text = self.get_text('en') or ''
        if text:
            text = ' - %s' % text
        return 'Error from %s: %s%s' % (self.jid, condition, text) class MalformedStanzaError(BaseError):

    log_level = logging.WARNING

    def __init__(self, text, stanza, is_fatal=True):
        BaseError.__init__(self, is_fatal=is_fatal)
        self.stanza = stanza
        self.text = str(text) class BaseModule:

    _depends: dict[str, str] = {}

    def __init__(self, client: Client):
        logger_name = 'nbxmpp.m.%s' % self.__class__.__name__.lower()
        self._log = LogAdapter(logging.getLogger(logger_name),
                               {'context': client.log_context})

    def __getattr__(self, name: str) -> Any:
        if name not in self._depends:
            raise AttributeError('Unknown method: %s' % name)

        module = self._client.get_module(self._depends[name])
        return getattr(module, name) def parse_rsm(stanza):
    stanza = stanza.getTag('set', namespace=Namespace.RSM)
    if stanza is None:
        return None

    after = stanza.getTagData('after') or None
    before = stanza.getTagData('before') or None
    last = stanza.getTagData('last') or None

    first_index = None
    first = stanza.getTagData('first') or None
    if first is not None:
        try:
            first_index = int(first.getAttr('index'))
        except Exception:
            pass

    try:
        count = int(stanza.getTagData('count'))
    except Exception:
        count = None

    try:
        max_ = int(stanza.getTagData('max'))
    except Exception:
        max_ = None

    try:
        index = int(stanza.getTagData('index'))
    except Exception:
        index = None

    return RSMData(after=after,
                   before=before,
                   last=last,
                   first=first,
                   first_index=first_index,
                   count=count,
                   max=max_,
                   index=index) class SimpleDataForm(DataForm, DataRecord):
    def __init__(self, type_=None, title=None, instructions=None, fields=None,
                 extend=None):
        DataForm.__init__(self, type_=type_, title=title,
                          instructions=instructions, extend=extend)
        DataRecord.__init__(self, fields=fields, extend=self, associated=self)

    def get_purged(self):
        simple_form = SimpleDataForm(extend=self)
        del simple_form.title
        simple_form.instructions = ''
        to_be_removed = []
        for field in simple_form.iter_fields():
            if field.required:
                # add <value> if there is not
                if hasattr(field, 'value') and not field.value:
                    field.value = ''
                # Keep all required fields
                continue
            if ((hasattr(field, 'value') and
                 not field.value and
                 field.value != 0) or
                    (hasattr(field, 'values') and not field.values)):
                to_be_removed.append(field)
            else:
                del field.label
                del field.description
                del field.media
        for field in to_be_removed:
            simple_form.delChild(field)
        return simple_form def create_field(typ, **attrs):
    ''' Helper function to create a field of given type. '''
    field = {
        'boolean': BooleanField,
        'fixed': StringField,
        'hidden': StringField,
        'text-private': StringField,
        'text-single': StringField,
        'jid-multi': JidMultiField,
        'jid-single': JidSingleField,
        'list-multi': ListMultiField,
        'list-single': ListSingleField,
        'text-multi': TextMultiField,
    }[typ](typ=typ, **attrs)
    return field def process_response(response):
    if response.isError():
        raise StanzaError(response)

    return CommonResult(jid=response.getFrom())
from nbxmpp.protocol import JID
from nbxmpp.protocol import Iq
from nbxmpp.protocol import Node
from nbxmpp.namespaces import Namespace
from nbxmpp.structs import MAMQueryData
from nbxmpp.structs import MAMPreferencesData
from nbxmpp.task import iq_request_task
from nbxmpp.errors import StanzaError
from nbxmpp.errors import MalformedStanzaError
from nbxmpp.modules.base import BaseModule
from nbxmpp.modules.rsm import parse_rsm
from nbxmpp.modules.dataforms import SimpleDataForm
from nbxmpp.modules.dataforms import create_field
from nbxmpp.modules.util import process_response
# Copyright (C) 2020 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of nbxmpp.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; If not, see <http://www.gnu.org/licenses/>.




class MAM(BaseModule):
    def __init__(self, client):
        BaseModule.__init__(self, client)

        self._client = client
        self.handlers = []

    @iq_request_task
    def make_query(self,
                   jid,
                   queryid=None,
                   start=None,
                   end=None,
                   with_=None,
                   after=None,
                   max_=70):

        _task = yield

        response = yield _make_request(jid, queryid,
                                       start, end, with_, after, max_)
        if response.isError():
            raise StanzaError(response)

        jid = response.getFrom()
        fin = response.getTag('fin', namespace=Namespace.MAM_2)
        if fin is None:
            raise MalformedStanzaError('fin node missing', response)

        rsm = parse_rsm(fin)
        if rsm is None:
            raise MalformedStanzaError('rsm set missing', response)

        complete = fin.getAttr('complete') == 'true'
        if max_ != 0 and not complete:
            # max_ == 0 is a request for count of the items in a result set
            # in this case first and last will be absent
            # See: https://xmpp.org/extensions/xep-0059.html#count
            if rsm.first is None or rsm.last is None:
                raise MalformedStanzaError('first or last element missing',
                                           response)


        yield MAMQueryData(jid=jid,