Compare commits
	
		
			82 Commits
		
	
	
		
			adhoc-exec
			...
			slix-1.5.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0dd32be7f5 | ||
|   | bf69698af1 | ||
|   | aa732b3c94 | ||
|   | d076cef023 | ||
|   | f884b67b8b | ||
|   | 0d3116dbdf | ||
|   | f1ab9ab964 | ||
|   | e520ab1f5e | ||
|   | 3dcb96d9d8 | ||
|   | 0a7a4c3abe | ||
|   | a4bbc404ed | ||
|   | c3fbc6cb80 | ||
|   | 355d789061 | ||
|   | 47ed67c04e | ||
|   | 34567f450a | ||
|   | 9126bd8392 | ||
|   | 02202f7cd8 | ||
|   | 2add94f5b0 | ||
|   | 5fc757f200 | ||
|   | 98108d0445 | ||
|   | 76f4fb49d6 | ||
|   | 5be46a5e68 | ||
|   | ab9040c30e | ||
|   | a16e2a0f6c | ||
|   | 842aa3be8f | ||
|   | 6c28b49e7f | ||
|   | 621255027d | ||
|   | efe316dc8c | ||
|   | e9a87a0b77 | ||
|   | 85c9967b9c | ||
|   | deb6d4f176 | ||
|   | 7218bb4499 | ||
|   | d85efec7a2 | ||
|   | 115c234527 | ||
|   | a0f5cb6e09 | ||
|   | 110bbf8afc | ||
|   | d97efa0bd8 | ||
|   | 672f1b28f6 | ||
|   | 27d3ae958b | ||
|   | a32794ec35 | ||
|   | aa11ba463e | ||
|   | a83c00e933 | ||
|   | 31f6ef6814 | ||
|   | 9b3874b5df | ||
|   | 0139fb291e | ||
|   | e58988484a | ||
|   | 5d5e5cda19 | ||
|   | 11f707987d | ||
|   | db13794e0f | ||
|   | 37bc1bb9b3 | ||
|   | 9be30e5291 | ||
|   | 9fe20a4056 | ||
|   | 3253d34c0a | ||
|   | fef575ee1a | ||
|   | 540ff89427 | ||
|   | dd8ac8fc87 | ||
|   | 2249d878d1 | ||
|   | 89fa9dc1dd | ||
|   | d7729e8683 | ||
|   | d618f55dea | ||
|   | b0e688eb35 | ||
|   | 0e7176483b | ||
|   | f35569a2c1 | ||
|   | bec6f7c8f3 | ||
|   | 027ce2434d | ||
|   | d57fbb57a2 | ||
|   | 85cd7a9166 | ||
|   | d50d996c68 | ||
|   | 371ad20ca7 | ||
|   | 5f49df6b56 | ||
|   | b50bfb2f34 | ||
|   | b29bb30eb7 | ||
|   | 4435c81d77 | ||
|   | 2638ba2744 | ||
|   | dbc9758311 | ||
|   | 47968963b1 | ||
|   | 4e8800f954 | ||
|   | 40053518aa | ||
|   | 1ee0f72ead | ||
|   | 4bb81228ae | ||
|   | 60a7a5b8df | ||
|   | 946674f424 | 
							
								
								
									
										13
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| ################ Please use Gitlab instead of Github ################################### | ||||
|  | ||||
| Hello, thank you for contributing to slixmpp! | ||||
|  | ||||
| You’re about to open a pull request on github. However this github repository is not the official place for contributions on slixmpp. | ||||
|  | ||||
| Please open your merge request on https://lab.louiz.org/poezio/slixmpp/ | ||||
|  | ||||
| You should be able to log in there with your github credentials, clone the slixmpp repository in your namespace, push your existing pull request into a new branch, and then open a merge request with one click, within 3 minutes. | ||||
|  | ||||
| This will help us review your contribution, avoid spreading things everywhere and it will even run the tests automatically with your changes. | ||||
|  | ||||
| Thank you. | ||||
| @@ -1,9 +1,7 @@ | ||||
| language: python | ||||
| python: | ||||
|   - "3.4" | ||||
|   - "3.5" | ||||
|   - "3.6" | ||||
|   - "3.7-dev" | ||||
|   - "3.7" | ||||
|   - "3.8-dev" | ||||
| install: | ||||
|   - "pip install ." | ||||
| script: testall.py | ||||
|   | ||||
							
								
								
									
										2
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| Pre-requisites: | ||||
| - Python 3.5+ | ||||
| - Python 3.7+ | ||||
| - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) | ||||
| - GnuPG, for testing | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| Slixmpp | ||||
| ######### | ||||
|  | ||||
| Slixmpp is an MIT licensed XMPP library for Python 3.5+. It is a fork of | ||||
| Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of | ||||
| SleekXMPP. | ||||
|  | ||||
| Slixmpp's goals is to only rewrite the core of the library (the low level | ||||
|   | ||||
							
								
								
									
										21
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							| @@ -408,24 +408,3 @@ div.viewcode-block:target { | ||||
|     margin: -1px -12px; | ||||
|     padding: 0 12px; | ||||
| } | ||||
|  | ||||
| #from_andyet { | ||||
|     -webkit-box-shadow: #CCC 0px 0px 3px; | ||||
|     background: rgba(255, 255, 255, 1); | ||||
|     bottom: 0px; | ||||
|     right: 17px; | ||||
|     padding: 3px 10px; | ||||
|     position: fixed; | ||||
| } | ||||
|  | ||||
| #from_andyet h2 { | ||||
|     background-image: url("images/from_&yet.png"); | ||||
|     background-repeat: no-repeat; | ||||
|     height: 29px; | ||||
|     line-height: 0; | ||||
|     text-indent: -9999em; | ||||
|     width: 79px; | ||||
|     margin-top: 0; | ||||
|     margin: 0px; | ||||
|     padding: 0px; | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
								
							| @@ -65,6 +65,5 @@ | ||||
