Mercurial > libervia-backend
comparison libervia/backend/core/xmpp.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 2417ad1d0f23 |
children | 0f953ce5f0a8 |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
89 return partial(getattr(self.plugin, attr), self.client) | 89 return partial(getattr(self.plugin, attr), self.client) |
90 | 90 |
91 | 91 |
92 class SatXMPPEntity(core_types.SatXMPPEntity): | 92 class SatXMPPEntity(core_types.SatXMPPEntity): |
93 """Common code for Client and Component""" | 93 """Common code for Client and Component""" |
94 | |
94 # profile is added there when start_connection begins and removed when it is finished | 95 # profile is added there when start_connection begins and removed when it is finished |
95 profiles_connecting = set() | 96 profiles_connecting = set() |
96 | 97 |
97 def __init__(self, host_app, profile, max_retries): | 98 def __init__(self, host_app, profile, max_retries): |
98 factory = self.factory | 99 factory = self.factory |
100 # we monkey patch clientConnectionLost to handle network_enabled/network_disabled | 101 # we monkey patch clientConnectionLost to handle network_enabled/network_disabled |
101 # and to allow plugins to tune reconnection mechanism | 102 # and to allow plugins to tune reconnection mechanism |
102 clientConnectionFailed_ori = factory.clientConnectionFailed | 103 clientConnectionFailed_ori = factory.clientConnectionFailed |
103 clientConnectionLost_ori = factory.clientConnectionLost | 104 clientConnectionLost_ori = factory.clientConnectionLost |
104 factory.clientConnectionFailed = partial( | 105 factory.clientConnectionFailed = partial( |
105 self.connection_terminated, term_type="failed", cb=clientConnectionFailed_ori) | 106 self.connection_terminated, term_type="failed", cb=clientConnectionFailed_ori |
107 ) | |
106 factory.clientConnectionLost = partial( | 108 factory.clientConnectionLost = partial( |
107 self.connection_terminated, term_type="lost", cb=clientConnectionLost_ori) | 109 self.connection_terminated, term_type="lost", cb=clientConnectionLost_ori |
110 ) | |
108 | 111 |
109 factory.maxRetries = max_retries | 112 factory.maxRetries = max_retries |
110 factory.maxDelay = 30 | 113 factory.maxDelay = 30 |
111 # when self._connected_d is None, we are not connected | 114 # when self._connected_d is None, we are not connected |
112 # else, it's a deferred which fire on disconnection | 115 # else, it's a deferred which fire on disconnection |
113 self._connected_d = None | 116 self._connected_d = None |
114 self.profile = profile | 117 self.profile = profile |
115 self.host_app = host_app | 118 self.host_app = host_app |
116 self.cache = cache.Cache(host_app, profile) | 119 self.cache = cache.Cache(host_app, profile) |
117 self.mess_id2uid = {} # map from message id to uid used in history. | 120 self.mess_id2uid = {} # map from message id to uid used in history. |
118 # Key: (full_jid, message_id) Value: uid | 121 # Key: (full_jid, message_id) Value: uid |
119 # this Deferred fire when entity is connected | 122 # this Deferred fire when entity is connected |
120 self.conn_deferred = defer.Deferred() | 123 self.conn_deferred = defer.Deferred() |
121 self._progress_cb = {} # callback called when a progress is requested | 124 self._progress_cb = {} # callback called when a progress is requested |
122 # (key = progress id) | 125 # (key = progress id) |
123 self.actions = {} # used to keep track of actions for retrieval (key = action_id) | 126 self.actions = {} # used to keep track of actions for retrieval (key = action_id) |
124 self.encryption = encryption.EncryptionHandler(self) | 127 self.encryption = encryption.EncryptionHandler(self) |
125 | 128 |
126 def __str__(self): | 129 def __str__(self): |
127 return f"Client for profile {self.profile}" | 130 return f"Client for profile {self.profile}" |
146 if plugin.is_handler: | 149 if plugin.is_handler: |
147 plugin.get_handler(self).setHandlerParent(self) | 150 plugin.get_handler(self).setHandlerParent(self) |
148 | 151 |
149 # profile_connecting/profile_connected methods handling | 152 # profile_connecting/profile_connected methods handling |
150 | 153 |
151 timer = connection_timer[plugin] = { | 154 timer = connection_timer[plugin] = {"total": 0} |
152 "total": 0 | |
153 } | |
154 # profile connecting is called right now (before actually starting client) | 155 # profile connecting is called right now (before actually starting client) |
155 connecting_cb = getattr(plugin, "profile_connecting", None) | 156 connecting_cb = getattr(plugin, "profile_connecting", None) |
156 if connecting_cb is not None: | 157 if connecting_cb is not None: |
157 connecting_start = time.time() | 158 connecting_start = time.time() |
158 await utils.as_deferred(connecting_cb, self) | 159 await utils.as_deferred(connecting_cb, self) |
185 """ | 186 """ |
186 return | 187 return |
187 | 188 |
188 @staticmethod | 189 @staticmethod |
189 async def _run_profile_connected( | 190 async def _run_profile_connected( |
190 callback: Callable, | 191 callback: Callable, entity: "SatXMPPEntity", timer: Dict[str, float] |
191 entity: "SatXMPPEntity", | |
192 timer: Dict[str, float] | |
193 ) -> None: | 192 ) -> None: |
194 connected_start = time.time() | 193 connected_start = time.time() |
195 await utils.as_deferred(callback, entity) | 194 await utils.as_deferred(callback, entity) |
196 timer["connected"] = time.time() - connected_start | 195 timer["connected"] = time.time() - connected_start |
197 timer["total"] += timer["connected"] | 196 timer["total"] += timer["connected"] |
215 C.FORCE_PORT_PARAM, "Connection", profile_key=profile | 214 C.FORCE_PORT_PARAM, "Connection", profile_key=profile |
216 ) | 215 ) |
217 ) | 216 ) |
218 except ValueError: | 217 except ValueError: |
219 log.debug(_("Can't parse port value, using default value")) | 218 log.debug(_("Can't parse port value, using default value")) |
220 port = ( | 219 port = None # will use default value 5222 or be retrieved from a DNS SRV record |
221 None | |
222 ) # will use default value 5222 or be retrieved from a DNS SRV record | |
223 | 220 |
224 password = await host.memory.param_get_a_async( | 221 password = await host.memory.param_get_a_async( |
225 "Password", "Connection", profile_key=profile | 222 "Password", "Connection", profile_key=profile |
226 ) | 223 ) |
227 | 224 |
228 entity_jid_s = await host.memory.param_get_a_async( | 225 entity_jid_s = await host.memory.param_get_a_async( |
229 "JabberID", "Connection", profile_key=profile) | 226 "JabberID", "Connection", profile_key=profile |
227 ) | |
230 entity_jid = jid.JID(entity_jid_s) | 228 entity_jid = jid.JID(entity_jid_s) |
231 | 229 |
232 if not entity_jid.resource and not cls.is_component and entity_jid.user: | 230 if not entity_jid.resource and not cls.is_component and entity_jid.user: |
233 # if no resource is specified, we create our own instead of using | 231 # if no resource is specified, we create our own instead of using |
234 # server returned one, as it will then stay stable in case of | 232 # server returned one, as it will then stay stable in case of |
235 # reconnection. we only do that for client and if there is a user part, to | 233 # reconnection. we only do that for client and if there is a user part, to |
236 # let server decide for anonymous login | 234 # let server decide for anonymous login |
237 resource_dict = await host.memory.storage.get_privates( | 235 resource_dict = await host.memory.storage.get_privates( |
238 "core:xmpp", ["resource"] , profile=profile) | 236 "core:xmpp", ["resource"], profile=profile |
237 ) | |
239 try: | 238 try: |
240 resource = resource_dict["resource"] | 239 resource = resource_dict["resource"] |
241 except KeyError: | 240 except KeyError: |
242 resource = f"{C.APP_NAME_FILE}.{shortuuid.uuid()}" | 241 resource = f"{C.APP_NAME_FILE}.{shortuuid.uuid()}" |
243 await host.memory.storage.set_private_value( | 242 await host.memory.storage.set_private_value( |
244 "core:xmpp", "resource", resource, profile=profile) | 243 "core:xmpp", "resource", resource, profile=profile |
245 | 244 ) |
246 log.info(_("We'll use the stable resource {resource}").format( | 245 |
247 resource=resource)) | 246 log.info( |
247 _("We'll use the stable resource {resource}").format( | |
248 resource=resource | |
249 ) | |
250 ) | |
248 entity_jid.resource = resource | 251 entity_jid.resource = resource |
249 | 252 |
250 if profile in host.profiles: | 253 if profile in host.profiles: |
251 if host.profiles[profile].is_connected(): | 254 if host.profiles[profile].is_connected(): |
252 raise exceptions.InternalError( | 255 raise exceptions.InternalError( |
253 f"There is already a connected profile of name {profile!r} in " | 256 f"There is already a connected profile of name {profile!r} in " |
254 f"host") | 257 f"host" |
255 log.debug( | 258 ) |
256 "removing unconnected profile {profile!r}") | 259 log.debug("removing unconnected profile {profile!r}") |
257 del host.profiles[profile] | 260 del host.profiles[profile] |
258 entity = host.profiles[profile] = cls( | 261 entity = host.profiles[profile] = cls( |
259 host, profile, entity_jid, password, | 262 host, |
260 host.memory.param_get_a(C.FORCE_SERVER_PARAM, "Connection", | 263 profile, |
261 profile_key=profile) or None, | 264 entity_jid, |
262 port, max_retries, | 265 password, |
263 ) | 266 host.memory.param_get_a( |
267 C.FORCE_SERVER_PARAM, "Connection", profile_key=profile | |
268 ) | |
269 or None, | |
270 port, | |
271 max_retries, | |
272 ) | |
264 | 273 |
265 await entity.encryption.load_sessions() | 274 await entity.encryption.load_sessions() |
266 | 275 |
267 entity._create_sub_protocols() | 276 entity._create_sub_protocols() |
268 | 277 |
313 | 322 |
314 log.debug(f"Plugin loading time for {profile!r} (longer to shorter):\n") | 323 log.debug(f"Plugin loading time for {profile!r} (longer to shorter):\n") |
315 plugins_by_timer = sorted( | 324 plugins_by_timer = sorted( |
316 connection_timer, | 325 connection_timer, |
317 key=lambda p: connection_timer[p]["total"], | 326 key=lambda p: connection_timer[p]["total"], |
318 reverse=True | 327 reverse=True, |
319 ) | 328 ) |
320 # total is the addition of all connecting and connected, doesn't really | 329 # total is the addition of all connecting and connected, doesn't really |
321 # reflect the real loading time as connected are launched in a | 330 # reflect the real loading time as connected are launched in a |
322 # DeferredList | 331 # DeferredList |
323 total_plugins = 0 | 332 total_plugins = 0 |
439 anymore | 448 anymore |
440 """ | 449 """ |
441 # we save connector because it may be deleted when connection will be dropped | 450 # we save connector because it may be deleted when connection will be dropped |
442 # if reconnection is disabled | 451 # if reconnection is disabled |
443 self._saved_connector = connector | 452 self._saved_connector = connector |
444 if reason is not None and not isinstance(reason.value, | 453 if reason is not None and not isinstance( |
445 internet_error.ConnectionDone): | 454 reason.value, internet_error.ConnectionDone |
455 ): | |
446 try: | 456 try: |
447 reason_str = str(reason.value) | 457 reason_str = str(reason.value) |
448 except Exception: | 458 except Exception: |
449 # FIXME: workaround for Android were p4a strips docstrings | 459 # FIXME: workaround for Android were p4a strips docstrings |
450 # while Twisted use docstring in __str__ | 460 # while Twisted use docstring in __str__ |
494 connector.connect() | 504 connector.connect() |
495 | 505 |
496 def _connected(self, xs): | 506 def _connected(self, xs): |
497 send_hooks = [] | 507 send_hooks = [] |
498 receive_hooks = [] | 508 receive_hooks = [] |
499 self.host_app.trigger.point( | 509 self.host_app.trigger.point("stream_hooks", self, receive_hooks, send_hooks) |
500 "stream_hooks", self, receive_hooks, send_hooks) | |
501 for hook in receive_hooks: | 510 for hook in receive_hooks: |
502 xs.add_hook(C.STREAM_HOOK_RECEIVE, hook) | 511 xs.add_hook(C.STREAM_HOOK_RECEIVE, hook) |
503 for hook in send_hooks: | 512 for hook in send_hooks: |
504 xs.add_hook(C.STREAM_HOOK_SEND, hook) | 513 xs.add_hook(C.STREAM_HOOK_SEND, hook) |
505 super(SatXMPPEntity, self)._connected(xs) | 514 super(SatXMPPEntity, self)._connected(xs) |
527 else: | 536 else: |
528 err = reason | 537 err = reason |
529 try: | 538 try: |
530 if err.value.args[0][0][2] == "certificate verify failed": | 539 if err.value.args[0][0][2] == "certificate verify failed": |
531 err = exceptions.InvalidCertificate( | 540 err = exceptions.InvalidCertificate( |
532 _("Your server certificate is not valid " | 541 _( |
533 "(its identity can't be checked).\n\n" | 542 "Your server certificate is not valid " |
534 "This should never happen and may indicate that " | 543 "(its identity can't be checked).\n\n" |
535 "somebody is trying to spy on you.\n" | 544 "This should never happen and may indicate that " |
536 "Please contact your server administrator.")) | 545 "somebody is trying to spy on you.\n" |
546 "Please contact your server administrator." | |
547 ) | |
548 ) | |
537 self.factory.stopTrying() | 549 self.factory.stopTrying() |
538 try: | 550 try: |
539 # with invalid certificate, we should not retry to connect | 551 # with invalid certificate, we should not retry to connect |
540 # so we delete saved connector to avoid reconnection if | 552 # so we delete saved connector to avoid reconnection if |
541 # network_enabled is called. | 553 # network_enabled is called. |
613 self.xmlstream.send(iq_error_elt) | 625 self.xmlstream.send(iq_error_elt) |
614 | 626 |
615 def generate_message_xml( | 627 def generate_message_xml( |
616 self, | 628 self, |
617 data: core_types.MessageData, | 629 data: core_types.MessageData, |
618 post_xml_treatments: Optional[defer.Deferred] = None | 630 post_xml_treatments: Optional[defer.Deferred] = None, |
619 ) -> core_types.MessageData: | 631 ) -> core_types.MessageData: |
620 """Generate <message/> stanza from message data | 632 """Generate <message/> stanza from message data |
621 | 633 |
622 @param data: message data | 634 @param data: message data |
623 domish element will be put in data['xml'] | 635 domish element will be put in data['xml'] |
708 # XXX: This is the last trigger before u"send" (last but one globally) | 720 # XXX: This is the last trigger before u"send" (last but one globally) |
709 # for sending message. | 721 # for sending message. |
710 # This is intented for e2e encryption which doesn't do full stanza | 722 # This is intented for e2e encryption which doesn't do full stanza |
711 # encryption (e.g. OTR) | 723 # encryption (e.g. OTR) |
712 # This trigger point can't cancel the method | 724 # This trigger point can't cancel the method |
713 await self.host_app.trigger.async_point("send_message_data", self, mess_data, | 725 await self.host_app.trigger.async_point( |
714 triggers_no_cancel=True) | 726 "send_message_data", self, mess_data, triggers_no_cancel=True |
727 ) | |
715 await self.a_send(mess_data["xml"]) | 728 await self.a_send(mess_data["xml"]) |
716 return mess_data | 729 return mess_data |
717 | 730 |
718 def sendMessage( | 731 def sendMessage( |
719 self, to_jid, message, subject=None, mess_type="auto", extra=None, uid=None, | 732 self, |
720 no_trigger=False): | 733 to_jid, |
734 message, | |
735 subject=None, | |
736 mess_type="auto", | |
737 extra=None, | |
738 uid=None, | |
739 no_trigger=False, | |
740 ): | |
721 r"""Send a message to an entity | 741 r"""Send a message to an entity |
722 | 742 |
723 @param to_jid(jid.JID): destinee of the message | 743 @param to_jid(jid.JID): destinee of the message |
724 @param message(dict): message body, key is the language (use '' when unknown) | 744 @param message(dict): message body, key is the language (use '' when unknown) |
725 @param subject(dict): message subject, key is the language (use '' when unknown) | 745 @param subject(dict): message subject, key is the language (use '' when unknown) |
795 pre_xml_treatments, | 815 pre_xml_treatments, |
796 post_xml_treatments, | 816 post_xml_treatments, |
797 ): | 817 ): |
798 return defer.succeed(None) | 818 return defer.succeed(None) |
799 | 819 |
800 log.debug(_("Sending message (type {type}, to {to})") | 820 log.debug( |
801 .format(type=data["type"], to=to_jid.full())) | 821 _("Sending message (type {type}, to {to})").format( |
802 | 822 type=data["type"], to=to_jid.full() |
803 pre_xml_treatments.addCallback(lambda __: self.generate_message_xml(data, post_xml_treatments)) | 823 ) |
824 ) | |
825 | |
826 pre_xml_treatments.addCallback( | |
827 lambda __: self.generate_message_xml(data, post_xml_treatments) | |
828 ) | |
804 pre_xml_treatments.addCallback(lambda __: post_xml_treatments) | 829 pre_xml_treatments.addCallback(lambda __: post_xml_treatments) |
805 pre_xml_treatments.addErrback(self._cancel_error_trap) | 830 pre_xml_treatments.addErrback(self._cancel_error_trap) |
806 post_xml_treatments.addCallback( | 831 post_xml_treatments.addCallback( |
807 lambda __: defer.ensureDeferred(self.send_message_data(data)) | 832 lambda __: defer.ensureDeferred(self.send_message_data(data)) |
808 ) | 833 ) |
809 if send_only: | 834 if send_only: |
810 log.debug(_("Triggers, storage and echo have been inhibited by the " | 835 log.debug( |
811 "'send_only' parameter")) | 836 _( |
837 "Triggers, storage and echo have been inhibited by the " | |
838 "'send_only' parameter" | |
839 ) | |
840 ) | |
812 else: | 841 else: |
813 self.add_post_xml_callbacks(post_xml_treatments) | 842 self.add_post_xml_callbacks(post_xml_treatments) |
814 post_xml_treatments.addErrback(self._cancel_error_trap) | 843 post_xml_treatments.addErrback(self._cancel_error_trap) |
815 post_xml_treatments.addErrback(self.host_app.log_errback) | 844 post_xml_treatments.addErrback(self.host_app.log_errback) |
816 pre_xml_treatments.callback(data) | 845 pre_xml_treatments.callback(data) |
821 failure.trap(exceptions.CancelError) | 850 failure.trap(exceptions.CancelError) |
822 | 851 |
823 def is_message_printable(self, mess_data): | 852 def is_message_printable(self, mess_data): |
824 """Return True if a message contain payload to show in frontends""" | 853 """Return True if a message contain payload to show in frontends""" |
825 return ( | 854 return ( |
826 mess_data["message"] or mess_data["subject"] | 855 mess_data["message"] |
856 or mess_data["subject"] | |
827 or mess_data["extra"].get(C.KEY_ATTACHMENTS) | 857 or mess_data["extra"].get(C.KEY_ATTACHMENTS) |
828 or mess_data["type"] == C.MESS_TYPE_INFO | 858 or mess_data["type"] == C.MESS_TYPE_INFO |
829 ) | 859 ) |
830 | 860 |
831 async def message_add_to_history(self, data): | 861 async def message_add_to_history(self, data): |
847 ) # empty body should be managed by plugins before this point | 877 ) # empty body should be managed by plugins before this point |
848 return data | 878 return data |
849 | 879 |
850 def message_get_bridge_args(self, data): | 880 def message_get_bridge_args(self, data): |
851 """Generate args to use with bridge from data dict""" | 881 """Generate args to use with bridge from data dict""" |
852 return (data["uid"], data["timestamp"], data["from"].full(), | 882 return ( |
853 data["to"].full(), data["message"], data["subject"], | 883 data["uid"], |
854 data["type"], data_format.serialise(data["extra"])) | 884 data["timestamp"], |
855 | 885 data["from"].full(), |
886 data["to"].full(), | |
887 data["message"], | |
888 data["subject"], | |
889 data["type"], | |
890 data_format.serialise(data["extra"]), | |
891 ) | |
856 | 892 |
857 def message_send_to_bridge(self, data): | 893 def message_send_to_bridge(self, data): |
858 """Send message to bridge, so frontends can display it | 894 """Send message to bridge, so frontends can display it |
859 | 895 |
860 @param data: message data dictionnary | 896 @param data: message data dictionnary |
867 # we need a message to send something | 903 # we need a message to send something |
868 if self.is_message_printable(data): | 904 if self.is_message_printable(data): |
869 | 905 |
870 # We send back the message, so all frontends are aware of it | 906 # We send back the message, so all frontends are aware of it |
871 self.host_app.bridge.message_new( | 907 self.host_app.bridge.message_new( |
872 *self.message_get_bridge_args(data), | 908 *self.message_get_bridge_args(data), profile=self.profile |
873 profile=self.profile | |
874 ) | 909 ) |
875 else: | 910 else: |
876 log.warning(_("No message found")) | 911 log.warning(_("No message found")) |
877 return data | 912 return data |
878 | 913 |
898 @implementer(iwokkel.IDisco) | 933 @implementer(iwokkel.IDisco) |
899 class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient): | 934 class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient): |
900 trigger_suffix = "" | 935 trigger_suffix = "" |
901 is_component = False | 936 is_component = False |
902 | 937 |
903 def __init__(self, host_app, profile, user_jid, password, host=None, | 938 def __init__( |
904 port=C.XMPP_C2S_PORT, max_retries=C.XMPP_MAX_RETRIES): | 939 self, |
940 host_app, | |
941 profile, | |
942 user_jid, | |
943 password, | |
944 host=None, | |
945 port=C.XMPP_C2S_PORT, | |
946 max_retries=C.XMPP_MAX_RETRIES, | |
947 ): | |
905 # XXX: DNS SRV records are checked when the host is not specified. | 948 # XXX: DNS SRV records are checked when the host is not specified. |
906 # If no SRV record is found, the host is directly extracted from the JID. | 949 # If no SRV record is found, the host is directly extracted from the JID. |
907 self.started = time.time() | 950 self.started = time.time() |
908 | 951 |
909 # Currently, we use "client/pc/Salut à Toi", but as | 952 # Currently, we use "client/pc/Salut à Toi", but as |
931 _("invalid data used for host: {data}").format(data=host_data) | 974 _("invalid data used for host: {data}").format(data=host_data) |
932 ) | 975 ) |
933 host_data = None | 976 host_data = None |
934 if host_data is not None: | 977 if host_data is not None: |
935 log.info( | 978 log.info( |
936 "using {host}:{port} for host {host_ori} as requested in config" | 979 "using {host}:{port} for host {host_ori} as requested in config".format( |
937 .format(host_ori=user_jid.host, host=host, port=port) | 980 host_ori=user_jid.host, host=host, port=port |
981 ) | |
938 ) | 982 ) |
939 | 983 |
940 self.check_certificate = host_app.memory.param_get_a( | 984 self.check_certificate = host_app.memory.param_get_a( |
941 "check_certificate", "Connection", profile_key=profile) | 985 "check_certificate", "Connection", profile_key=profile |
986 ) | |
942 | 987 |
943 if self.check_certificate: | 988 if self.check_certificate: |
944 tls_required, configurationForTLS = True, None | 989 tls_required, configurationForTLS = True, None |
945 else: | 990 else: |
946 tls_required = False | 991 tls_required = False |
947 configurationForTLS = ssl.CertificateOptions(trustRoot=None) | 992 configurationForTLS = ssl.CertificateOptions(trustRoot=None) |
948 | 993 |
949 wokkel_client.XMPPClient.__init__( | 994 wokkel_client.XMPPClient.__init__( |
950 self, user_jid, password, host or None, port or C.XMPP_C2S_PORT, | 995 self, |
951 tls_required=tls_required, configurationForTLS=configurationForTLS | 996 user_jid, |
997 password, | |
998 host or None, | |
999 port or C.XMPP_C2S_PORT, | |
1000 tls_required=tls_required, | |
1001 configurationForTLS=configurationForTLS, | |
952 ) | 1002 ) |
953 SatXMPPEntity.__init__(self, host_app, profile, max_retries) | 1003 SatXMPPEntity.__init__(self, host_app, profile, max_retries) |
954 | 1004 |
955 if not self.check_certificate: | 1005 if not self.check_certificate: |
956 msg = (_("Certificate validation is deactivated, this is unsecure and " | 1006 msg = _( |
1007 "Certificate validation is deactivated, this is unsecure and " | |
957 "somebody may be spying on you. If you have no good reason to disable " | 1008 "somebody may be spying on you. If you have no good reason to disable " |
958 "certificate validation, please activate \"Check certificate\" in your " | 1009 'certificate validation, please activate "Check certificate" in your ' |
959 "settings in \"Connection\" tab.")) | 1010 'settings in "Connection" tab.' |
960 xml_tools.quick_note(host_app, self, msg, _("Security notice"), | 1011 ) |
961 level = C.XMLUI_DATA_LVL_WARNING) | 1012 xml_tools.quick_note( |
1013 host_app, self, msg, _("Security notice"), level=C.XMLUI_DATA_LVL_WARNING | |
1014 ) | |
962 | 1015 |
963 @property | 1016 @property |
964 def server_jid(self): | 1017 def server_jid(self): |
965 return jid.JID(self.jid.host) | 1018 return jid.JID(self.jid.host) |
966 | 1019 |
1000 lambda ret: defer.ensureDeferred(self.message_add_to_history(ret)) | 1053 lambda ret: defer.ensureDeferred(self.message_add_to_history(ret)) |
1001 ) | 1054 ) |
1002 post_xml_treatments.addCallback(self.message_send_to_bridge) | 1055 post_xml_treatments.addCallback(self.message_send_to_bridge) |
1003 | 1056 |
1004 def feedback( | 1057 def feedback( |
1005 self, | 1058 self, to_jid: jid.JID, message: str, extra: Optional[ExtraDict] = None |
1006 to_jid: jid.JID, | |
1007 message: str, | |
1008 extra: Optional[ExtraDict] = None | |
1009 ) -> None: | 1059 ) -> None: |
1010 """Send message to frontends | 1060 """Send message to frontends |
1011 | 1061 |
1012 This message will be an info message, not recorded in history. | 1062 This message will be an info message, not recorded in history. |
1013 It can be used to give feedback of a command | 1063 It can be used to give feedback of a command |
1043 An entry point plugin is launched after component is connected. | 1093 An entry point plugin is launched after component is connected. |
1044 Component need to instantiate MessageProtocol itself | 1094 Component need to instantiate MessageProtocol itself |
1045 """ | 1095 """ |
1046 | 1096 |
1047 trigger_suffix = ( | 1097 trigger_suffix = ( |
1048 "Component" | 1098 "Component" # used for to distinguish some trigger points set in SatXMPPEntity |
1049 ) # used for to distinguish some trigger points set in SatXMPPEntity | 1099 ) |
1050 is_component = True | 1100 is_component = True |
1051 # XXX: set to True from entry plugin to keep messages in history for sent messages | 1101 # XXX: set to True from entry plugin to keep messages in history for sent messages |
1052 sendHistory = False | 1102 sendHistory = False |
1053 # XXX: same as sendHistory but for received messaged | 1103 # XXX: same as sendHistory but for received messaged |
1054 receiveHistory = False | 1104 receiveHistory = False |
1055 | 1105 |
1056 def __init__(self, host_app, profile, component_jid, password, host=None, port=None, | 1106 def __init__( |
1057 max_retries=C.XMPP_MAX_RETRIES): | 1107 self, |
1108 host_app, | |
1109 profile, | |
1110 component_jid, | |
1111 password, | |
1112 host=None, | |
1113 port=None, | |
1114 max_retries=C.XMPP_MAX_RETRIES, | |
1115 ): | |
1058 self.started = time.time() | 1116 self.started = time.time() |
1059 if port is None: | 1117 if port is None: |
1060 port = C.XMPP_COMPONENT_PORT | 1118 port = C.XMPP_COMPONENT_PORT |
1061 | 1119 |
1062 ## entry point ## | 1120 ## entry point ## |
1176 plugin to your dependencies. | 1234 plugin to your dependencies. |
1177 A "user" part must be present in "to_jid" (otherwise, the component itself is addressed) | 1235 A "user" part must be present in "to_jid" (otherwise, the component itself is addressed) |
1178 @param to_jid: destination JID of the request | 1236 @param to_jid: destination JID of the request |
1179 """ | 1237 """ |
1180 try: | 1238 try: |
1181 unescape = self.host_app.plugins['XEP-0106'].unescape | 1239 unescape = self.host_app.plugins["XEP-0106"].unescape |
1182 except KeyError: | 1240 except KeyError: |
1183 raise exceptions.MissingPlugin("Plugin XEP-0106 is needed to retrieve owner") | 1241 raise exceptions.MissingPlugin("Plugin XEP-0106 is needed to retrieve owner") |
1184 else: | 1242 else: |
1185 user = unescape(to_jid.user) | 1243 user = unescape(to_jid.user) |
1186 if '@' in user: | 1244 if "@" in user: |
1187 # a full jid is specified | 1245 # a full jid is specified |
1188 return jid.JID(user) | 1246 return jid.JID(user) |
1189 else: | 1247 else: |
1190 # only user part is specified, we use our own host to build the full jid | 1248 # only user part is specified, we use our own host to build the full jid |
1191 return jid.JID(None, (user, self.host, None)) | 1249 return jid.JID(None, (user, self.host, None)) |
1197 with our host. | 1255 with our host. |
1198 Peer jid is the requesting jid from the IQ element | 1256 Peer jid is the requesting jid from the IQ element |
1199 @param iq_elt: IQ stanza sent from the requested | 1257 @param iq_elt: IQ stanza sent from the requested |
1200 @return: owner and peer JIDs | 1258 @return: owner and peer JIDs |
1201 """ | 1259 """ |
1202 to_jid = jid.JID(iq_elt['to']) | 1260 to_jid = jid.JID(iq_elt["to"]) |
1203 if to_jid.user: | 1261 if to_jid.user: |
1204 owner = self.get_owner_from_jid(to_jid) | 1262 owner = self.get_owner_from_jid(to_jid) |
1205 else: | 1263 else: |
1206 owner = jid.JID(iq_elt["from"]).userhostJID() | 1264 owner = jid.JID(iq_elt["from"]).userhostJID() |
1207 | 1265 |
1225 class SatMessageProtocol(xmppim.MessageProtocol): | 1283 class SatMessageProtocol(xmppim.MessageProtocol): |
1226 | 1284 |
1227 def __init__(self, host): | 1285 def __init__(self, host): |
1228 xmppim.MessageProtocol.__init__(self) | 1286 xmppim.MessageProtocol.__init__(self) |
1229 self.host = host | 1287 self.host = host |
1230 self.messages_queue = defer.DeferredQueue() | 1288 self.messages_queue = defer.DeferredQueue() |
1231 | 1289 |
1232 def setHandlerParent(self, parent): | 1290 def setHandlerParent(self, parent): |
1233 super().setHandlerParent(parent) | 1291 super().setHandlerParent(parent) |
1234 defer.ensureDeferred(self.process_messages()) | 1292 defer.ensureDeferred(self.process_messages()) |
1235 | 1293 |
1250 @param client(SatXMPPClient, None): client to map message id to uid | 1308 @param client(SatXMPPClient, None): client to map message id to uid |
1251 if None, mapping will not be done | 1309 if None, mapping will not be done |
1252 @return(dict): message data | 1310 @return(dict): message data |
1253 """ | 1311 """ |
1254 if message_elt.name != "message": | 1312 if message_elt.name != "message": |
1255 log.warning(_( | 1313 log.warning( |
1256 "parse_message used with a non <message/> stanza, ignoring: {xml}" | 1314 _( |
1257 .format(xml=message_elt.toXml()))) | 1315 "parse_message used with a non <message/> stanza, ignoring: {xml}".format( |
1316 xml=message_elt.toXml() | |
1317 ) | |
1318 ) | |
1319 ) | |
1258 return {} | 1320 return {} |
1259 | 1321 |
1260 if message_elt.uri == None: | 1322 if message_elt.uri == None: |
1261 # xmlns may be None when wokkel element parsing strip out root namespace | 1323 # xmlns may be None when wokkel element parsing strip out root namespace |
1262 self.normalize_ns(message_elt, None) | 1324 self.normalize_ns(message_elt, None) |
1263 elif message_elt.uri != C.NS_CLIENT: | 1325 elif message_elt.uri != C.NS_CLIENT: |
1264 log.warning(_( | 1326 log.warning( |
1265 "received <message> with a wrong namespace: {xml}" | 1327 _( |
1266 .format(xml=message_elt.toXml()))) | 1328 "received <message> with a wrong namespace: {xml}".format( |
1329 xml=message_elt.toXml() | |
1330 ) | |
1331 ) | |
1332 ) | |
1267 | 1333 |
1268 client = self.parent | 1334 client = self.parent |
1269 | 1335 |
1270 if not message_elt.hasAttribute('to'): | 1336 if not message_elt.hasAttribute("to"): |
1271 message_elt['to'] = client.jid.full() | 1337 message_elt["to"] = client.jid.full() |
1272 | 1338 |
1273 message = {} | 1339 message = {} |
1274 subject = {} | 1340 subject = {} |
1275 extra = {} | 1341 extra = {} |
1276 data: MessageData = { | 1342 data: MessageData = { |
1304 try: | 1370 try: |
1305 received_timestamp = message_elt._received_timestamp | 1371 received_timestamp = message_elt._received_timestamp |
1306 except AttributeError: | 1372 except AttributeError: |
1307 # message_elt._received_timestamp should have been set in onMessage | 1373 # message_elt._received_timestamp should have been set in onMessage |
1308 # but if parse_message is called directly, it can be missing | 1374 # but if parse_message is called directly, it can be missing |
1309 log.debug("missing received timestamp for {message_elt}".format( | 1375 log.debug( |
1310 message_elt=message_elt)) | 1376 "missing received timestamp for {message_elt}".format( |
1377 message_elt=message_elt | |
1378 ) | |
1379 ) | |
1311 received_timestamp = time.time() | 1380 received_timestamp = time.time() |
1312 | 1381 |
1313 try: | 1382 try: |
1314 delay_elt = next(message_elt.elements(delay.NS_DELAY, "delay")) | 1383 delay_elt = next(message_elt.elements(delay.NS_DELAY, "delay")) |
1315 except StopIteration: | 1384 except StopIteration: |
1319 data["timestamp"] = calendar.timegm(parsed_delay.stamp.utctimetuple()) | 1388 data["timestamp"] = calendar.timegm(parsed_delay.stamp.utctimetuple()) |
1320 data["received_timestamp"] = received_timestamp | 1389 data["received_timestamp"] = received_timestamp |
1321 if parsed_delay.sender: | 1390 if parsed_delay.sender: |
1322 data["delay_sender"] = parsed_delay.sender.full() | 1391 data["delay_sender"] = parsed_delay.sender.full() |
1323 | 1392 |
1324 self.host.trigger.point("message_parse", client, message_elt, data) | 1393 self.host.trigger.point("message_parse", client, message_elt, data) |
1325 return data | 1394 return data |
1326 | |
1327 | 1395 |
1328 def onMessage(self, message_elt: domish.Element) -> None: | 1396 def onMessage(self, message_elt: domish.Element) -> None: |
1329 message_elt._received_timestamp = time.time() | 1397 message_elt._received_timestamp = time.time() |
1330 self.messages_queue.put(message_elt) | 1398 self.messages_queue.put(message_elt) |
1331 | 1399 |
1345 await self.process_message(client, message_elt) | 1413 await self.process_message(client, message_elt) |
1346 except Exception: | 1414 except Exception: |
1347 log.exception(f"Can't process message {message_elt.toXml()}") | 1415 log.exception(f"Can't process message {message_elt.toXml()}") |
1348 | 1416 |
1349 def _on_processing_timeout( | 1417 def _on_processing_timeout( |
1350 self, | 1418 self, message_elt: domish.Element, async_point_d: defer.Deferred |
1351 message_elt: domish.Element, | |
1352 async_point_d: defer.Deferred | |
1353 ) -> None: | 1419 ) -> None: |
1354 log.error( | 1420 log.error( |
1355 "Processing of following message took too long, cancelling:" | 1421 "Processing of following message took too long, cancelling:" |
1356 f"{message_elt.toXml()}" | 1422 f"{message_elt.toXml()}" |
1357 ) | 1423 ) |
1358 async_point_d.cancel() | 1424 async_point_d.cancel() |
1359 | 1425 |
1360 async def process_message( | 1426 async def process_message( |
1361 self, | 1427 self, client: SatXMPPEntity, message_elt: domish.Element |
1362 client: SatXMPPEntity, | |
1363 message_elt: domish.Element | |
1364 ) -> None: | 1428 ) -> None: |
1365 # TODO: handle threads | 1429 # TODO: handle threads |
1366 if not "from" in message_elt.attributes: | 1430 if not "from" in message_elt.attributes: |
1367 message_elt["from"] = client.jid.host | 1431 message_elt["from"] = client.jid.host |
1368 log.debug(_("got message from: {from_}").format(from_=message_elt["from"])) | 1432 log.debug(_("got message from: {from_}").format(from_=message_elt["from"])) |
1370 # we use client namespace all the time to simplify parsing | 1434 # we use client namespace all the time to simplify parsing |
1371 self.normalize_ns(message_elt, component.NS_COMPONENT_ACCEPT) | 1435 self.normalize_ns(message_elt, component.NS_COMPONENT_ACCEPT) |
1372 | 1436 |
1373 # plugin can add their treatments to this deferred | 1437 # plugin can add their treatments to this deferred |
1374 post_treat = defer.Deferred() | 1438 post_treat = defer.Deferred() |
1375 async_point_d = defer.ensureDeferred(self.host.trigger.async_point( | 1439 async_point_d = defer.ensureDeferred( |
1376 "message_received", client, message_elt, post_treat | 1440 self.host.trigger.async_point( |
1377 )) | 1441 "message_received", client, message_elt, post_treat |
1442 ) | |
1443 ) | |
1378 # message_received triggers block the messages queue, so they must not take too | 1444 # message_received triggers block the messages queue, so they must not take too |
1379 # long to proceed. | 1445 # long to proceed. |
1380 delayed_call = reactor.callLater( | 1446 delayed_call = reactor.callLater( |
1381 10, | 1447 10, self._on_processing_timeout, message_elt, async_point_d |
1382 self._on_processing_timeout, | |
1383 message_elt, | |
1384 async_point_d | |
1385 ) | 1448 ) |
1386 trigger_ret_continue = await async_point_d | 1449 trigger_ret_continue = await async_point_d |
1387 | 1450 |
1388 if delayed_call.active(): | 1451 if delayed_call.active(): |
1389 delayed_call.cancel() | 1452 delayed_call.cancel() |
1409 except exceptions.CancelError: | 1472 except exceptions.CancelError: |
1410 pass | 1473 pass |
1411 | 1474 |
1412 def complete_attachments(self, data: MessageData) -> MessageData: | 1475 def complete_attachments(self, data: MessageData) -> MessageData: |
1413 """Complete missing metadata of attachments""" | 1476 """Complete missing metadata of attachments""" |
1414 for attachment in data['extra'].get(C.KEY_ATTACHMENTS, []): | 1477 for attachment in data["extra"].get(C.KEY_ATTACHMENTS, []): |
1415 if "name" not in attachment and "url" in attachment: | 1478 if "name" not in attachment and "url" in attachment: |
1416 name = (Path(unquote(urlparse(attachment['url']).path)).name | 1479 name = ( |
1417 or C.FILE_DEFAULT_NAME) | 1480 Path(unquote(urlparse(attachment["url"]).path)).name |
1481 or C.FILE_DEFAULT_NAME | |
1482 ) | |
1418 attachment["name"] = name | 1483 attachment["name"] = name |
1419 if ((C.KEY_ATTACHMENTS_MEDIA_TYPE not in attachment | 1484 if C.KEY_ATTACHMENTS_MEDIA_TYPE not in attachment and "name" in attachment: |
1420 and "name" in attachment)): | 1485 media_type = mimetypes.guess_type(attachment["name"], strict=False)[0] |
1421 media_type = mimetypes.guess_type(attachment['name'], strict=False)[0] | |
1422 if media_type: | 1486 if media_type: |
1423 attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type | 1487 attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type |
1424 return data | 1488 return data |
1425 | 1489 |
1426 async def add_to_history(self, data: MessageData) -> MessageData: | 1490 async def add_to_history(self, data: MessageData) -> MessageData: |
1430 else: | 1494 else: |
1431 # we need a message to store | 1495 # we need a message to store |
1432 if self.parent.is_message_printable(data): | 1496 if self.parent.is_message_printable(data): |
1433 return await self.host.memory.add_to_history(self.parent, data) | 1497 return await self.host.memory.add_to_history(self.parent, data) |
1434 else: | 1498 else: |
1435 log.debug("not storing empty message to history: {data}" | 1499 log.debug( |
1436 .format(data=data)) | 1500 "not storing empty message to history: {data}".format(data=data) |
1501 ) | |
1437 return data | 1502 return data |
1438 | 1503 |
1439 def bridge_signal(self, data: MessageData) -> MessageData: | 1504 def bridge_signal(self, data: MessageData) -> MessageData: |
1440 """Send signal to frontends for the given message""" | 1505 """Send signal to frontends for the given message""" |
1441 try: | 1506 try: |
1457 data["type"], | 1522 data["type"], |
1458 data_format.serialise(data["extra"]), | 1523 data_format.serialise(data["extra"]), |
1459 profile=self.parent.profile, | 1524 profile=self.parent.profile, |
1460 ) | 1525 ) |
1461 else: | 1526 else: |
1462 log.debug("Discarding bridge signal for empty message: {data}".format( | 1527 log.debug( |
1463 data=data)) | 1528 "Discarding bridge signal for empty message: {data}".format(data=data) |
1529 ) | |
1464 return data | 1530 return data |
1465 | 1531 |
1466 | 1532 |
1467 class LiberviaRosterProtocol(xmppim.RosterClientProtocol): | 1533 class LiberviaRosterProtocol(xmppim.RosterClientProtocol): |
1468 | 1534 |
1478 return self.is_jid_in_roster(entity_jid) | 1544 return self.is_jid_in_roster(entity_jid) |
1479 | 1545 |
1480 @property | 1546 @property |
1481 def versioning(self): | 1547 def versioning(self): |
1482 """True if server support roster versioning""" | 1548 """True if server support roster versioning""" |
1483 return (NS_ROSTER_VER, 'ver') in self.parent.xmlstream.features | 1549 return (NS_ROSTER_VER, "ver") in self.parent.xmlstream.features |
1484 | 1550 |
1485 @property | 1551 @property |
1486 def roster_cache(self): | 1552 def roster_cache(self): |
1487 """Cache of roster from storage | 1553 """Cache of roster from storage |
1488 | 1554 |
1545 self._groups.clear() | 1611 self._groups.clear() |
1546 yield self.request_roster() | 1612 yield self.request_roster() |
1547 | 1613 |
1548 @defer.inlineCallbacks | 1614 @defer.inlineCallbacks |
1549 def request_roster(self): | 1615 def request_roster(self): |
1550 """Ask the server for Roster list """ | 1616 """Ask the server for Roster list""" |
1551 if self.versioning: | 1617 if self.versioning: |
1552 log.info(_("our server support roster versioning, we use it")) | 1618 log.info(_("our server support roster versioning, we use it")) |
1553 roster_cache = self.roster_cache | 1619 roster_cache = self.roster_cache |
1554 yield roster_cache.load() | 1620 yield roster_cache.load() |
1555 try: | 1621 try: |
1563 # we deserialise cached roster to our local cache | 1629 # we deserialise cached roster to our local cache |
1564 for roster_jid_s, roster_item_elt_s in roster_cache.items(): | 1630 for roster_jid_s, roster_item_elt_s in roster_cache.items(): |
1565 if roster_jid_s == ROSTER_VER_KEY: | 1631 if roster_jid_s == ROSTER_VER_KEY: |
1566 continue | 1632 continue |
1567 roster_jid = jid.JID(roster_jid_s) | 1633 roster_jid = jid.JID(roster_jid_s) |
1568 roster_item_elt = generic.parseXml(roster_item_elt_s.encode('utf-8')) | 1634 roster_item_elt = generic.parseXml(roster_item_elt_s.encode("utf-8")) |
1569 roster_item = xmppim.RosterItem.fromElement(roster_item_elt) | 1635 roster_item = xmppim.RosterItem.fromElement(roster_item_elt) |
1570 self._jids[roster_jid] = roster_item | 1636 self._jids[roster_jid] = roster_item |
1571 self._register_item(roster_item) | 1637 self._register_item(roster_item) |
1572 else: | 1638 else: |
1573 log.warning(_("our server doesn't support roster versioning")) | 1639 log.warning(_("our server doesn't support roster versioning")) |
1574 version = None | 1640 version = None |
1575 | 1641 |
1576 log.debug("requesting roster") | 1642 log.debug("requesting roster") |
1577 roster = yield self.getRoster(version=version) | 1643 roster = yield self.getRoster(version=version) |
1578 if roster is None: | 1644 if roster is None: |
1579 log.debug("empty roster result received, we'll get roster item with roster " | 1645 log.debug( |
1580 "pushes") | 1646 "empty roster result received, we'll get roster item with roster " |
1647 "pushes" | |
1648 ) | |
1581 else: | 1649 else: |
1582 # a full roster is received | 1650 # a full roster is received |
1583 self._groups.clear() | 1651 self._groups.clear() |
1584 self._jids = roster | 1652 self._jids = roster |
1585 for item in roster.values(): | 1653 for item in roster.values(): |
1587 # XXX: current behaviour: we don't want contact in our roster list | 1655 # XXX: current behaviour: we don't want contact in our roster list |
1588 # if there is no presence subscription | 1656 # if there is no presence subscription |
1589 # may change in the future | 1657 # may change in the future |
1590 log.info( | 1658 log.info( |
1591 "Removing contact {} from roster because there is no presence " | 1659 "Removing contact {} from roster because there is no presence " |
1592 "subscription".format( | 1660 "subscription".format(item.jid) |
1593 item.jid | |
1594 ) | |
1595 ) | 1661 ) |
1596 self.removeItem(item.entity) # FIXME: to be checked | 1662 self.removeItem(item.entity) # FIXME: to be checked |
1597 else: | 1663 else: |
1598 self._register_item(item) | 1664 self._register_item(item) |
1599 yield self._cache_roster(roster.version) | 1665 yield self._cache_roster(roster.version) |
1644 except KeyError: | 1710 except KeyError: |
1645 pass # no previous item registration (or it's been cleared) | 1711 pass # no previous item registration (or it's been cleared) |
1646 self._jids[entity] = item | 1712 self._jids[entity] = item |
1647 self._register_item(item) | 1713 self._register_item(item) |
1648 self.host.bridge.contact_new( | 1714 self.host.bridge.contact_new( |
1649 entity.full(), self.get_attributes(item), list(item.groups), | 1715 entity.full(), |
1650 self.parent.profile | 1716 self.get_attributes(item), |
1717 list(item.groups), | |
1718 self.parent.profile, | |
1651 ) | 1719 ) |
1652 | 1720 |
1653 def removeReceived(self, request): | 1721 def removeReceived(self, request): |
1654 entity = request.item.entity | 1722 entity = request.item.entity |
1655 log.info(_("removing {entity} from roster").format(entity=entity.full())) | 1723 log.info(_("removing {entity} from roster").format(entity=entity.full())) |
1708 | 1776 |
1709 def is_jid_in_roster(self, entity_jid): | 1777 def is_jid_in_roster(self, entity_jid): |
1710 """Return True if jid is in roster""" | 1778 """Return True if jid is in roster""" |
1711 if not isinstance(entity_jid, jid.JID): | 1779 if not isinstance(entity_jid, jid.JID): |
1712 raise exceptions.InternalError( | 1780 raise exceptions.InternalError( |
1713 f"a JID is expected, not {type(entity_jid)}: {entity_jid!r}") | 1781 f"a JID is expected, not {type(entity_jid)}: {entity_jid!r}" |
1782 ) | |
1714 return entity_jid in self._jids | 1783 return entity_jid in self._jids |
1715 | 1784 |
1716 def is_subscribed_from(self, entity_jid: jid.JID) -> bool: | 1785 def is_subscribed_from(self, entity_jid: jid.JID) -> bool: |
1717 """Return True if entity is authorised to see our presence""" | 1786 """Return True if entity is authorised to see our presence""" |
1718 try: | 1787 try: |
1823 | 1892 |
1824 if None in statuses: # we only want string keys | 1893 if None in statuses: # we only want string keys |
1825 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) | 1894 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) |
1826 | 1895 |
1827 if not self.host.trigger.point( | 1896 if not self.host.trigger.point( |
1828 "presence_received", self.parent, entity, C.PRESENCE_UNAVAILABLE, 0, statuses, | 1897 "presence_received", |
1898 self.parent, | |
1899 entity, | |
1900 C.PRESENCE_UNAVAILABLE, | |
1901 0, | |
1902 statuses, | |
1829 ): | 1903 ): |
1830 return | 1904 return |
1831 | 1905 |
1832 # now it's time to notify frontends | 1906 # now it's time to notify frontends |
1833 # if the entity is not known yet in this session or is already unavailable, | 1907 # if the entity is not known yet in this session or is already unavailable, |
1834 # there is no need to send an unavailable signal | 1908 # there is no need to send an unavailable signal |
1835 try: | 1909 try: |
1836 presence = self.host.memory.get_entity_datum( | 1910 presence = self.host.memory.get_entity_datum(self.client, entity, "presence") |
1837 self.client, entity, "presence" | |
1838 ) | |
1839 except (KeyError, exceptions.UnknownEntityError): | 1911 except (KeyError, exceptions.UnknownEntityError): |
1840 # the entity has not been seen yet in this session | 1912 # the entity has not been seen yet in this session |
1841 pass | 1913 pass |
1842 else: | 1914 else: |
1843 if presence.show != C.PRESENCE_UNAVAILABLE: | 1915 if presence.show != C.PRESENCE_UNAVAILABLE: |
1949 disco.DiscoClientProtocol.__init__(self) | 2021 disco.DiscoClientProtocol.__init__(self) |
1950 | 2022 |
1951 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): | 2023 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): |
1952 # those features are implemented in Wokkel (or sat_tmp.wokkel) | 2024 # those features are implemented in Wokkel (or sat_tmp.wokkel) |
1953 # and thus are always available | 2025 # and thus are always available |
1954 return [disco.DiscoFeature(NS_X_DATA), | 2026 return [ |
1955 disco.DiscoFeature(NS_XML_ELEMENT), | 2027 disco.DiscoFeature(NS_X_DATA), |
1956 disco.DiscoFeature(NS_DISCO_INFO)] | 2028 disco.DiscoFeature(NS_XML_ELEMENT), |
2029 disco.DiscoFeature(NS_DISCO_INFO), | |
2030 ] | |
1957 | 2031 |
1958 def getDiscoItems(self, requestor, target, nodeIdentifier=""): | 2032 def getDiscoItems(self, requestor, target, nodeIdentifier=""): |
1959 return [] | 2033 return [] |
1960 | 2034 |
1961 | 2035 |
1983 | 2057 |
1984 | 2058 |
1985 @implementer(iwokkel.IDisco) | 2059 @implementer(iwokkel.IDisco) |
1986 class SatIdentityHandler(XMPPHandler): | 2060 class SatIdentityHandler(XMPPHandler): |
1987 """Manage disco Identity of SàT.""" | 2061 """Manage disco Identity of SàT.""" |
2062 | |
1988 # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have | 2063 # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have |
1989 # several identities | 2064 # several identities |
1990 | 2065 |
1991 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): | 2066 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): |
1992 return self.parent.identities | 2067 return self.parent.identities |