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 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 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 AnnotationNote(NamedTuple):
    jid: JID
    data: str
    cdate: Optional[datetime] = None
    mdate: Optional[datetime] = None 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) 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 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 process_response(response):
    if response.isError():
        raise StanzaError(response)

    return CommonResult(jid=response.getFrom()) def parse_datetime(timestring, check_utc=False,
                   convert='utc', epoch=False):
    '''
    Parse a XEP-0082 DateTime Profile String

    :param timestring: a XEP-0082 DateTime profile formated string

    :param check_utc:  if True, returns None if timestring is not
                       a timestring expressing UTC

    :param convert:    convert the given timestring to utc or local time

    :param epoch:      if True, returns the time in epoch

    Examples:
    '2017-11-05T01:41:20Z'
    '2017-11-05T01:41:20.123Z'
    '2017-11-05T01:41:20.123+05:00'

    return a datetime or epoch
    '''
    if timestring is None:
        return None
    if convert not in (None, 'utc', 'local'):
        raise TypeError('"%s" is not a valid value for convert')
    if check_utc:
        match = PATTERN_DELAY.match(timestring)
    else:
        match = PATTERN_DATETIME.match(timestring)

    if match:
        timestring = ''.join(match.groups(''))
        strformat = '%Y-%m-%d%H:%M:%S%z'
        if match.group(3):
            # Fractional second addendum to Time
            strformat = '%Y-%m-%d%H:%M:%S.%f%z'
        if match.group(4):
            # UTC string denoted by addition of the character 'Z'
            timestring = timestring[:-1] + '+0000'
        try:
            date_time = datetime.strptime(timestring, strformat)
        except ValueError:
            pass
        else:
            if check_utc:
                if convert != 'utc':
                    raise ValueError(
                        'check_utc can only be used with convert="utc"')
                date_time.replace(tzinfo=timezone.utc)
                if epoch:
                    return date_time.timestamp()
                return date_time

            if convert == 'utc':
                date_time = date_time.astimezone(timezone.utc)
                if epoch:
                    return date_time.timestamp()
                return date_time

            if epoch:
                # epoch is always UTC, use convert='utc' or check_utc=True
                raise ValueError(
                    'epoch not available while converting to local')

            if convert == 'local':
                date_time = date_time.astimezone(LocalTimezone())
                return date_time

            # convert=None
            return date_time
    return None
from nbxmpp.namespaces import Namespace
from nbxmpp.protocol import Iq
from nbxmpp.protocol import Node
from nbxmpp.protocol import JID
from nbxmpp.structs import AnnotationNote
from nbxmpp.errors import StanzaError
from nbxmpp.errors import MalformedStanzaError
from nbxmpp.task import iq_request_task
from nbxmpp.modules.base import BaseModule
from nbxmpp.modules.util import process_response
from nbxmpp.modules.date_and_time import parse_datetime
# Copyright (C) 2019 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 Annotations(BaseModule):
    def __init__(self, client):
        BaseModule.__init__(self, client)

        self._client = client
        self.handlers = []

    @property
    def domain(self):
        return self._client.get_bound_jid().domain

    @iq_request_task
    def request_annotations(self):
        _task = yield

        response = yield _make_request()
        if response.isError():
            raise StanzaError(response)

        query = response.getQuery()
        storage = query.getTag('storage', namespace=Namespace.ROSTERNOTES)
        if storage is None:
            raise MalformedStanzaError('storage node missing', response)

        notes = []
        for note in storage.getTags('note'):
            try:
                jid = JID.from_string(note.getAttr('jid'))
            except Exception as error:
                self._log.warning('Invalid JID: %s, %s',
                                  note.getAttr('jid'), error)
                continue

            cdate = note.getAttr('cdate')
            if cdate is not None:
                cdate = parse_datetime(cdate, epoch=True)

            mdate = note.getAttr('mdate')
            if mdate is not None:
                mdate = parse_datetime(mdate, epoch=True)

            data = note.getData()

            notes.append(AnnotationNote(jid=jid, cdate=cdate,