diff options
-rw-r--r-- | doap.xml | 8 | ||||
-rw-r--r-- | slixmpp/jid.py | 4 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0030/disco.py | 2 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0115/static.py | 4 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0444/stanza.py | 4 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0461/__init__.py | 6 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0461/reply.py | 48 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0461/stanza.py | 47 | ||||
-rw-r--r-- | slixmpp/xmlstream/resolver.py | 36 | ||||
-rw-r--r-- | slixmpp/xmlstream/xmlstream.py | 4 | ||||
-rw-r--r-- | tests/test_stanza_xep_0461.py | 48 | ||||
-rw-r--r-- | tests/test_stream_xep_0461.py | 48 |
12 files changed, 234 insertions, 25 deletions
@@ -457,6 +457,14 @@ </implements> <implements> <xmpp:SupportedXep> + <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0175.html"/> + <xmpp:status>complete</xmpp:status> + <xmpp:version>1.2</xmpp:version> + <xmpp:since>1.0</xmpp:since> + </xmpp:SupportedXep> + </implements> + <implements> + <xmpp:SupportedXep> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/> <xmpp:status>complete</xmpp:status> <xmpp:version>1.4.0</xmpp:version> diff --git a/slixmpp/jid.py b/slixmpp/jid.py index d02f98a3..c705a422 100644 --- a/slixmpp/jid.py +++ b/slixmpp/jid.py @@ -368,7 +368,7 @@ class JID: return self._node @node.setter - def node(self, value: str): + def node(self, value: Optional[str]): self._node = _validate_node(value) self._update_bare_full() @@ -386,7 +386,7 @@ class JID: return self._resource @resource.setter - def resource(self, value: str): + def resource(self, value: Optional[str]): self._resource = _validate_resource(value) self._update_bare_full() diff --git a/slixmpp/plugins/xep_0030/disco.py b/slixmpp/plugins/xep_0030/disco.py index 37d453aa..1169a50e 100644 --- a/slixmpp/plugins/xep_0030/disco.py +++ b/slixmpp/plugins/xep_0030/disco.py @@ -385,6 +385,8 @@ class XEP_0030(BasePlugin): local = True ifrom = kwargs.pop('ifrom', None) + if self.xmpp.is_component and ifrom is None: + ifrom = self.xmpp.boundjid if local: log.debug("Looking up local disco#info data " "for %s, node %s.", jid, node) diff --git a/slixmpp/plugins/xep_0115/static.py b/slixmpp/plugins/xep_0115/static.py index 74f2beb8..4bf77d75 100644 --- a/slixmpp/plugins/xep_0115/static.py +++ b/slixmpp/plugins/xep_0115/static.py @@ -60,7 +60,7 @@ class StaticCaps(object): return False if node in (None, ''): - info = self.caps.get_caps(jid) + info = await self.caps.get_caps(jid) if info and feature in info['features']: return True @@ -134,7 +134,7 @@ class StaticCaps(object): def get_verstring(self, jid, node, ifrom, data): return self.jid_vers.get(jid, None) - def get_caps(self, jid, node, ifrom, data): + async def get_caps(self, jid, node, ifrom, data): verstring = data.get('verstring', None) if verstring is None: return None diff --git a/slixmpp/plugins/xep_0444/stanza.py b/slixmpp/plugins/xep_0444/stanza.py index 02684df1..c9ee07d7 100644 --- a/slixmpp/plugins/xep_0444/stanza.py +++ b/slixmpp/plugins/xep_0444/stanza.py @@ -6,9 +6,7 @@ from typing import Set, Iterable from slixmpp.xmlstream import ElementBase try: - from emoji import UNICODE_EMOJI - if UNICODE_EMOJI.get('en'): - UNICODE_EMOJI = UNICODE_EMOJI['en'] + from emoji import EMOJI_DATA as UNICODE_EMOJI except ImportError: UNICODE_EMOJI = None diff --git a/slixmpp/plugins/xep_0461/__init__.py b/slixmpp/plugins/xep_0461/__init__.py new file mode 100644 index 00000000..1e9b2829 --- /dev/null +++ b/slixmpp/plugins/xep_0461/__init__.py @@ -0,0 +1,6 @@ +from slixmpp.plugins.base import register_plugin + +from .reply import XEP_0461 +from . import stanza + +register_plugin(XEP_0461) diff --git a/slixmpp/plugins/xep_0461/reply.py b/slixmpp/plugins/xep_0461/reply.py new file mode 100644 index 00000000..6607012a --- /dev/null +++ b/slixmpp/plugins/xep_0461/reply.py @@ -0,0 +1,48 @@ +from slixmpp.plugins import BasePlugin +from slixmpp.types import JidStr +from slixmpp.xmlstream import StanzaBase +from slixmpp.xmlstream.handler import Callback +from slixmpp.xmlstream.matcher import StanzaPath + +from . import stanza + + +class XEP_0461(BasePlugin): + """XEP-0461: Message Replies""" + + name = "xep_0461" + description = "XEP-0461: Message Replies" + + dependencies = {"xep_0030"} + stanza = stanza + namespace = stanza.NS + + def plugin_init(self) -> None: + stanza.register_plugins() + self.xmpp.register_handler( + Callback( + "Message replied to", + StanzaPath("message/reply"), + self._handle_reply_to_message, + ) + ) + + def plugin_end(self): + self.xmpp.plugin["xep_0030"].del_feature(feature=stanza.NS) + + def session_bind(self, jid): + self.xmpp.plugin["xep_0030"].add_feature(feature=stanza.NS) + + def _handle_reply_to_message(self, msg: StanzaBase): + self.xmpp.event("message_reply", msg) + + def send_reply(self, reply_to: JidStr, reply_id: str, **msg_kwargs): + """ + + :param reply_to: Full JID of the quoted author + :param reply_id: ID of the message to reply to + """ + msg = self.xmpp.make_message(**msg_kwargs) + msg["reply"]["to"] = reply_to + msg["reply"]["id"] = reply_id + msg.send() diff --git a/slixmpp/plugins/xep_0461/stanza.py b/slixmpp/plugins/xep_0461/stanza.py new file mode 100644 index 00000000..b99b2745 --- /dev/null +++ b/slixmpp/plugins/xep_0461/stanza.py @@ -0,0 +1,47 @@ +from slixmpp.stanza import Message +from slixmpp.xmlstream import ElementBase, register_stanza_plugin + +NS = "urn:xmpp:reply:0" + + +class Reply(ElementBase): + namespace = NS + name = "reply" + plugin_attrib = "reply" + interfaces = {"id", "to"} + + +class FeatureFallBack(ElementBase): + # should also be a multi attrib + namespace = "urn:xmpp:feature-fallback:0" + name = "fallback" + plugin_attrib = "feature_fallback" + interfaces = {"for"} + + def get_stripped_body(self): + # only works for a single fallback_body attrib + start = self["fallback_body"]["start"] + end = self["fallback_body"]["end"] + body = self.parent()["body"] + try: + start = int(start) + end = int(end) + except ValueError: + return body + else: + return body[:start] + body[end:] + + +class FallBackBody(ElementBase): + # According to https://xmpp.org/extensions/inbox/compatibility-fallback.html + # this should be a multi_attrib *but* since it's a protoXEP, we'll see... + namespace = FeatureFallBack.namespace + name = "body" + plugin_attrib = "fallback_body" + interfaces = {"start", "end"} + + +def register_plugins(): + register_stanza_plugin(Message, Reply) + register_stanza_plugin(Message, FeatureFallBack) + register_stanza_plugin(FeatureFallBack, FallBackBody) diff --git a/slixmpp/xmlstream/resolver.py b/slixmpp/xmlstream/resolver.py index e524da3b..3de6629d 100644 --- a/slixmpp/xmlstream/resolver.py +++ b/slixmpp/xmlstream/resolver.py @@ -15,7 +15,13 @@ from slixmpp.types import Protocol log = logging.getLogger(__name__) -class AnswerProtocol(Protocol): +class GetHostByNameAnswerProtocol(Protocol): + name: str + aliases: List[str] + addresses: List[str] + + +class QueryAnswerProtocol(Protocol): host: str priority: int weight: int @@ -23,6 +29,9 @@ class AnswerProtocol(Protocol): class ResolverProtocol(Protocol): + def gethostbyname(self, host: str, socket_family: socket.AddressFamily) -> Future: + ... + def query(self, query: str, querytype: str) -> Future: ... @@ -147,11 +156,6 @@ async def resolve(host: str, port: int, *, loop: AbstractEventLoop, results = [] for host, port in hosts: - if host == 'localhost': - if use_ipv6: - results.append((host, '::1', port)) - results.append((host, '127.0.0.1', port)) - if use_ipv6: aaaa = await get_AAAA(host, resolver=resolver, use_aiodns=use_aiodns, loop=loop) @@ -201,13 +205,13 @@ async def get_A(host: str, *, loop: AbstractEventLoop, return [] # Using aiodns: - future = resolver.query(host, 'A') + future = resolver.gethostbyname(host, socket.AF_INET) try: - recs = cast(Iterable[AnswerProtocol], await future) + recs = cast(GetHostByNameAnswerProtocol, await future) except Exception as e: log.debug('DNS: Exception while querying for %s A records: %s', host, e) - recs = [] - return [rec.host for rec in recs] + return [] + return [addr for addr in recs.addresses] async def get_AAAA(host: str, *, loop: AbstractEventLoop, @@ -249,13 +253,13 @@ async def get_AAAA(host: str, *, loop: AbstractEventLoop, return [] # Using aiodns: - future = resolver.query(host, 'AAAA') + future = resolver.gethostbyname(host, socket.AF_INET6) try: - recs = cast(Iterable[AnswerProtocol], await future) + recs = cast(GetHostByNameAnswerProtocol, await future) except Exception as e: log.debug('DNS: Exception while querying for %s AAAA records: %s', host, e) - recs = [] - return [rec.host for rec in recs] + return [] + return [addr for addr in recs.addresses] async def get_SRV(host: str, port: int, service: str, @@ -295,12 +299,12 @@ async def get_SRV(host: str, port: int, service: str, try: future = resolver.query('_%s._%s.%s' % (service, proto, host), 'SRV') - recs = cast(Iterable[AnswerProtocol], await future) + recs = cast(Iterable[QueryAnswerProtocol], await future) except Exception as e: log.debug('DNS: Exception while querying for %s SRV records: %s', host, e) return [] - answers: Dict[int, List[AnswerProtocol]] = {} + answers: Dict[int, List[QueryAnswerProtocol]] = {} for rec in recs: if rec.priority not in answers: answers[rec.priority] = [] diff --git a/slixmpp/xmlstream/xmlstream.py b/slixmpp/xmlstream/xmlstream.py index 18464ccd..19c4ddcc 100644 --- a/slixmpp/xmlstream/xmlstream.py +++ b/slixmpp/xmlstream/xmlstream.py @@ -574,7 +574,7 @@ class XMLStream(asyncio.BaseProtocol): stream=self, top_level=True, open_only=True)) - self.start_stream_handler(self.xml_root) + self.start_stream_handler(self.xml_root) # type:ignore self.xml_depth += 1 if event == 'end': self.xml_depth -= 1 @@ -1267,7 +1267,7 @@ class XMLStream(asyncio.BaseProtocol): already_run_filters.add(filter) if iscoroutinefunction(filter): filter = cast(AsyncFilter, filter) - task = asyncio.create_task(filter(data)) + task = asyncio.create_task(filter(data)) # type:ignore completed, pending = await wait( {task}, timeout=1, diff --git a/tests/test_stanza_xep_0461.py b/tests/test_stanza_xep_0461.py new file mode 100644 index 00000000..b9550481 --- /dev/null +++ b/tests/test_stanza_xep_0461.py @@ -0,0 +1,48 @@ +import unittest +from slixmpp import Message +from slixmpp.test import SlixTest +from slixmpp.plugins.xep_0461 import stanza + + +class TestReply(SlixTest): + def setUp(self): + stanza.register_plugins() + + def testReply(self): + message = Message() + message["reply"]["id"] = "some-id" + message["body"] = "some-body" + + self.check( + message, + """ + <message> + <reply xmlns="urn:xmpp:reply:0" id="some-id" /> + <body>some-body</body> + </message> + """, + ) + + def testFallback(self): + message = Message() + message["body"] = "12345\nrealbody" + message["feature_fallback"]["for"] = "NS" + message["feature_fallback"]["fallback_body"]["start"] = "0" + message["feature_fallback"]["fallback_body"]["end"] = "6" + + self.check( + message, + """ + <message xmlns="jabber:client"> + <body>12345\nrealbody</body> + <fallback xmlns='urn:xmpp:feature-fallback:0' for='NS'> + <body start="0" end="6" /> + </fallback> + </message> + """, + ) + + assert message["feature_fallback"].get_stripped_body() == "realbody" + + +suite = unittest.TestLoader().loadTestsFromTestCase(TestReply) diff --git a/tests/test_stream_xep_0461.py b/tests/test_stream_xep_0461.py new file mode 100644 index 00000000..b73a9964 --- /dev/null +++ b/tests/test_stream_xep_0461.py @@ -0,0 +1,48 @@ +import logging +import unittest +from slixmpp.test import SlixTest + + +class TestReply(SlixTest): + def setUp(self): + self.stream_start(plugins=["xep_0461"]) + + def tearDown(self): + self.stream_close() + + def testFallBackBody(self): + async def on_reply(msg): + start = msg["feature_fallback"]["fallback_body"]["start"] + end = msg["feature_fallback"]["fallback_body"]["end"] + self.xmpp["xep_0461"].send_reply( + reply_to=msg.get_from(), + reply_id=msg.get_id(), + mto="test@test.com", + mbody=f"{start} to {end}", + ) + + self.xmpp.add_event_handler("message_reply", on_reply) + + self.recv( + """ + <message id="other-id" from="from@from.com/res"> + <reply xmlns="urn:xmpp:reply:0" id="some-id" /> + <body>> quoted\nsome-body</body> + <fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'> + <body start="0" end="8" /> + </fallback> + </message> + """ + ) + self.send( + """ + <message xmlns="jabber:client" to="test@test.com" type="normal"> + <reply xmlns="urn:xmpp:reply:0" id="other-id" to="from@from.com/res" /> + <body>0 to 8</body> + </message> + """ + ) + + +logging.basicConfig(level=logging.DEBUG) +suite = unittest.TestLoader().loadTestsFromTestCase(TestReply) |