|       <div class="bottomnav"> | ||||
|       {{ nav() }} | ||||
|       </div> | ||||
|       <a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a> | ||||
| {% endblock %} | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ hides namespaces when able and does not introduce excessive namespace | ||||
| prefixes:: | ||||
|  | ||||
|     >>> from slixmpp.xmlstream.tostring import tostring | ||||
|     >>> from xml.etree import cElementTree as ET | ||||
|     >>> from xml.etree import ElementTree as ET | ||||
|     >>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>') | ||||
|     >>> ET.tostring(xml) | ||||
|     '<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>' | ||||
|   | ||||
| @@ -3,8 +3,9 @@ | ||||
| Differences from SleekXMPP | ||||
| ========================== | ||||
|  | ||||
| **Python 3.5+ only** | ||||
|     slixmpp will only work on python 3.5 and above. | ||||
| **Python 3.7+ only** | ||||
|     slixmpp will work on python 3.7 and above. It may work with previous | ||||
|     versions but we provide no guarantees. | ||||
|  | ||||
| **Stanza copies** | ||||
|     The same stanza object is given through all the handlers; a handler that | ||||
|   | ||||
| @@ -47,11 +47,11 @@ the roster. Next, we want to send our message, and to do that we will use :meth: | ||||
|         self.send_message(mto=self.recipient, mbody=self.msg) | ||||
|  | ||||
| Finally, we need to disconnect the client using :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>`. | ||||
| Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call | ||||
| :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible | ||||
| for the client to disconnect before the send queue is processed and the message is actually | ||||
| sent on the wire. To ensure that our message is processed, we use | ||||
| :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`. | ||||
| Now, sent stanzas are placed in a queue to pass them to the send thread. | ||||
| :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` by default will wait for an | ||||
| acknowledgement from the server for at least `2.0` seconds. This time is configurable with | ||||
| the `wait` parameter. If `0.0` is passed for `wait`, :meth:`disconnect | ||||
| <slixmpp.xmlstream.XMLStream.disconnect>` will not close the connection gracefully. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
| @@ -61,12 +61,12 @@ sent on the wire. To ensure that our message is processed, we use | ||||
|  | ||||
|         self.send_message(mto=self.recipient, mbody=self.msg) | ||||
|  | ||||
|         self.disconnect(wait=True) | ||||
|         self.disconnect() | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     If you happen to be adding stanzas to the send queue faster than the send thread | ||||
|     can process them, then :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>` | ||||
|     can process them, then :meth:`disconnect() <slixmpp.xmlstream.XMLStream.disconnect>` | ||||
|     will block and not disconnect. | ||||
|  | ||||
| Final Product | ||||
|   | ||||
| @@ -21,7 +21,7 @@ Slixmpp | ||||
|     which goal is to use asyncio instead of threads to handle networking. See | ||||
|     :ref:`differences`. | ||||
|  | ||||
| Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.5+, | ||||
| Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+, | ||||
|  | ||||
| Slixmpp's design goals and philosphy are: | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ class CommandBot(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -47,7 +47,7 @@ class CommandBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         # We add the command after session_start has fired | ||||
|         # to ensure that the correct full JID is used. | ||||
|   | ||||
| @@ -37,7 +37,7 @@ class CommandUserBot(slixmpp.ClientXMPP): | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -51,7 +51,7 @@ class CommandUserBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         # We first create a session dictionary containing: | ||||
|         #   'next'  -- the handler to execute on a successful response | ||||
| @@ -176,4 +176,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class AdminCommands(slixmpp.ClientXMPP): | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -44,7 +44,7 @@ class AdminCommands(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         def command_success(iq, session): | ||||
|             print('Command completed') | ||||
|   | ||||
| @@ -69,7 +69,7 @@ class Disco(slixmpp.ClientXMPP): | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|         try: | ||||
|   | ||||
| @@ -159,4 +159,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -58,7 +58,7 @@ class GTalkBot(slixmpp.ClientXMPP): | ||||
|             logging.error(err.message) | ||||
|             self.disconnect() | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -72,7 +72,7 @@ class GTalkBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -39,7 +39,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -53,7 +53,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -104,4 +104,4 @@ def on_session2(event): | ||||
| new_xmpp.add_event_handler('session_start', on_session2) | ||||
|  | ||||
| new_xmpp.connect() | ||||
| new_xmpp.process() | ||||
| new_xmpp.process(forever=False) | ||||
|   | ||||
| @@ -52,7 +52,7 @@ class MUCBot(slixmpp.ClientXMPP): | ||||
|                                self.muc_online) | ||||
|  | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -65,7 +65,7 @@ class MUCBot(slixmpp.ClientXMPP): | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|         self.send_presence() | ||||
|         self.plugin['xep_0045'].join_muc(self.room, | ||||
|                                          self.nick, | ||||
|   | ||||
| @@ -51,7 +51,7 @@ class PingTest(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         try: | ||||
|             rtt = await self['xep_0199'].ping(self.pingjid, | ||||
| @@ -109,4 +109,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -32,7 +32,7 @@ class PubsubClient(slixmpp.ClientXMPP): | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|  | ||||
|     async def start(self, event): | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|         try: | ||||
|   | ||||
| @@ -38,8 +38,8 @@ class PubsubEvents(slixmpp.ClientXMPP): | ||||
|         # self.add_event_handler('event_prefix_purge', handler) | ||||
|         # self.add_event_handler('event_prefix_delete', handler) | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|     async def start(self, event): | ||||
|         await self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|     def _publish(self, msg): | ||||
|   | ||||
| @@ -47,7 +47,7 @@ class RegisterBot(slixmpp.ClientXMPP): | ||||
|         # for data forms and OOB links that will make that easier. | ||||
|         self.add_event_handler("register", self.register) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -61,7 +61,7 @@ class RegisterBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         # We're only concerned about registering, so nothing more to do here. | ||||
|         self.disconnect() | ||||
|   | ||||
| @@ -51,12 +51,8 @@ class RosterBrowser(slixmpp.ClientXMPP): | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         future = asyncio.Future() | ||||
|         def callback(result): | ||||
|             future.set_result(None) | ||||
|         try: | ||||
|             self.get_roster(callback=callback) | ||||
|             await future | ||||
|             await self.get_roster() | ||||
|         except IqError as err: | ||||
|             print('Error: %s' % err.iq['error']['condition']) | ||||
|         except IqTimeout: | ||||
| @@ -138,4 +134,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class SendMsgBot(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -52,7 +52,7 @@ class SendMsgBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         self.send_message(mto=self.recipient, | ||||
|                           mbody=self.msg, | ||||
| @@ -107,4 +107,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -46,7 +46,7 @@ class AvatarSetter(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         avatar_file = None | ||||
|         try: | ||||
| @@ -137,4 +137,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -60,7 +60,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP): | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -74,7 +74,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -38,9 +38,9 @@ class LocationBot(ClientXMPP): | ||||
|  | ||||
|         self.current_tune = None | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|         self['xep_0115'].update_caps() | ||||
|  | ||||
|         print("Using freegeoip.net to get geolocation.") | ||||
|   | ||||
| @@ -35,9 +35,9 @@ class TuneBot(ClientXMPP): | ||||
|  | ||||
|         self.current_tune = None | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|         self['xep_0115'].update_caps() | ||||
|  | ||||
|     def _update_tune(self): | ||||
|   | ||||
							
								
								
									
										3
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								setup.py
									
									
									
									
									
								
							| @@ -28,9 +28,8 @@ CLASSIFIERS = [ | ||||
|     'Intended Audience :: Developers', | ||||
|     'License :: OSI Approved :: MIT License', | ||||
|     'Programming Language :: Python', | ||||
|     'Programming Language :: Python :: 3.5', | ||||
|     'Programming Language :: Python :: 3.6', | ||||
|     'Programming Language :: Python :: 3.7', | ||||
|     'Programming Language :: Python :: 3.8', | ||||
|     'Topic :: Internet :: XMPP', | ||||
|     'Topic :: Software Development :: Libraries :: Python Modules', | ||||
| ] | ||||
|   | ||||
| @@ -111,6 +111,9 @@ class BaseXMPP(XMLStream): | ||||
|         #: outgoing messages an ID. | ||||
|         self.use_presence_ids = True | ||||
|  | ||||
|         #: XEP-0359 <origin-id/> tag that gets added to <message/> stanzas. | ||||
|         self.use_origin_id = True | ||||
|  | ||||
|         #: The API registry is a way to process callbacks based on | ||||
|         #: JID+node combinations. Each callback in the registry is | ||||
|         #: marked with: | ||||
|   | ||||
| @@ -423,7 +423,10 @@ class JID: | ||||
|         if isinstance(other, UnescapedJID): | ||||
|             return False | ||||
|         if not isinstance(other, JID): | ||||
|             other = JID(other) | ||||
|             try: | ||||
|                 other = JID(other) | ||||
|             except InvalidJID: | ||||
|                 return NotImplemented | ||||
|  | ||||
|         return (self._node == other._node and | ||||
|                 self._domain == other._domain and | ||||
|   | ||||
| @@ -85,4 +85,6 @@ __all__ = [ | ||||
|     'xep_0323',  # IoT Systems Sensor Data | ||||
|     'xep_0325',  # IoT Systems Control | ||||
|     'xep_0332',  # HTTP Over XMPP Transport | ||||
|     'protoxep_reactions',  # https://dino.im/xeps/reactions.html | ||||
|     'protoxep_occupantid',  # https://dino.im/xeps/occupant-id.html | ||||
| ] | ||||
|   | ||||
							
								
								
									
										12
									
								
								slixmpp/plugins/protoxep_occupantid/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								slixmpp/plugins/protoxep_occupantid/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2019 Mathieu Pasquet | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from slixmpp.plugins.base import register_plugin | ||||
| from slixmpp.plugins.protoxep_occupantid.occupantid import XEP_OccupantID | ||||
| from slixmpp.plugins.protoxep_occupantid.stanza import OccupantID | ||||
|  | ||||
| register_plugin(XEP_OccupantID) | ||||
							
								
								
									
										23
									
								
								slixmpp/plugins/protoxep_occupantid/occupantid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								slixmpp/plugins/protoxep_occupantid/occupantid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2019 Mathieu Pasquet | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from slixmpp.plugins import BasePlugin | ||||
| from slixmpp.stanza import Message, Presence | ||||
| from slixmpp.xmlstream import register_stanza_plugin | ||||
|  | ||||
| from slixmpp.plugins.protoxep_occupantid import stanza | ||||
|  | ||||
|  | ||||
| class XEP_OccupantID(BasePlugin): | ||||
|     name = 'protoxep_occupantid' | ||||
|     description = 'XEP-XXXX: Anonymous unique occupant identifiers for MUCs' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Message, stanza.OccupantID) | ||||
|         register_stanza_plugin(Presence, stanza.OccupantID) | ||||
							
								
								
									
										16
									
								
								slixmpp/plugins/protoxep_occupantid/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								slixmpp/plugins/protoxep_occupantid/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2019 Mathieu Pasquet | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp.xmlstream import ElementBase | ||||
|  | ||||
|  | ||||
| class OccupantID(ElementBase): | ||||
|     name = 'occupant-id' | ||||
|     plugin_attrib = 'occupant-id' | ||||
|     namespace = 'urn:xmpp:occupant-id:0' | ||||
|     interfaces = {'id'} | ||||
							
								
								
									
										11
									
								
								slixmpp/plugins/protoxep_reactions/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								slixmpp/plugins/protoxep_reactions/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2019 Mathieu Pasquet | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from slixmpp.plugins.base import register_plugin | ||||
| from slixmpp.plugins.protoxep_reactions.reactions import XEP_Reactions | ||||
|  | ||||
| register_plugin(XEP_Reactions) | ||||
							
								
								
									
										54
									
								
								slixmpp/plugins/protoxep_reactions/reactions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								slixmpp/plugins/protoxep_reactions/reactions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2019 Mathieu Pasquet | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from typing import Iterable | ||||
|  | ||||
| from slixmpp.plugins import BasePlugin | ||||
| from slixmpp.stanza import Message | ||||
| from slixmpp.xmlstream import register_stanza_plugin | ||||
| from slixmpp.xmlstream.matcher import MatchXMLMask | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
|  | ||||
| from slixmpp.plugins.protoxep_reactions import stanza | ||||
|  | ||||
|  | ||||
| class XEP_Reactions(BasePlugin): | ||||
|     name = 'protoxep_reactions' | ||||
|     description = 'XEP-XXXX: Message Reactions' | ||||
|     dependencies = {'xep_0030'} | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.register_handler( | ||||
|             Callback( | ||||
|                 'Reaction received', | ||||
|                 MatchXMLMask('<message><reactions xmlns="urn:xmpp:reactions:0"/></message>'), | ||||
|                 self._handle_reactions, | ||||
|             ) | ||||
|         ) | ||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:reactions:0') | ||||
|         register_stanza_plugin(Message, stanza.Reactions) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Reaction received') | ||||
|         self.xmpp['xep_0030'].remove_feature('urn:xmpp:reactions:0') | ||||
|  | ||||
|     def _handle_reactions(self, message: Message): | ||||
|         self.xmpp.event('reactions', message) | ||||
|  | ||||
|     @staticmethod | ||||
|     def set_reactions(message: Message, to_id: str, reactions: Iterable[str]): | ||||
|         """ | ||||
|         Add reactions to a Message object. | ||||
|         """ | ||||
|         reactions_stanza = stanza.Reactions() | ||||
|         reactions_stanza['to'] = to_id | ||||
|         for reaction in reactions: | ||||
|             reaction_stanza = stanza.Reaction() | ||||
|             reaction_stanza['value'] = reaction | ||||
|             reactions_stanza.append(reaction_stanza) | ||||
|         message.append(reactions_stanza) | ||||
							
								
								
									
										31
									
								
								slixmpp/plugins/protoxep_reactions/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								slixmpp/plugins/protoxep_reactions/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2019 Mathieu Pasquet | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp.xmlstream import ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Reactions(ElementBase): | ||||
|     name = 'reactions' | ||||
|     plugin_attrib = 'reactions' | ||||
|     namespace = 'urn:xmpp:reactions:0' | ||||
|     interfaces = {'to'} | ||||
|  | ||||
|  | ||||
| class Reaction(ElementBase): | ||||
|     name = 'reaction' | ||||
|     namespace = 'urn:xmpp:reactions:0' | ||||
|     interfaces = {'value'} | ||||
|  | ||||
|     def get_value(self) -> str: | ||||
|         return self.xml.text | ||||
|  | ||||
|     def set_value(self, value: str): | ||||
|         self.xml.text = value | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Reactions, Reaction, iterable=True) | ||||
| @@ -7,7 +7,7 @@ | ||||
| """ | ||||
|  | ||||
| from slixmpp.xmlstream.stanzabase import ElementBase | ||||
| from xml.etree import cElementTree as ET | ||||
| from xml.etree import ElementTree as ET | ||||
|  | ||||
|  | ||||
| class RPCQuery(ElementBase): | ||||
|   | ||||
| @@ -300,6 +300,8 @@ class XEP_0030(BasePlugin): | ||||
|  | ||||
|     async def get_info_from_domain(self, domain=None, timeout=None, | ||||
|                                    cached=True, callback=None): | ||||
|         """Fetch disco#info of specified domain and one disco#items level below""" | ||||
|  | ||||
|         if domain is None: | ||||
|             domain = self.xmpp.boundjid.domain | ||||
|  | ||||
|   | ||||
| @@ -162,7 +162,7 @@ class XEP_0045(BasePlugin): | ||||
|             return | ||||
|         self.xmpp.roster[pr['from']].ignore_updates = True | ||||
|         entry = pr['muc'].get_stanza_values() | ||||
|         entry['show'] = pr['show'] | ||||
|         entry['show'] = pr['show'] if pr['show'] in pr.showtypes else None | ||||
|         entry['status'] = pr['status'] | ||||
|         entry['alt_nick'] = pr['nick'] | ||||
|         if pr['type'] == 'unavailable': | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp import JID | ||||
| from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| @@ -52,6 +53,12 @@ class Conference(ElementBase): | ||||
|         if value in ('1', 'true', True): | ||||
|             self._set_attr('autojoin', 'true') | ||||
|  | ||||
|     def set_jid(self, value): | ||||
|         del self['jid'] | ||||
|         if isinstance(value, JID): | ||||
|             value = value.full | ||||
|         self._set_attr('jid', value) | ||||
|  | ||||
|  | ||||
| class URL(ElementBase): | ||||
|     name = 'url' | ||||
|   | ||||
| @@ -79,7 +79,8 @@ class ResultIterator: | ||||
|         """ | ||||
|         if self._stop: | ||||
|             raise StopAsyncIteration | ||||
|         self.query[self.interface]['rsm']['before'] = self.reverse | ||||
|         if self.query[self.interface]['rsm']['before'] is None: | ||||
|             self.query[self.interface]['rsm']['before'] = self.reverse | ||||
|         self.query['id'] = self.query.stream.new_id() | ||||
|         self.query[self.interface]['rsm']['max'] = str(self.amount) | ||||
|  | ||||
| @@ -141,7 +142,7 @@ class XEP_0059(BasePlugin): | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Set.namespace) | ||||
|  | ||||
|     def iterate(self, stanza, interface, results='substanzas', | ||||
|     def iterate(self, stanza, interface, results='substanzas', amount=10, reverse=False, | ||||
|                 recv_interface=None, pre_cb=None, post_cb=None): | ||||
|         """ | ||||
|         Create a new result set iterator for a given stanza query. | ||||
| @@ -169,6 +170,6 @@ class XEP_0059(BasePlugin): | ||||
|             results   -- The name of the interface containing the | ||||
|                          query results (typically just 'substanzas'). | ||||
|         """ | ||||
|         return ResultIterator(stanza, interface, results, | ||||
|         return ResultIterator(stanza, interface, results, amount, reverse=reverse, | ||||
|                               recv_interface=recv_interface, pre_cb=pre_cb, | ||||
|                               post_cb=post_cb) | ||||
|   | ||||
| @@ -11,10 +11,9 @@ from slixmpp.xmlstream import ElementBase, ET | ||||
|  | ||||
| class UserGaming(ElementBase): | ||||
|  | ||||
|     name = 'gaming' | ||||
|     name = 'game' | ||||
|     namespace = 'urn:xmpp:gaming:0' | ||||
|     plugin_attrib = 'gaming' | ||||
|     interfaces = {'character_name', 'character_profile', 'name', | ||||
|                   'level', 'server_address', 'server_name', 'uri'} | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
|   | ||||
| @@ -71,7 +71,8 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|         self.window_counter = self.window | ||||
|  | ||||
|         self.enabled = False | ||||
|         self.enabled_in = False | ||||
|         self.enabled_out = False | ||||
|         self.unacked_queue = collections.deque() | ||||
|  | ||||
|         register_stanza_plugin(StreamFeatures, stanza.StreamManagement) | ||||
| @@ -82,10 +83,6 @@ class XEP_0198(BasePlugin): | ||||
|         self.xmpp.register_stanza(stanza.Ack) | ||||
|         self.xmpp.register_stanza(stanza.RequestAck) | ||||
|  | ||||
|         # Only end the session when a </stream> element is sent, | ||||
|         # not just because the connection has died. | ||||
|         self.xmpp.end_session_on_disconnect = False | ||||
|  | ||||
|         # Register the feature twice because it may be ordered two | ||||
|         # different ways: enabling after binding and resumption | ||||
|         # before binding. | ||||
| @@ -131,6 +128,7 @@ class XEP_0198(BasePlugin): | ||||
|         self.xmpp.add_filter('in', self._handle_incoming) | ||||
|         self.xmpp.add_filter('out_sync', self._handle_outgoing) | ||||
|  | ||||
|         self.xmpp.add_event_handler('disconnected', self.disconnected) | ||||
|         self.xmpp.add_event_handler('session_end', self.session_end) | ||||
|  | ||||
|     def plugin_end(self): | ||||
| @@ -139,6 +137,7 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|         self.xmpp.unregister_feature('sm', self.order) | ||||
|         self.xmpp.unregister_feature('sm', self.resume_order) | ||||
|         self.xmpp.del_event_handler('disconnected', self.disconnected) | ||||
|         self.xmpp.del_event_handler('session_end', self.session_end) | ||||
|         self.xmpp.del_filter('in', self._handle_incoming) | ||||
|         self.xmpp.del_filter('out_sync', self._handle_outgoing) | ||||
| @@ -154,9 +153,19 @@ class XEP_0198(BasePlugin): | ||||
|         self.xmpp.remove_stanza(stanza.Ack) | ||||
|         self.xmpp.remove_stanza(stanza.RequestAck) | ||||
|  | ||||
|     def disconnected(self, event): | ||||
|         """Reset enabled state until we can resume/reenable.""" | ||||
|         log.debug("disconnected, disabling SM") | ||||
|         self.xmpp.event('sm_disabled', event) | ||||
|         self.enabled_in = False | ||||
|         self.enabled_out = False | ||||
|  | ||||
|     def session_end(self, event): | ||||
|         """Reset stream management state.""" | ||||
|         self.enabled = False | ||||
|         log.debug("session_end, disabling SM") | ||||
|         self.xmpp.event('sm_disabled', event) | ||||
|         self.enabled_in = False | ||||
|         self.enabled_out = False | ||||
|         self.unacked_queue.clear() | ||||
|         self.sm_id = None | ||||
|         self.handled = 0 | ||||
| @@ -171,6 +180,7 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|     def request_ack(self, e=None): | ||||
|         """Request an ack from the server.""" | ||||
|         log.debug("requesting ack") | ||||
|         req = stanza.RequestAck(self.xmpp) | ||||
|         self.xmpp.send_raw(str(req)) | ||||
|  | ||||
| @@ -193,9 +203,7 @@ class XEP_0198(BasePlugin): | ||||
|                 enable = stanza.Enable(self.xmpp) | ||||
|                 enable['resume'] = self.allow_resume | ||||
|                 enable.send() | ||||
|                 self.enabled = True | ||||
|                 self.handled = 0 | ||||
|                 self.unacked_queue.clear() | ||||
|                 log.debug("enabling SM") | ||||
|  | ||||
|                 waiter = Waiter('enabled_or_failed', | ||||
|                         MatchMany([ | ||||
| @@ -204,11 +212,11 @@ class XEP_0198(BasePlugin): | ||||
|                 self.xmpp.register_handler(waiter) | ||||
|                 result = await waiter.wait() | ||||
|         elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features: | ||||
|             self.enabled = True | ||||
|             resume = stanza.Resume(self.xmpp) | ||||
|             resume['h'] = self.handled | ||||
|             resume['previd'] = self.sm_id | ||||
|             resume.send() | ||||
|             log.debug("resuming SM") | ||||
|  | ||||
|             # Wait for a response before allowing stream feature processing | ||||
|             # to continue. The actual result processing will be done in the | ||||
| @@ -231,7 +239,10 @@ class XEP_0198(BasePlugin): | ||||
|         self.xmpp.features.add('stream_management') | ||||
|         if stanza['id']: | ||||
|             self.sm_id = stanza['id'] | ||||
|         self.enabled_in = True | ||||
|         self.handled = 0 | ||||
|         self.xmpp.event('sm_enabled', stanza) | ||||
|         self.xmpp.end_session_on_disconnect = False | ||||
|  | ||||
|     def _handle_resumed(self, stanza): | ||||
|         """Finish resuming a stream by resending unacked stanzas. | ||||
| @@ -239,10 +250,12 @@ class XEP_0198(BasePlugin): | ||||
|         Raises a :term:`session_resumed` event. | ||||
|         """ | ||||
|         self.xmpp.features.add('stream_management') | ||||
|         self.enabled_in = True | ||||
|         self._handle_ack(stanza) | ||||
|         for id, stanza in self.unacked_queue: | ||||
|             self.xmpp.send(stanza, use_filters=False) | ||||
|         self.xmpp.event('session_resumed', stanza) | ||||
|         self.xmpp.end_session_on_disconnect = False | ||||
|  | ||||
|     def _handle_failed(self, stanza): | ||||
|         """ | ||||
| @@ -252,7 +265,8 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|         Raises an :term:`sm_failed` event. | ||||
|         """ | ||||
|         self.enabled = False | ||||
|         self.enabled_in = False | ||||
|         self.enabled_out = False | ||||
|         self.unacked_queue.clear() | ||||
|         self.xmpp.event('sm_failed', stanza) | ||||
|  | ||||
| @@ -289,7 +303,7 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|     def _handle_incoming(self, stanza): | ||||
|         """Increment the handled counter for each inbound stanza.""" | ||||
|         if not self.enabled: | ||||
|         if not self.enabled_in: | ||||
|             return stanza | ||||
|  | ||||
|         if isinstance(stanza, (Message, Presence, Iq)): | ||||
| @@ -299,7 +313,13 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|     def _handle_outgoing(self, stanza): | ||||
|         """Store outgoing stanzas in a queue to be acked.""" | ||||
|         if not self.enabled: | ||||
|         from slixmpp.plugins.xep_0198 import stanza as st | ||||
|         if isinstance(stanza, (st.Enable, st.Resume)): | ||||
|             self.enabled_out = True | ||||
|             self.unacked_queue.clear() | ||||
|             log.debug("enabling outgoing SM: %s" % stanza) | ||||
|  | ||||
|         if not self.enabled_out: | ||||
|             return stanza | ||||
|  | ||||
|         if isinstance(stanza, (Message, Presence, Iq)): | ||||
|   | ||||
| @@ -50,7 +50,7 @@ class XEP_0202(BasePlugin): | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|             Callback('Entity Time', | ||||
|                  StanzaPath('iq/entity_time'), | ||||
|                  StanzaPath('iq@type=get/entity_time'), | ||||
|                  self._handle_time_request)) | ||||
|         register_stanza_plugin(Iq, stanza.EntityTime) | ||||
|  | ||||
|   | ||||
| @@ -31,11 +31,11 @@ class XEP_0279(BasePlugin): | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0') | ||||
|  | ||||
|     def check_ip(self, ifrom=None, block=True, timeout=None, callback=None, | ||||
|     def check_ip(self, ifrom=None, timeout=None, callback=None, | ||||
|                       timeout_callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['from'] = ifrom | ||||
|         iq.enable('ip_check') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback, | ||||
|         return iq.send(timeout=timeout, callback=callback, | ||||
|                        timeout_callback=timeout_callback) | ||||
|   | ||||
| @@ -3,16 +3,18 @@ | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permissio | ||||
|     See the file LICENSE for copying permission | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| import slixmpp | ||||
| from datetime import datetime | ||||
| from typing import Any, Dict, Callable, Optional, Awaitable | ||||
|  | ||||
| from slixmpp import JID | ||||
| from slixmpp.stanza import Message, Iq | ||||
| from slixmpp.exceptions import XMPPError | ||||
| from slixmpp.xmlstream.handler import Collector | ||||
| from slixmpp.xmlstream.matcher import StanzaPath | ||||
| from slixmpp.xmlstream.matcher import MatchXMLMask | ||||
| from slixmpp.xmlstream import register_stanza_plugin | ||||
| from slixmpp.plugins import BasePlugin | ||||
| from slixmpp.plugins.xep_0313 import stanza | ||||
| @@ -41,8 +43,32 @@ class XEP_0313(BasePlugin): | ||||
|         register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set) | ||||
|         register_stanza_plugin(stanza.Fin, self.xmpp['xep_0059'].stanza.Set) | ||||
|  | ||||
|     def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None, | ||||
|                  timeout=None, callback=None, iterator=False, rsm=None): | ||||
|     def retrieve( | ||||
|             self, | ||||
|             jid: Optional[JID] = None, | ||||
|             start: Optional[datetime] = None, | ||||
|             end: Optional[datetime] = None, | ||||
|             with_jid: Optional[JID] = None, | ||||
|             ifrom: Optional[JID] = None, | ||||
|             reverse: bool = False, | ||||
|             timeout: int = None, | ||||
|             callback: Callable[[Iq], None] = None, | ||||
|             iterator: bool = False, | ||||
|             rsm: Optional[Dict[str, Any]] = None | ||||
|     ) -> Awaitable: | ||||
|         """ | ||||
|         Send a MAM query and retrieve the results. | ||||
|  | ||||
|         :param JID jid: Entity holding the MAM records | ||||
|         :param datetime start,end: MAM query temporal boundaries | ||||
|         :param JID with_jid: Filter results on this JID | ||||
|         :param JID ifrom: To change the from address of the query | ||||
|         :param bool reverse: Get the results in reverse order | ||||
|         :param int timeout: IQ timeout | ||||
|         :param func callback: Custom callback for handling results | ||||
|         :param bool iterator: Use RSM and iterate over a paginated query | ||||
|         :param dict rsm: RSM custom options | ||||
|         """ | ||||
|         iq = self.xmpp.Iq() | ||||
|         query_id = iq['id'] | ||||
|  | ||||
| @@ -53,35 +79,48 @@ class XEP_0313(BasePlugin): | ||||
|         iq['mam']['start'] = start | ||||
|         iq['mam']['end'] = end | ||||
|         iq['mam']['with'] = with_jid | ||||
|         amount = 10 | ||||
|         if rsm: | ||||
|             for key, value in rsm.items(): | ||||
|                 iq['mam']['rsm'][key] = str(value) | ||||
|  | ||||
|                 if key == 'max': | ||||
|                     amount = value | ||||
|         cb_data = {} | ||||
|         def pre_cb(query): | ||||
|  | ||||
|         stanza_mask = self.xmpp.Message() | ||||
|         stanza_mask.xml.remove(stanza_mask.xml.find('{urn:xmpp:sid:0}origin-id')) | ||||
|         del stanza_mask['id'] | ||||
|         del stanza_mask['lang'] | ||||
|         stanza_mask['from'] = jid | ||||
|         stanza_mask['mam_result']['queryid'] = query_id | ||||
|         xml_mask = str(stanza_mask) | ||||
|  | ||||
|         def pre_cb(query: Iq) -> None: | ||||
|             stanza_mask['mam_result']['queryid'] = query['id'] | ||||
|             xml_mask = str(stanza_mask) | ||||
|             query['mam']['queryid'] = query['id'] | ||||
|             collector = Collector( | ||||
|                 'MAM_Results_%s' % query_id, | ||||
|                 StanzaPath('message/mam_result@queryid=%s' % query['id'])) | ||||
|                 MatchXMLMask(xml_mask)) | ||||
|             self.xmpp.register_handler(collector) | ||||
|             cb_data['collector'] = collector | ||||
|  | ||||
|         def post_cb(result): | ||||
|         def post_cb(result: Iq) -> None: | ||||
|             results = cb_data['collector'].stop() | ||||
|             if result['type'] == 'result': | ||||
|                 result['mam']['results'] = results | ||||
|  | ||||
|         if iterator: | ||||
|             return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results', | ||||
|                                                  recv_interface='mam_fin', | ||||
|             return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results', amount=amount, | ||||
|                                                  reverse=reverse, recv_interface='mam_fin', | ||||
|                                                  pre_cb=pre_cb, post_cb=post_cb) | ||||
|  | ||||
|         collector = Collector( | ||||
|             'MAM_Results_%s' % query_id, | ||||
|             StanzaPath('message/mam_result@queryid=%s' % query_id)) | ||||
|             MatchXMLMask(xml_mask)) | ||||
|         self.xmpp.register_handler(collector) | ||||
|  | ||||
|         def wrapped_cb(iq): | ||||
|         def wrapped_cb(iq: Iq) -> None: | ||||
|             results = collector.stop() | ||||
|             if iq['type'] == 'result': | ||||
|                 iq['mam']['results'] = results | ||||
| @@ -90,8 +129,15 @@ class XEP_0313(BasePlugin): | ||||
|  | ||||
|         return iq.send(timeout=timeout, callback=wrapped_cb) | ||||
|  | ||||
|     def get_preferences(self, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         query_id = iq['id'] | ||||
|         iq['mam_prefs']['query_id'] = query_id | ||||
|         return iq.send(timeout=timeout, callback=callback) | ||||
|  | ||||
|     def set_preferences(self, jid=None, default=None, always=None, never=None, | ||||
|                         ifrom=None, block=True, timeout=None, callback=None): | ||||
|                         ifrom=None, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['to'] = jid | ||||
| @@ -99,7 +145,7 @@ class XEP_0313(BasePlugin): | ||||
|         iq['mam_prefs']['default'] = default | ||||
|         iq['mam_prefs']['always'] = always | ||||
|         iq['mam_prefs']['never'] = never | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|         return iq.send(timeout=timeout, callback=callback) | ||||
|  | ||||
|     def get_configuration_commands(self, jid, **kwargs): | ||||
|         return self.xmpp['xep_0030'].get_items( | ||||
|   | ||||
| @@ -516,7 +516,7 @@ class Field(ElementBase): | ||||
|         :param value: string | ||||
|         """ | ||||
|  | ||||
|         pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$") | ||||
|         pattern = re.compile(r"^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$") | ||||
|         if pattern.match(value) is not None: | ||||
|             self.xml.stringIds = value | ||||
|         else: | ||||
|   | ||||
| @@ -114,7 +114,6 @@ class XEP_0332(BasePlugin): | ||||
|             iq['http-req']['data'] = data | ||||
|         return iq.send( | ||||
|             timeout=kwargs.get('timeout', None), | ||||
|             block=kwargs.get('block', True), | ||||
|             callback=kwargs.get('callback', None), | ||||
|             timeout_callback=kwargs.get('timeout_callback', None) | ||||
|         ) | ||||
| @@ -135,7 +134,6 @@ class XEP_0332(BasePlugin): | ||||
|             iq['http-resp']['data'] = data | ||||
|         return iq.send( | ||||
|             timeout=kwargs.get('timeout', None), | ||||
|             block=kwargs.get('block', True), | ||||
|             callback=kwargs.get('callback', None), | ||||
|             timeout_callback=kwargs.get('timeout_callback', None) | ||||
|         ) | ||||
| @@ -153,7 +151,6 @@ class XEP_0332(BasePlugin): | ||||
|             iq['id'] = kwargs["id"] | ||||
|         return iq.send( | ||||
|             timeout=kwargs.get('timeout', None), | ||||
|             block=kwargs.get('block', True), | ||||
|             callback=kwargs.get('callback', None), | ||||
|             timeout_callback=kwargs.get('timeout_callback', None) | ||||
|         ) | ||||
|   | ||||
| @@ -10,6 +10,9 @@ from slixmpp.stanza.rootstanza import RootStanza | ||||
| from slixmpp.xmlstream import StanzaBase, ET | ||||
|  | ||||
|  | ||||
| ORIGIN_NAME = '{urn:xmpp:sid:0}origin-id' | ||||
|  | ||||
|  | ||||
| class Message(RootStanza): | ||||
|  | ||||
|     """ | ||||
| @@ -63,6 +66,8 @@ class Message(RootStanza): | ||||
|         if self['id'] == '': | ||||
|             if self.stream is not None and self.stream.use_message_ids: | ||||
|                 self['id'] = self.stream.new_id() | ||||
|             else: | ||||
|                 del self['origin_id'] | ||||
|  | ||||
|     def get_type(self): | ||||
|         """ | ||||
| @@ -76,6 +81,43 @@ class Message(RootStanza): | ||||
|         """ | ||||
|         return self._get_attr('type', 'normal') | ||||
|  | ||||
|     def get_id(self): | ||||
|         return self._get_attr('id') or '' | ||||
|  | ||||
|     def get_origin_id(self): | ||||
|         sub = self.xml.find(ORIGIN_NAME) | ||||
|         if sub is not None: | ||||
|             return sub.attrib.get('id') or '' | ||||
|         return '' | ||||
|  | ||||
|     def _set_ids(self, value) -> None: | ||||
|         if value is None or value == '': | ||||
|             return None | ||||
|  | ||||
|         self.xml.attrib['id'] = value | ||||
|  | ||||
|         if not self.stream.use_origin_id: | ||||
|             return None | ||||
|  | ||||
|         sub = self.xml.find(ORIGIN_NAME) | ||||
|         if sub is not None: | ||||
|             sub.attrib['id'] = value | ||||
|         else: | ||||
|             sub = ET.Element(ORIGIN_NAME) | ||||
|             sub.attrib['id'] = value | ||||
|             self.xml.append(sub) | ||||
|  | ||||
|     def set_id(self, value): | ||||
|         return self._set_ids(value) | ||||
|  | ||||
|     def set_origin_id(self, value: str): | ||||
|         return self._set_ids(value) | ||||
|  | ||||
|     def del_origin_id(self): | ||||
|         sub = self.xml.find(ORIGIN_NAME) | ||||
|         if sub is not None: | ||||
|             self.xml.remove(sub) | ||||
|  | ||||
|     def get_parent_thread(self): | ||||
|         """Return the message thread's parent thread. | ||||
|  | ||||
| @@ -140,6 +182,8 @@ class Message(RootStanza): | ||||
|         new_message['parent_thread'] = self['parent_thread'] | ||||
|  | ||||
|         del new_message['id'] | ||||
|         if self.stream is not None and self.stream.use_message_ids: | ||||
|             new_message['id'] = self.stream.new_id() | ||||
|  | ||||
|         if body is not None: | ||||
|             new_message['body'] = body | ||||
|   | ||||
| @@ -90,10 +90,10 @@ class Presence(RootStanza): | ||||
|     def get_type(self): | ||||
|         """ | ||||
|         Return the value of the <presence> stanza's type attribute, or | ||||
|         the value of the <show> element. | ||||
|         the value of the <show> element if valid. | ||||
|         """ | ||||
|         out = self._get_attr('type') | ||||
|         if not out: | ||||
|         if not out and self['show'] in self.showtypes: | ||||
|             out = self['show'] | ||||
|         if not out or out is None: | ||||
|             out = 'available' | ||||
|   | ||||
| @@ -340,11 +340,19 @@ class SlixTest(unittest.TestCase): | ||||
|         self.xmpp.default_lang = None | ||||
|         self.xmpp.peer_default_lang = None | ||||
|  | ||||
|         def new_id(): | ||||
|             self.xmpp._id += 1 | ||||
|             return str(self.xmpp._id) | ||||
|  | ||||
|         self.xmpp._id = 0 | ||||
|         self.xmpp.new_id = new_id | ||||
|  | ||||
|         # Must have the stream header ready for xmpp.process() to work. | ||||
|         if not header: | ||||
|             header = self.xmpp.stream_header | ||||
|  | ||||
|         self.xmpp.data_received(header) | ||||
|         self.wait_for_send_queue() | ||||
|  | ||||
|         if skip: | ||||
|             self.xmpp.socket.next_sent() | ||||
| @@ -592,6 +600,7 @@ class SlixTest(unittest.TestCase): | ||||
|                             'id', 'stanzapath', 'xpath', and 'mask'. | ||||
|                             Defaults to the value of self.match_method. | ||||
|         """ | ||||
|         self.wait_for_send_queue() | ||||
|         sent = self.xmpp.socket.next_sent(timeout) | ||||
|         if data is None and sent is None: | ||||
|             return | ||||
| @@ -608,6 +617,14 @@ class SlixTest(unittest.TestCase): | ||||
|                    defaults=defaults, | ||||
|                    use_values=use_values) | ||||
|  | ||||
|     def wait_for_send_queue(self): | ||||
|         loop = asyncio.get_event_loop() | ||||
|         future = asyncio.ensure_future(self.xmpp.run_filters(), loop=loop) | ||||
|         queue = self.xmpp.waiting_queue | ||||
|         print(queue) | ||||
|         loop.run_until_complete(queue.join()) | ||||
|         future.cancel() | ||||
|  | ||||
|     def stream_close(self): | ||||
|         """ | ||||
|         Disconnect the dummy XMPP client. | ||||
|   | ||||
							
								
								
									
										2
									
								
								slixmpp/thirdparty/mini_dateutil.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								slixmpp/thirdparty/mini_dateutil.py
									
									
									
									
										vendored
									
									
								
							| @@ -160,7 +160,7 @@ except: | ||||
|         return _fixed_offset_tzs[offsetmins] | ||||
|  | ||||
|  | ||||
|     _iso8601_parser = re.compile(""" | ||||
|     _iso8601_parser = re.compile(r""" | ||||
|         ^ | ||||
|         (?P<year> [0-9]{4})?(?P<ymdsep>-?)? | ||||
|         (?P<month>[0-9]{2})?(?P=ymdsep)? | ||||
|   | ||||
| @@ -9,5 +9,5 @@ | ||||
| # We don't want to have to import the entire library | ||||
| # just to get the version info for setup.py | ||||
|  | ||||
| __version__ = '1.4.2' | ||||
| __version_info__ = (1, 4, 2) | ||||
| __version__ = '1.5.1' | ||||
| __version_info__ = (1, 5, 1) | ||||
|   | ||||
| @@ -17,7 +17,7 @@ from __future__ import with_statement, unicode_literals | ||||
| import copy | ||||
| import logging | ||||
| import weakref | ||||
| from xml.etree import cElementTree as ET | ||||
| from xml.etree import ElementTree as ET | ||||
|  | ||||
| from slixmpp.xmlstream import JID | ||||
| from slixmpp.xmlstream.tostring import tostring | ||||
| @@ -203,7 +203,7 @@ class ElementBase(object): | ||||
|  | ||||
|     """ | ||||
|     The core of Slixmpp's stanza XML manipulation and handling is provided | ||||
|     by ElementBase. ElementBase wraps XML cElementTree objects and enables | ||||
|     by ElementBase. ElementBase wraps XML ElementTree objects and enables | ||||
|     access to the XML contents through dictionary syntax, similar in style | ||||
|     to the Ruby XMPP library Blather's stanza implementation. | ||||
|  | ||||
| @@ -387,7 +387,7 @@ class ElementBase(object): | ||||
|         self._index = 0 | ||||
|  | ||||
|         #: The underlying XML object for the stanza. It is a standard | ||||
|         #: :class:`xml.etree.cElementTree` object. | ||||
|         #: :class:`xml.etree.ElementTree` object. | ||||
|         self.xml = xml | ||||
|  | ||||
|         #: An ordered dictionary of plugin stanzas, mapped by their | ||||
| @@ -1031,14 +1031,19 @@ class ElementBase(object): | ||||
|         if not lang: | ||||
|             lang = default_lang | ||||
|  | ||||
|         parent = self.xml | ||||
|         for level, _ in enumerate(path): | ||||
|             # Generate the paths to the target elements and their parent. | ||||
|             element_path = "/".join(path[:len(path) - level]) | ||||
|             parent_path = "/".join(path[:len(path) - level - 1]) | ||||
|  | ||||
|             elements = self.xml.findall(element_path) | ||||
|             parent = self.xml.find(parent_path) | ||||
|  | ||||
|              | ||||
|             if parent_path == '': | ||||
|                 parent_path = None              | ||||
|             if parent_path is not None:      | ||||
|                 parent = self.xml.find(parent_path) | ||||
|                          | ||||
|             if elements: | ||||
|                 if parent is None: | ||||
|                     parent = self.xml | ||||
| @@ -1374,14 +1379,6 @@ class StanzaBase(ElementBase): | ||||
|     #: The default XMPP client namespace | ||||
|     namespace = 'jabber:client' | ||||
|  | ||||
|     #: There is a small set of attributes which apply to all XMPP stanzas: | ||||
|     #: the stanza type, the to and from JIDs, the stanza ID, and, especially | ||||
|     #: in the case of an Iq stanza, a payload. | ||||
|     interfaces = {'type', 'to', 'from', 'id', 'payload'} | ||||
|  | ||||
|     #: A basic set of allowed values for the ``'type'`` interface. | ||||
|     types = {'get', 'set', 'error', None, 'unavailable', 'normal', 'chat'} | ||||
|  | ||||
|     def __init__(self, stream=None, xml=None, stype=None, | ||||
|                  sto=None, sfrom=None, sid=None, parent=None): | ||||
|         self.stream = stream | ||||
|   | ||||
| @@ -12,6 +12,8 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| from typing import Optional, Set, Callable | ||||
|  | ||||
| import functools | ||||
| import logging | ||||
| import socket as Socket | ||||
| @@ -19,6 +21,8 @@ import ssl | ||||
| import weakref | ||||
| import uuid | ||||
|  | ||||
| from asyncio import iscoroutinefunction, wait | ||||
|  | ||||
| import xml.etree.ElementTree as ET | ||||
|  | ||||
| from slixmpp.xmlstream.asyncio import asyncio | ||||
| @@ -30,6 +34,10 @@ from slixmpp.xmlstream.resolver import resolve, default_resolver | ||||
| RESPONSE_TIMEOUT = 30 | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
| class ContinueQueue(Exception): | ||||
|     """ | ||||
|     Exception raised in the send queue to "continue" from within an inner loop | ||||
|     """ | ||||
|  | ||||
| class NotConnectedError(Exception): | ||||
|     """ | ||||
| @@ -81,6 +89,8 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         self.force_starttls = None | ||||
|         self.disable_starttls = None | ||||
|  | ||||
|         self.waiting_queue = asyncio.Queue() | ||||
|  | ||||
|         # A dict of {name: handle} | ||||
|         self.scheduled_events = {} | ||||
|  | ||||
| @@ -199,11 +209,6 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         self.__event_handlers = {} | ||||
|         self.__filters = {'in': [], 'out': [], 'out_sync': []} | ||||
|  | ||||
|         self._id = 0 | ||||
|  | ||||
|         #: We use an ID prefix to ensure that all ID values are unique. | ||||
|         self._id_prefix = '%s-' % uuid.uuid4() | ||||
|  | ||||
|         # Current connection attempt (Future) | ||||
|         self._current_connection_attempt = None | ||||
|  | ||||
| @@ -241,12 +246,7 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         ID values. Using this method ensures that all new ID values | ||||
|         are unique in this stream. | ||||
|         """ | ||||
|         self._id += 1 | ||||
|         return self.get_id() | ||||
|  | ||||
|     def get_id(self): | ||||
|         """Return the current unique stream ID in hexadecimal form.""" | ||||
|         return "%s%X" % (self._id_prefix, self._id) | ||||
|         return uuid.uuid4().hex | ||||
|  | ||||
|     def connect(self, host='', port=0, use_ssl=False, | ||||
|                 force_starttls=True, disable_starttls=False): | ||||
| @@ -271,8 +271,13 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|                                localhost | ||||
|  | ||||
|         """ | ||||
|         asyncio.ensure_future( | ||||
|             self.run_filters(), | ||||
|             loop=self.loop, | ||||
|         ) | ||||
|         self.disconnect_reason = None | ||||
|         self.cancel_connection_attempt() | ||||
|         self.connect_loop_wait = 0 | ||||
|         if host and port: | ||||
|             self.address = (host, int(port)) | ||||
|         try: | ||||
| @@ -297,6 +302,10 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|     async def _connect_routine(self): | ||||
|         self.event_when_connected = "connected" | ||||
|  | ||||
|         if self.connect_loop_wait > 0: | ||||
|             self.event('reconnect_delay', self.connect_loop_wait) | ||||
|             await asyncio.sleep(self.connect_loop_wait, loop=self.loop) | ||||
|  | ||||
|         record = await self.pick_dns_answer(self.default_domain) | ||||
|         if record is not None: | ||||
|             host, address, dns_port = record | ||||
| @@ -313,7 +322,6 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         else: | ||||
|             ssl_context = None | ||||
|  | ||||
|         await asyncio.sleep(self.connect_loop_wait, loop=self.loop) | ||||
|         if self._current_connection_attempt is None: | ||||
|             return | ||||
|         try: | ||||
| @@ -372,6 +380,7 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|             "ssl_object", | ||||
|             default=self.transport.get_extra_info("socket") | ||||
|         ) | ||||
|         self._current_connection_attempt = None | ||||
|         self.init_parser() | ||||
|         self.send_raw(self.stream_header) | ||||
|         self.dns_answers = None | ||||
| @@ -430,6 +439,9 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|             self.send(error) | ||||
|             self.disconnect() | ||||
|  | ||||
|     def is_connecting(self): | ||||
|         return self._current_connection_attempt is not None | ||||
|  | ||||
|     def is_connected(self): | ||||
|         return self.transport is not None | ||||
|  | ||||
| @@ -463,10 +475,10 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|             self._current_connection_attempt.cancel() | ||||
|             self._current_connection_attempt = None | ||||
|  | ||||
|     def disconnect(self, wait=2.0, reason=None): | ||||
|     def disconnect(self, wait: float = 2.0, reason: Optional[str] = None, ignore_send_queue: bool = False) -> None: | ||||
|         """Close the XML stream and wait for an acknowldgement from the server for | ||||
|         at most `wait` seconds.  After the given number of seconds has | ||||
|         passed without a response from the serveur, or when the server | ||||
|         passed without a response from the server, or when the server | ||||
|         successfully responds with a closure of its own stream, abort() is | ||||
|         called. If wait is 0.0, this will call abort() directly without closing | ||||
|         the stream. | ||||
| @@ -476,13 +488,38 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         :param wait: Time to wait for a response from the server. | ||||
|  | ||||
|         """ | ||||
|         # Compat: docs/getting_started/sendlogout.rst has been promoting | ||||
|         # `disconnect(wait=True)` for ages. This doesn't mean anything to the | ||||
|         # schedule call below. It would fortunately be converted to `1` later | ||||
|         # down the call chain. Praise the implicit casts lord. | ||||
|         if wait == True: | ||||
|             wait = 2.0 | ||||
|  | ||||
|         if self.transport: | ||||
|             if self.waiting_queue.empty() or ignore_send_queue: | ||||
|                 self.disconnect_reason = reason | ||||
|                 self.cancel_connection_attempt() | ||||
|                 if wait > 0.0: | ||||
|                     self.send_raw(self.stream_footer) | ||||
|                 self.schedule('Disconnect wait', wait, | ||||
|                               self.abort, repeat=False) | ||||
|             else: | ||||
|                 asyncio.ensure_future( | ||||
|                     self._consume_send_queue_before_disconnecting(reason, wait), | ||||
|                     loop=self.loop, | ||||
|                 ) | ||||
|         else: | ||||
|             self.event("disconnected", reason) | ||||
|  | ||||
|     async def _consume_send_queue_before_disconnecting(self, reason: Optional[str], wait: float): | ||||
|         """Wait until the send queue is empty before disconnecting""" | ||||
|         await self.waiting_queue.join() | ||||
|         self.disconnect_reason = reason | ||||
|         self.cancel_connection_attempt() | ||||
|         if self.transport: | ||||
|             if wait > 0.0: | ||||
|                 self.send_raw(self.stream_footer) | ||||
|             self.schedule('Disconnect wait', wait, | ||||
|                           self.abort, repeat=False) | ||||
|         if wait > 0.0: | ||||
|             self.send_raw(self.stream_footer) | ||||
|         self.schedule('Disconnect wait', wait, | ||||
|                       self.abort, repeat=False) | ||||
|  | ||||
|     def abort(self): | ||||
|         """ | ||||
| @@ -495,14 +532,15 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|             self.event("killed") | ||||
|             self.disconnected.set_result(True) | ||||
|             self.disconnected = asyncio.Future() | ||||
|             self.event("disconnected", self.disconnect_reason) | ||||
|  | ||||
|     def reconnect(self, wait=2.0, reason="Reconnecting"): | ||||
|         """Calls disconnect(), and once we are disconnected (after the timeout, or | ||||
|         when the server acknowledgement is received), call connect() | ||||
|         """ | ||||
|         log.debug("reconnecting...") | ||||
|         self.disconnect(wait, reason) | ||||
|         self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True) | ||||
|         self.disconnect(wait, reason) | ||||
|  | ||||
|     def configure_socket(self): | ||||
|         """Set timeout and other options for self.socket. | ||||
| @@ -790,7 +828,7 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|  | ||||
|             # If the callback is a coroutine, schedule it instead of | ||||
|             # running it directly | ||||
|             if asyncio.iscoroutinefunction(handler_callback): | ||||
|             if iscoroutinefunction(handler_callback): | ||||
|                 async def handler_callback_routine(cb): | ||||
|                     try: | ||||
|                         await cb(data) | ||||
| @@ -877,7 +915,9 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         Execute the callback and remove the handler for it. | ||||
|         """ | ||||
|         self._safe_cb_run(name, cb) | ||||
|         del self.scheduled_events[name] | ||||
|         # workaround for specific events which unschedule themselves | ||||
|         if name in self.scheduled_events: | ||||
|             del self.scheduled_events[name] | ||||
|  | ||||
|     def incoming_filter(self, xml): | ||||
|         """Filter incoming XML objects before they are processed. | ||||
| @@ -889,11 +929,93 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         """ | ||||
|         return xml | ||||
|  | ||||
|     async def _continue_slow_send( | ||||
|             self, | ||||
|             task: asyncio.Task, | ||||
|             already_used: Set[Callable[[ElementBase], Optional[StanzaBase]]] | ||||
|     ) -> None: | ||||
|         """ | ||||
|         Used when an item in the send queue has taken too long to process. | ||||
|  | ||||
|         This is away from the send queue and can take as much time as needed. | ||||
|         :param asyncio.Task task: the Task wrapping the coroutine | ||||
|         :param set already_used: Filters already used on this outgoing stanza | ||||
|         """ | ||||
|         data = await task | ||||
|         for filter in self.__filters['out']: | ||||
|             if filter in already_used: | ||||
|                 continue | ||||
|             if iscoroutinefunction(filter): | ||||
|                 data = await task | ||||
|             else: | ||||
|                 data = filter(data) | ||||
|             if data is None: | ||||
|                 return | ||||
|  | ||||
|         if isinstance(data, ElementBase): | ||||
|             for filter in self.__filters['out_sync']: | ||||
|                 data = filter(data) | ||||
|                 if data is None: | ||||
|                     return | ||||
|             str_data = tostring(data.xml, xmlns=self.default_ns, | ||||
|                                 stream=self, top_level=True) | ||||
|             self.send_raw(str_data) | ||||
|         else: | ||||
|             self.send_raw(data) | ||||
|  | ||||
|  | ||||
|     async def run_filters(self): | ||||
|         """ | ||||
|         Background loop that processes stanzas to send. | ||||
|         """ | ||||
|         while True: | ||||
|             (data, use_filters) = await self.waiting_queue.get() | ||||
|             try: | ||||
|                 if isinstance(data, ElementBase): | ||||
|                     if use_filters: | ||||
|                         already_run_filters = set() | ||||
|                         for filter in self.__filters['out']: | ||||
|                             already_run_filters.add(filter) | ||||
|                             if iscoroutinefunction(filter): | ||||
|                                 task = asyncio.create_task(filter(data)) | ||||
|                                 completed, pending = await wait( | ||||
|                                     {task}, | ||||
|                                     timeout=1, | ||||
|                                 ) | ||||
|                                 if pending: | ||||
|                                     asyncio.ensure_future( | ||||
|                                         self._continue_slow_send( | ||||
|                                             task, | ||||
|                                             already_run_filters | ||||
|                                         ) | ||||
|                                     ) | ||||
|                                     raise Exception("Slow coro, rescheduling") | ||||
|                                 data = task.result() | ||||
|                             else: | ||||
|                                 data = filter(data) | ||||
|                             if data is None: | ||||
|                                 raise ContinueQueue('Empty stanza') | ||||
|  | ||||
|                 if isinstance(data, ElementBase): | ||||
|                     if use_filters: | ||||
|                         for filter in self.__filters['out_sync']: | ||||
|                             data = filter(data) | ||||
|                             if data is None: | ||||
|                                 raise ContinueQueue('Empty stanza') | ||||
|                     str_data = tostring(data.xml, xmlns=self.default_ns, | ||||
|                                         stream=self, top_level=True) | ||||
|                     self.send_raw(str_data) | ||||
|                 else: | ||||
|                     self.send_raw(data) | ||||
|             except ContinueQueue as exc: | ||||
|                 log.debug('Stanza in send queue not sent: %s', exc) | ||||
|             except Exception: | ||||
|                 log.error('Exception raised in send queue:', exc_info=True) | ||||
|             self.waiting_queue.task_done() | ||||
|  | ||||
|     def send(self, data, use_filters=True): | ||||
|         """A wrapper for :meth:`send_raw()` for sending stanza objects. | ||||
|  | ||||
|         May optionally block until an expected response is received. | ||||
|  | ||||
|         :param data: The :class:`~slixmpp.xmlstream.stanzabase.ElementBase` | ||||
|                      stanza to send on the stream. | ||||
|         :param bool use_filters: Indicates if outgoing filters should be | ||||
| @@ -901,24 +1023,7 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|                                  filters is useful when resending stanzas. | ||||
|                                  Defaults to ``True``. | ||||
|         """ | ||||
|         if isinstance(data, ElementBase): | ||||
|             if use_filters: | ||||
|                 for filter in self.__filters['out']: | ||||
|                     data = filter(data) | ||||
|                     if data is None: | ||||
|                         return | ||||
|  | ||||
|         if isinstance(data, ElementBase): | ||||
|             if use_filters: | ||||
|                 for filter in self.__filters['out_sync']: | ||||
|                     data = filter(data) | ||||
|                     if data is None: | ||||
|                         return | ||||
|             str_data = tostring(data.xml, xmlns=self.default_ns, | ||||
|                                 stream=self, top_level=True) | ||||
|             self.send_raw(str_data) | ||||
|         else: | ||||
|             self.send_raw(data) | ||||
|         self.waiting_queue.put_nowait((data, use_filters)) | ||||
|  | ||||
|     def send_xml(self, data): | ||||
|         """Send an XML object on the stream | ||||
|   | ||||
| @@ -68,13 +68,5 @@ class TestStanzaBase(SlixTest): | ||||
|         self.assertTrue(stanza['payload'] == [], | ||||
|             "Stanza reply did not empty stanza payload.") | ||||
|  | ||||
|     def testError(self): | ||||
|         """Test marking a stanza as an error.""" | ||||
|         stanza = StanzaBase() | ||||
|         stanza['type'] = 'get' | ||||
|         stanza.error() | ||||
|         self.assertTrue(stanza['type'] == 'error', | ||||
|             "Stanza type is not 'error' after calling error()") | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestStanzaBase) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import sys | ||||
| import datetime | ||||
| import time | ||||
| import threading | ||||
| import unittest | ||||
| import re | ||||
|  | ||||
| from slixmpp.test import * | ||||
| @@ -11,6 +12,7 @@ from slixmpp.xmlstream import ElementBase | ||||
| from slixmpp.plugins.xep_0323.device import Device | ||||
|  | ||||
|  | ||||
| @unittest.skip('') | ||||
| class TestStreamSensorData(SlixTest): | ||||
|  | ||||
|     """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user