Compare commits
173 Commits
1.1.10
...
hildjj-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48def71d0c | ||
|
|
12e8bb6ddc | ||
|
|
c8c20fff71 | ||
|
|
75a18b5ffe | ||
|
|
06a690a259 | ||
|
|
52feabbe76 | ||
|
|
14c9e9a9cc | ||
|
|
a22ca228cc | ||
|
|
d0666a5eb6 | ||
|
|
931d49560a | ||
|
|
2a4e435228 | ||
|
|
3655827ef2 | ||
|
|
c5046b9c91 | ||
|
|
4598031dd2 | ||
|
|
12e0e1a16b | ||
|
|
5e9266ba90 | ||
|
|
0d448b8221 | ||
|
|
e6c95f0a2a | ||
|
|
63b58edda1 | ||
|
|
af9632519c | ||
|
|
d367fb938d | ||
|
|
77f2a339e1 | ||
|
|
4190027a78 | ||
|
|
ef48a8c4d9 | ||
|
|
829b225053 | ||
|
|
747a6e94e6 | ||
|
|
cebc798e72 | ||
|
|
7c485c6a8b | ||
|
|
e2e8c4b5dc | ||
|
|
675c0112ac | ||
|
|
4dd2c15775 | ||
|
|
9f6decdbc1 | ||
|
|
fc07e23ff8 | ||
|
|
4ea328b9f2 | ||
|
|
84a2fc382b | ||
|
|
098714b3c4 | ||
|
|
cf2c94d974 | ||
|
|
657102e938 | ||
|
|
44e7585bf8 | ||
|
|
94488fa2ea | ||
|
|
a2c60a4911 | ||
|
|
ee9c4abd08 | ||
|
|
b5b1c932c7 | ||
|
|
b8f04983e1 | ||
|
|
90807dd973 | ||
|
|
ef974114ea | ||
|
|
f6e1fecdf8 | ||
|
|
94e8b2becf | ||
|
|
a6ca6701a0 | ||
|
|
c4edb9724b | ||
|
|
b5c669bdff | ||
|
|
e449dce65c | ||
|
|
73ce9a5ecc | ||
|
|
671f680bb3 | ||
|
|
dfff19ffbf | ||
|
|
a4abdf9fa6 | ||
|
|
6c57bb0553 | ||
|
|
d385b9e708 | ||
|
|
c2ae1ee891 | ||
|
|
67147570e9 | ||
|
|
fb3e6b7e35 | ||
|
|
cf28d4586d | ||
|
|
f65eb5eeea | ||
|
|
26fa9bd87e | ||
|
|
0016d9a638 | ||
|
|
a88b9737ff | ||
|
|
df9ac58d05 | ||
|
|
357406d801 | ||
|
|
19a78f63f4 | ||
|
|
c7ec6a72cd | ||
|
|
e68b07dbce | ||
|
|
e20610ab80 | ||
|
|
1ca0c46333 | ||
|
|
e510875f64 | ||
|
|
f52a10b061 | ||
|
|
7d382a2bfd | ||
|
|
09bec1c4fe | ||
|
|
ff28b0a005 | ||
|
|
8a03bd72ae | ||
|
|
a249f8736a | ||
|
|
f0e1fc5aad | ||
|
|
f09adf0014 | ||
|
|
c6ac64ed2d | ||
|
|
04dc68f5f6 | ||
|
|
92be051450 | ||
|
|
5c25208fb5 | ||
|
|
779c258e27 | ||
|
|
962dfad216 | ||
|
|
f7a710e55b | ||
|
|
814a50e36f | ||
|
|
230465b946 | ||
|
|
d11a67702e | ||
|
|
4e12e228cb | ||
|
|
4a94aeba49 | ||
|
|
14aa831169 | ||
|
|
295d23ccf3 | ||
|
|
75d904ed01 | ||
|
|
aebcf6ff82 | ||
|
|
8c2ece3bca | ||
|
|
80a90a6221 | ||
|
|
f81d5e4bd6 | ||
|
|
2324c90232 | ||
|
|
2f65fdbc76 | ||
|
|
59ff08174f | ||
|
|
2f4149c7d0 | ||
|
|
b84e359770 | ||
|
|
fb4275648c | ||
|
|
475ccfa8dc | ||
|
|
267c24c8ef | ||
|
|
06a9d9fc30 | ||
|
|
1383ca19b5 | ||
|
|
4c3ff2abab | ||
|
|
7c6ef18e4f | ||
|
|
f8856467d5 | ||
|
|
3bd84b8d27 | ||
|
|
bc8b5774ac | ||
|
|
8009b0485e | ||
|
|
8742a56b3e | ||
|
|
a792bcdafe | ||
|
|
167d1ce97b | ||
|
|
695cd95657 | ||
|
|
44ce01a70b | ||
|
|
c2189b4ecd | ||
|
|
c9b2cf6043 | ||
|
|
16ec0f151a | ||
|
|
c42f1ad4c7 | ||
|
|
a3ec1af205 | ||
|
|
2e580304f9 | ||
|
|
5492e9028d | ||
|
|
060c9ab679 | ||
|
|
78f0325398 | ||
|
|
1efe049959 | ||
|
|
2393148908 | ||
|
|
c7594b3ef0 | ||
|
|
b210870f48 | ||
|
|
5d6019a962 | ||
|
|
eb5df1aa37 | ||
|
|
546066d677 | ||
|
|
3234596974 | ||
|
|
5820d49cd4 | ||
|
|
1ab66e5767 | ||
|
|
aab2682f9a | ||
|
|
55d332bcc8 | ||
|
|
f89df6e70c | ||
|
|
250d28e870 | ||
|
|
19f65c8510 | ||
|
|
f70b49882f | ||
|
|
a7b092a305 | ||
|
|
daa73a3f3c | ||
|
|
0b51afe87a | ||
|
|
2b298766c9 | ||
|
|
10664d723b | ||
|
|
c012208a8f | ||
|
|
0953896d2d | ||
|
|
cf9e89d0ae | ||
|
|
48dd01b0bb | ||
|
|
7247efe055 | ||
|
|
8def3758e4 | ||
|
|
1851ab6f5f | ||
|
|
289b052338 | ||
|
|
26147f5ae0 | ||
|
|
ae01f1071a | ||
|
|
dcdf5dcd09 | ||
|
|
c59a6d0f51 | ||
|
|
2cd936318d | ||
|
|
2f38857681 | ||
|
|
39505ae1ff | ||
|
|
44ee0633f2 | ||
|
|
b52d2768b0 | ||
|
|
cf24b870b1 | ||
|
|
69cffce7dc | ||
|
|
a14979375b | ||
|
|
40ef4a16b1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ docs/_build/
|
|||||||
.tox/
|
.tox/
|
||||||
.coverage
|
.coverage
|
||||||
sleekxmpp.egg-info/
|
sleekxmpp.egg-info/
|
||||||
|
.ropeproject/
|
||||||
|
|||||||
29
LICENSE
29
LICENSE
@@ -69,8 +69,8 @@ modification, are permitted provided that the following conditions are met:
|
|||||||
* Redistributions in binary form must reproduce the above copyright
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
notice, this list of conditions and the following disclaimer in the
|
notice, this list of conditions and the following disclaimer in the
|
||||||
documentation and/or other materials provided with the distribution.
|
documentation and/or other materials provided with the distribution.
|
||||||
* Neither the name of Red Innovation nor the names of its contributors
|
* Neither the name of Red Innovation nor the names of its contributors
|
||||||
may be used to endorse or promote products derived from this software
|
may be used to endorse or promote products derived from this software
|
||||||
without specific prior written permission.
|
without specific prior written permission.
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
|
THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
|
||||||
@@ -167,3 +167,28 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
socksipy: A Python SOCKS client module.
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Copyright 2006 Dan-Haim. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||||
|
to endorse or promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||||
|
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||||
|
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ use ASCII. We can get Python to use UTF-8 as the default encoding by including:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ from stanza import Action
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ class ActionBot(sleekxmpp.ClientXMPP):
|
|||||||
StanzaPath('iq@type=set/action'),
|
StanzaPath('iq@type=set/action'),
|
||||||
self._handle_action))
|
self._handle_action))
|
||||||
|
|
||||||
self.add_event_handler('custom_action',
|
self.add_event_handler('custom_action',
|
||||||
self._handle_action_event,
|
self._handle_action_event,
|
||||||
threaded=True)
|
threaded=True)
|
||||||
|
|
||||||
register_stanza_plugin(Iq, Action)
|
register_stanza_plugin(Iq, Action)
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from stanza import Action
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import getpass
|
|||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
import sleekxmpp
|
import sleekxmpp
|
||||||
|
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
@@ -22,8 +23,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
@@ -83,50 +84,54 @@ class Disco(sleekxmpp.ClientXMPP):
|
|||||||
self.get_roster()
|
self.get_roster()
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
|
|
||||||
if self.get in self.info_types:
|
try:
|
||||||
# By using block=True, the result stanza will be
|
if self.get in self.info_types:
|
||||||
# returned. Execution will block until the reply is
|
# By using block=True, the result stanza will be
|
||||||
# received. Non-blocking options would be to listen
|
# returned. Execution will block until the reply is
|
||||||
# for the disco_info event, or passing a handler
|
# received. Non-blocking options would be to listen
|
||||||
# function using the callback parameter.
|
# for the disco_info event, or passing a handler
|
||||||
info = self['xep_0030'].get_info(jid=self.target_jid,
|
# function using the callback parameter.
|
||||||
node=self.target_node,
|
info = self['xep_0030'].get_info(jid=self.target_jid,
|
||||||
block=True)
|
node=self.target_node,
|
||||||
if self.get in self.items_types:
|
block=True)
|
||||||
# The same applies from above. Listen for the
|
elif self.get in self.items_types:
|
||||||
# disco_items event or pass a callback function
|
# The same applies from above. Listen for the
|
||||||
# if you need to process a non-blocking request.
|
# disco_items event or pass a callback function
|
||||||
items = self['xep_0030'].get_items(jid=self.target_jid,
|
# if you need to process a non-blocking request.
|
||||||
node=self.target_node,
|
items = self['xep_0030'].get_items(jid=self.target_jid,
|
||||||
block=True)
|
node=self.target_node,
|
||||||
|
block=True)
|
||||||
|
else:
|
||||||
|
logging.error("Invalid disco request type.")
|
||||||
|
return
|
||||||
|
except IqError as e:
|
||||||
|
logging.error("Entity returned an error: %s" % e.iq['error']['condition'])
|
||||||
|
except IqTimeout:
|
||||||
|
logging.error("No response received.")
|
||||||
else:
|
else:
|
||||||
logging.error("Invalid disco request type.")
|
header = 'XMPP Service Discovery: %s' % self.target_jid
|
||||||
self.disconnect()
|
print(header)
|
||||||
return
|
|
||||||
|
|
||||||
header = 'XMPP Service Discovery: %s' % self.target_jid
|
|
||||||
print(header)
|
|
||||||
print('-' * len(header))
|
|
||||||
if self.target_node != '':
|
|
||||||
print('Node: %s' % self.target_node)
|
|
||||||
print('-' * len(header))
|
print('-' * len(header))
|
||||||
|
if self.target_node != '':
|
||||||
|
print('Node: %s' % self.target_node)
|
||||||
|
print('-' * len(header))
|
||||||
|
|
||||||
if self.get in self.identity_types:
|
if self.get in self.identity_types:
|
||||||
print('Identities:')
|
print('Identities:')
|
||||||
for identity in info['disco_info']['identities']:
|
for identity in info['disco_info']['identities']:
|
||||||
print(' - %s' % str(identity))
|
print(' - %s' % str(identity))
|
||||||
|
|
||||||
if self.get in self.feature_types:
|
if self.get in self.feature_types:
|
||||||
print('Features:')
|
print('Features:')
|
||||||
for feature in info['disco_info']['features']:
|
for feature in info['disco_info']['features']:
|
||||||
print(' - %s' % feature)
|
print(' - %s' % feature)
|
||||||
|
|
||||||
if self.get in self.items_types:
|
if self.get in self.items_types:
|
||||||
print('Items:')
|
print('Items:')
|
||||||
for item in items['disco_items']['items']:
|
for item in items['disco_items']['items']:
|
||||||
print(' - %s' % str(item))
|
print(' - %s' % str(item))
|
||||||
|
finally:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ from sleekxmpp.exceptions import XMPPError
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ from sleekxmpp.componentxmpp import ComponentXMPP
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ from sleekxmpp.xmlstream import cert
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
|
|||||||
def stream_opened(self, stream):
|
def stream_opened(self, stream):
|
||||||
# NOTE: IBB streams are bi-directional, so the original sender is
|
# NOTE: IBB streams are bi-directional, so the original sender is
|
||||||
# now the opened stream's receiver.
|
# now the opened stream's receiver.
|
||||||
print('Stream opened: %s from ' % (stream.sid, stream.receiver))
|
print('Stream opened: %s from %s' % (stream.sid, stream.receiver))
|
||||||
|
|
||||||
# You could run a loop reading from the stream using stream.recv(),
|
# You could run a loop reading from the stream using stream.recv(),
|
||||||
# or use the ibb_stream_data event.
|
# or use the ibb_stream_data event.
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ from sleekxmpp.xmlstream import ET, tostring
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ from sleekxmpp.xmlstream.handler import Callback
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from sleekxmpp.exceptions import XMPPError
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ from sleekxmpp.xmlstream import JID
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
else:
|
else:
|
||||||
raw_input = input
|
raw_input = input
|
||||||
|
|
||||||
|
|||||||
16
setup.py
16
setup.py
@@ -50,6 +50,7 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/test',
|
'sleekxmpp/test',
|
||||||
'sleekxmpp/roster',
|
'sleekxmpp/roster',
|
||||||
'sleekxmpp/util',
|
'sleekxmpp/util',
|
||||||
|
'sleekxmpp/util/sasl',
|
||||||
'sleekxmpp/xmlstream',
|
'sleekxmpp/xmlstream',
|
||||||
'sleekxmpp/xmlstream/matcher',
|
'sleekxmpp/xmlstream/matcher',
|
||||||
'sleekxmpp/xmlstream/handler',
|
'sleekxmpp/xmlstream/handler',
|
||||||
@@ -59,16 +60,20 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0009',
|
'sleekxmpp/plugins/xep_0009',
|
||||||
'sleekxmpp/plugins/xep_0009/stanza',
|
'sleekxmpp/plugins/xep_0009/stanza',
|
||||||
'sleekxmpp/plugins/xep_0012',
|
'sleekxmpp/plugins/xep_0012',
|
||||||
|
'sleekxmpp/plugins/xep_0013',
|
||||||
|
'sleekxmpp/plugins/xep_0016',
|
||||||
'sleekxmpp/plugins/xep_0027',
|
'sleekxmpp/plugins/xep_0027',
|
||||||
'sleekxmpp/plugins/xep_0030',
|
'sleekxmpp/plugins/xep_0030',
|
||||||
'sleekxmpp/plugins/xep_0030/stanza',
|
'sleekxmpp/plugins/xep_0030/stanza',
|
||||||
'sleekxmpp/plugins/xep_0033',
|
'sleekxmpp/plugins/xep_0033',
|
||||||
'sleekxmpp/plugins/xep_0047',
|
'sleekxmpp/plugins/xep_0047',
|
||||||
|
'sleekxmpp/plugins/xep_0049',
|
||||||
'sleekxmpp/plugins/xep_0050',
|
'sleekxmpp/plugins/xep_0050',
|
||||||
'sleekxmpp/plugins/xep_0054',
|
'sleekxmpp/plugins/xep_0054',
|
||||||
'sleekxmpp/plugins/xep_0059',
|
'sleekxmpp/plugins/xep_0059',
|
||||||
'sleekxmpp/plugins/xep_0060',
|
'sleekxmpp/plugins/xep_0060',
|
||||||
'sleekxmpp/plugins/xep_0060/stanza',
|
'sleekxmpp/plugins/xep_0060/stanza',
|
||||||
|
'sleekxmpp/plugins/xep_0065',
|
||||||
'sleekxmpp/plugins/xep_0066',
|
'sleekxmpp/plugins/xep_0066',
|
||||||
'sleekxmpp/plugins/xep_0077',
|
'sleekxmpp/plugins/xep_0077',
|
||||||
'sleekxmpp/plugins/xep_0078',
|
'sleekxmpp/plugins/xep_0078',
|
||||||
@@ -76,6 +81,7 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0084',
|
'sleekxmpp/plugins/xep_0084',
|
||||||
'sleekxmpp/plugins/xep_0085',
|
'sleekxmpp/plugins/xep_0085',
|
||||||
'sleekxmpp/plugins/xep_0086',
|
'sleekxmpp/plugins/xep_0086',
|
||||||
|
'sleekxmpp/plugins/xep_0091',
|
||||||
'sleekxmpp/plugins/xep_0092',
|
'sleekxmpp/plugins/xep_0092',
|
||||||
'sleekxmpp/plugins/xep_0107',
|
'sleekxmpp/plugins/xep_0107',
|
||||||
'sleekxmpp/plugins/xep_0108',
|
'sleekxmpp/plugins/xep_0108',
|
||||||
@@ -95,8 +101,15 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0221',
|
'sleekxmpp/plugins/xep_0221',
|
||||||
'sleekxmpp/plugins/xep_0224',
|
'sleekxmpp/plugins/xep_0224',
|
||||||
'sleekxmpp/plugins/xep_0231',
|
'sleekxmpp/plugins/xep_0231',
|
||||||
|
'sleekxmpp/plugins/xep_0235',
|
||||||
'sleekxmpp/plugins/xep_0249',
|
'sleekxmpp/plugins/xep_0249',
|
||||||
|
'sleekxmpp/plugins/xep_0257',
|
||||||
'sleekxmpp/plugins/xep_0258',
|
'sleekxmpp/plugins/xep_0258',
|
||||||
|
'sleekxmpp/plugins/xep_0279',
|
||||||
|
'sleekxmpp/plugins/xep_0280',
|
||||||
|
'sleekxmpp/plugins/xep_0297',
|
||||||
|
'sleekxmpp/plugins/xep_0308',
|
||||||
|
'sleekxmpp/plugins/xep_0313',
|
||||||
'sleekxmpp/features',
|
'sleekxmpp/features',
|
||||||
'sleekxmpp/features/feature_mechanisms',
|
'sleekxmpp/features/feature_mechanisms',
|
||||||
'sleekxmpp/features/feature_mechanisms/stanza',
|
'sleekxmpp/features/feature_mechanisms/stanza',
|
||||||
@@ -104,9 +117,8 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/features/feature_bind',
|
'sleekxmpp/features/feature_bind',
|
||||||
'sleekxmpp/features/feature_session',
|
'sleekxmpp/features/feature_session',
|
||||||
'sleekxmpp/features/feature_rosterver',
|
'sleekxmpp/features/feature_rosterver',
|
||||||
|
'sleekxmpp/features/feature_preapproval',
|
||||||
'sleekxmpp/thirdparty',
|
'sleekxmpp/thirdparty',
|
||||||
'sleekxmpp/thirdparty/suelta',
|
|
||||||
'sleekxmpp/thirdparty/suelta/mechanisms',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ log = logging.getLogger(__name__)
|
|||||||
# In order to make sure that Unicode is handled properly
|
# In order to make sure that Unicode is handled properly
|
||||||
# in Python 2.x, reset the default encoding.
|
# in Python 2.x, reset the default encoding.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
|
|
||||||
|
|
||||||
class BaseXMPP(XMLStream):
|
class BaseXMPP(XMLStream):
|
||||||
@@ -68,8 +68,13 @@ class BaseXMPP(XMLStream):
|
|||||||
#: An identifier for the stream as given by the server.
|
#: An identifier for the stream as given by the server.
|
||||||
self.stream_id = None
|
self.stream_id = None
|
||||||
|
|
||||||
#: The JabberID (JID) used by this connection.
|
#: The JabberID (JID) requested for this connection.
|
||||||
self.boundjid = JID(jid)
|
self.requested_jid = JID(jid, cache_lock=True)
|
||||||
|
|
||||||
|
#: The JabberID (JID) used by this connection,
|
||||||
|
#: as set after session binding. This may even be a
|
||||||
|
#: different bare JID than what was requested.
|
||||||
|
self.boundjid = JID(jid, cache_lock=True)
|
||||||
|
|
||||||
self._expected_server_name = self.boundjid.host
|
self._expected_server_name = self.boundjid.host
|
||||||
self._redirect_attempts = 0
|
self._redirect_attempts = 0
|
||||||
@@ -109,6 +114,17 @@ class BaseXMPP(XMLStream):
|
|||||||
#: ``'to'`` and ``'from'`` JIDs of stanzas.
|
#: ``'to'`` and ``'from'`` JIDs of stanzas.
|
||||||
self.is_component = False
|
self.is_component = False
|
||||||
|
|
||||||
|
#: Messages may optionally be tagged with ID values. Setting
|
||||||
|
#: :attr:`use_message_ids` to `True` will assign all outgoing
|
||||||
|
#: messages an ID. Some plugin features require enabling
|
||||||
|
#: this option.
|
||||||
|
self.use_message_ids = False
|
||||||
|
|
||||||
|
#: Presence updates may optionally be tagged with ID values.
|
||||||
|
#: Setting :attr:`use_message_ids` to `True` will assign all
|
||||||
|
#: outgoing messages an ID.
|
||||||
|
self.use_presence_ids = False
|
||||||
|
|
||||||
#: The API registry is a way to process callbacks based on
|
#: The API registry is a way to process callbacks based on
|
||||||
#: JID+node combinations. Each callback in the registry is
|
#: JID+node combinations. Each callback in the registry is
|
||||||
#: marked with:
|
#: marked with:
|
||||||
@@ -196,6 +212,10 @@ class BaseXMPP(XMLStream):
|
|||||||
self.stream_version = xml.get('version', '')
|
self.stream_version = xml.get('version', '')
|
||||||
self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
|
self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
|
||||||
|
|
||||||
|
if not self.is_component and not self.stream_version:
|
||||||
|
log.warning('Legacy XMPP 0.9 protocol detected.')
|
||||||
|
self.event('legacy_protocol')
|
||||||
|
|
||||||
def process(self, *args, **kwargs):
|
def process(self, *args, **kwargs):
|
||||||
"""Initialize plugins and begin processing the XML stream.
|
"""Initialize plugins and begin processing the XML stream.
|
||||||
|
|
||||||
@@ -221,13 +241,6 @@ class BaseXMPP(XMLStream):
|
|||||||
- The send queue processor
|
- The send queue processor
|
||||||
- The scheduler
|
- The scheduler
|
||||||
"""
|
"""
|
||||||
if 'xep_0115' in self.plugin:
|
|
||||||
name = 'xep_0115'
|
|
||||||
if not hasattr(self.plugin[name], 'post_inited'):
|
|
||||||
if hasattr(self.plugin[name], 'post_init'):
|
|
||||||
self.plugin[name].post_init()
|
|
||||||
self.plugin[name].post_inited = True
|
|
||||||
|
|
||||||
for name in self.plugin:
|
for name in self.plugin:
|
||||||
if not hasattr(self.plugin[name], 'post_inited'):
|
if not hasattr(self.plugin[name], 'post_inited'):
|
||||||
if hasattr(self.plugin[name], 'post_init'):
|
if hasattr(self.plugin[name], 'post_init'):
|
||||||
@@ -652,7 +665,7 @@ class BaseXMPP(XMLStream):
|
|||||||
def set_jid(self, jid):
|
def set_jid(self, jid):
|
||||||
"""Rip a JID apart and claim it as our own."""
|
"""Rip a JID apart and claim it as our own."""
|
||||||
log.debug("setting jid to %s", jid)
|
log.debug("setting jid to %s", jid)
|
||||||
self.boundjid.full = jid
|
self.boundjid = JID(jid, cache_lock=True)
|
||||||
|
|
||||||
def getjidresource(self, fulljid):
|
def getjidresource(self, fulljid):
|
||||||
if '/' in fulljid:
|
if '/' in fulljid:
|
||||||
@@ -733,6 +746,8 @@ class BaseXMPP(XMLStream):
|
|||||||
item = self.roster[pres['to']][pres['from']]
|
item = self.roster[pres['to']][pres['from']]
|
||||||
if item['whitelisted']:
|
if item['whitelisted']:
|
||||||
item.authorize()
|
item.authorize()
|
||||||
|
if roster.auto_subscribe:
|
||||||
|
item.subscribe()
|
||||||
elif roster.auto_authorize:
|
elif roster.auto_authorize:
|
||||||
item.authorize()
|
item.authorize()
|
||||||
if roster.auto_subscribe:
|
if roster.auto_subscribe:
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ class ClientXMPP(BaseXMPP):
|
|||||||
escape_quotes=True, sasl_mech=None, lang='en'):
|
escape_quotes=True, sasl_mech=None, lang='en'):
|
||||||
BaseXMPP.__init__(self, jid, 'jabber:client')
|
BaseXMPP.__init__(self, jid, 'jabber:client')
|
||||||
|
|
||||||
self.set_jid(jid)
|
|
||||||
self.escape_quotes = escape_quotes
|
self.escape_quotes = escape_quotes
|
||||||
self.plugin_config = plugin_config
|
self.plugin_config = plugin_config
|
||||||
self.plugin_whitelist = plugin_whitelist
|
self.plugin_whitelist = plugin_whitelist
|
||||||
@@ -95,7 +94,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
self.bound = False
|
self.bound = False
|
||||||
self.bindfail = False
|
self.bindfail = False
|
||||||
|
|
||||||
self.add_event_handler('connected', self._handle_connected)
|
self.add_event_handler('connected', self._reset_connection_state)
|
||||||
self.add_event_handler('session_bind', self._handle_session_bind)
|
self.add_event_handler('session_bind', self._handle_session_bind)
|
||||||
|
|
||||||
self.register_stanza(StreamFeatures)
|
self.register_stanza(StreamFeatures)
|
||||||
@@ -113,9 +112,10 @@ class ClientXMPP(BaseXMPP):
|
|||||||
self.register_plugin('feature_starttls')
|
self.register_plugin('feature_starttls')
|
||||||
self.register_plugin('feature_bind')
|
self.register_plugin('feature_bind')
|
||||||
self.register_plugin('feature_session')
|
self.register_plugin('feature_session')
|
||||||
|
self.register_plugin('feature_rosterver')
|
||||||
|
self.register_plugin('feature_preapproval')
|
||||||
self.register_plugin('feature_mechanisms',
|
self.register_plugin('feature_mechanisms',
|
||||||
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
|
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
|
||||||
self.register_plugin('feature_rosterver')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def password(self):
|
def password(self):
|
||||||
@@ -252,7 +252,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
self._handle_roster(response)
|
self._handle_roster(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _handle_connected(self, event=None):
|
def _reset_connection_state(self, event=None):
|
||||||
#TODO: Use stream state here
|
#TODO: Use stream state here
|
||||||
self.authenticated = False
|
self.authenticated = False
|
||||||
self.sessionstarted = False
|
self.sessionstarted = False
|
||||||
@@ -272,6 +272,8 @@ class ClientXMPP(BaseXMPP):
|
|||||||
# Don't continue if the feature requires
|
# Don't continue if the feature requires
|
||||||
# restarting the XML stream.
|
# restarting the XML stream.
|
||||||
return True
|
return True
|
||||||
|
log.debug('Finished processing stream features.')
|
||||||
|
self.event('stream_negotiated')
|
||||||
|
|
||||||
def _handle_roster(self, iq):
|
def _handle_roster(self, iq):
|
||||||
"""Update the roster after receiving a roster stanza.
|
"""Update the roster after receiving a roster stanza.
|
||||||
@@ -286,15 +288,17 @@ class ClientXMPP(BaseXMPP):
|
|||||||
if iq['roster']['ver']:
|
if iq['roster']['ver']:
|
||||||
roster.version = iq['roster']['ver']
|
roster.version = iq['roster']['ver']
|
||||||
items = iq['roster']['items']
|
items = iq['roster']['items']
|
||||||
for jid in items:
|
|
||||||
item = items[jid]
|
|
||||||
roster[jid]['name'] = item['name']
|
|
||||||
roster[jid]['groups'] = item['groups']
|
|
||||||
roster[jid]['from'] = item['subscription'] in ['from', 'both']
|
|
||||||
roster[jid]['to'] = item['subscription'] in ['to', 'both']
|
|
||||||
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
|
||||||
|
|
||||||
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
valid_subscriptions = ('to', 'from', 'both', 'none', 'remove')
|
||||||
|
for jid, item in items.items():
|
||||||
|
if item['subscription'] in valid_subscriptions:
|
||||||
|
roster[jid]['name'] = item['name']
|
||||||
|
roster[jid]['groups'] = item['groups']
|
||||||
|
roster[jid]['from'] = item['subscription'] in ('from', 'both')
|
||||||
|
roster[jid]['to'] = item['subscription'] in ('to', 'both')
|
||||||
|
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
||||||
|
|
||||||
|
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
||||||
|
|
||||||
self.event("roster_update", iq)
|
self.event("roster_update", iq)
|
||||||
if iq['type'] == 'set':
|
if iq['type'] == 'set':
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ __all__ = [
|
|||||||
'feature_mechanisms',
|
'feature_mechanisms',
|
||||||
'feature_bind',
|
'feature_bind',
|
||||||
'feature_session',
|
'feature_session',
|
||||||
'feature_rosterver'
|
'feature_rosterver',
|
||||||
|
'feature_preapproval'
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.jid import JID
|
||||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
from sleekxmpp.stanza import Iq, StreamFeatures
|
||||||
from sleekxmpp.features.feature_bind import stanza
|
from sleekxmpp.features.feature_bind import stanza
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
@@ -48,7 +49,7 @@ class FeatureBind(BasePlugin):
|
|||||||
iq['bind']['resource'] = self.xmpp.boundjid.resource
|
iq['bind']['resource'] = self.xmpp.boundjid.resource
|
||||||
response = iq.send(now=True)
|
response = iq.send(now=True)
|
||||||
|
|
||||||
self.xmpp.set_jid(response['bind']['jid'])
|
self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True)
|
||||||
self.xmpp.bound = True
|
self.xmpp.bound = True
|
||||||
self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True)
|
self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True)
|
||||||
self.xmpp.session_bind_event.set()
|
self.xmpp.session_bind_event.set()
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import ssl
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sleekxmpp.thirdparty import suelta
|
from sleekxmpp.util import sasl
|
||||||
from sleekxmpp.thirdparty.suelta.exceptions import SASLCancelled, SASLError
|
from sleekxmpp.util.stringprep_profiles import StringPrepError
|
||||||
from sleekxmpp.thirdparty.suelta.exceptions import SASLPrepFailure
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures
|
from sleekxmpp.stanza import StreamFeatures
|
||||||
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
|
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
|
||||||
from sleekxmpp.plugins import BasePlugin
|
from sleekxmpp.plugins import BasePlugin
|
||||||
@@ -31,42 +31,29 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
stanza = stanza
|
stanza = stanza
|
||||||
default_config = {
|
default_config = {
|
||||||
'use_mech': None,
|
'use_mech': None,
|
||||||
|
'use_mechs': None,
|
||||||
|
'min_mech': None,
|
||||||
'sasl_callback': None,
|
'sasl_callback': None,
|
||||||
|
'security_callback': None,
|
||||||
|
'encrypted_plain': True,
|
||||||
|
'unencrypted_plain': False,
|
||||||
|
'unencrypted_digest': False,
|
||||||
|
'unencrypted_cram': False,
|
||||||
|
'unencrypted_scram': True,
|
||||||
'order': 100
|
'order': 100
|
||||||
}
|
}
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
if not self.use_mech and not self.xmpp.boundjid.user:
|
if not self.use_mech and not self.xmpp.requested_jid.user:
|
||||||
self.use_mech = 'ANONYMOUS'
|
self.use_mech = 'ANONYMOUS'
|
||||||
|
|
||||||
def tls_active():
|
|
||||||
return 'starttls' in self.xmpp.features
|
|
||||||
|
|
||||||
def basic_callback(mech, values):
|
|
||||||
creds = self.xmpp.credentials
|
|
||||||
for value in values:
|
|
||||||
if value == 'username':
|
|
||||||
values['username'] = self.xmpp.boundjid.user
|
|
||||||
elif value == 'password':
|
|
||||||
values['password'] = creds['password']
|
|
||||||
elif value == 'email':
|
|
||||||
jid = self.xmpp.boundjid.bare
|
|
||||||
values['email'] = creds.get('email', jid)
|
|
||||||
elif value in creds:
|
|
||||||
values[value] = creds[value]
|
|
||||||
mech.fulfill(values)
|
|
||||||
|
|
||||||
if self.sasl_callback is None:
|
if self.sasl_callback is None:
|
||||||
self.sasl_callback = basic_callback
|
self.sasl_callback = self._default_credentials
|
||||||
|
|
||||||
|
if self.security_callback is None:
|
||||||
|
self.security_callback = self._default_security
|
||||||
|
|
||||||
self.mech = None
|
self.mech = None
|
||||||
self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp',
|
|
||||||
username=self.xmpp.boundjid.user,
|
|
||||||
sec_query=suelta.sec_query_allow,
|
|
||||||
request_values=self.sasl_callback,
|
|
||||||
tls_active=tls_active,
|
|
||||||
mech=self.use_mech)
|
|
||||||
|
|
||||||
self.mech_list = set()
|
self.mech_list = set()
|
||||||
self.attempted_mechs = set()
|
self.attempted_mechs = set()
|
||||||
|
|
||||||
@@ -99,6 +86,51 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
restart=True,
|
restart=True,
|
||||||
order=self.order)
|
order=self.order)
|
||||||
|
|
||||||
|
def _default_credentials(self, required_values, optional_values):
|
||||||
|
creds = self.xmpp.credentials
|
||||||
|
result = {}
|
||||||
|
values = required_values.union(optional_values)
|
||||||
|
for value in values:
|
||||||
|
if value == 'username':
|
||||||
|
result[value] = self.xmpp.requested_jid.user
|
||||||
|
elif value == 'password':
|
||||||
|
result[value] = creds['password']
|
||||||
|
elif value == 'authzid':
|
||||||
|
result[value] = creds.get('authzid', '')
|
||||||
|
elif value == 'email':
|
||||||
|
jid = self.xmpp.requested_jid.bare
|
||||||
|
result[value] = creds.get('email', jid)
|
||||||
|
elif value == 'channel_binding':
|
||||||
|
if sys.version_info >= (3, 3):
|
||||||
|
result[value] = self.xmpp.socket.get_channel_binding()
|
||||||
|
else:
|
||||||
|
result[value] = None
|
||||||
|
elif value == 'host':
|
||||||
|
result[value] = self.xmpp.requested_jid.domain
|
||||||
|
elif value == 'realm':
|
||||||
|
result[value] = self.xmpp.requested_jid.domain
|
||||||
|
elif value == 'service-name':
|
||||||
|
result[value] = self.xmpp._service_name
|
||||||
|
elif value == 'service':
|
||||||
|
result[value] = 'xmpp'
|
||||||
|
elif value in creds:
|
||||||
|
result[value] = creds[value]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _default_security(self, values):
|
||||||
|
result = {}
|
||||||
|
for value in values:
|
||||||
|
if value == 'encrypted':
|
||||||
|
if 'starttls' in self.xmpp.features:
|
||||||
|
result[value] = True
|
||||||
|
elif isinstance(self.xmpp.socket, ssl.SSLSocket):
|
||||||
|
result[value] = True
|
||||||
|
else:
|
||||||
|
result[value] = False
|
||||||
|
else:
|
||||||
|
result[value] = self.config.get(value, False)
|
||||||
|
return result
|
||||||
|
|
||||||
def _handle_sasl_auth(self, features):
|
def _handle_sasl_auth(self, features):
|
||||||
"""
|
"""
|
||||||
Handle authenticating using SASL.
|
Handle authenticating using SASL.
|
||||||
@@ -111,37 +143,61 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
# server has incorrectly offered it again.
|
# server has incorrectly offered it again.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.use_mech:
|
enforce_limit = False
|
||||||
self.mech_list = set(features['mechanisms'])
|
limited_mechs = self.use_mechs
|
||||||
else:
|
|
||||||
self.mech_list = set([self.use_mech])
|
if limited_mechs is None:
|
||||||
|
limited_mechs = set()
|
||||||
|
elif limited_mechs and not isinstance(limited_mechs, set):
|
||||||
|
limited_mechs = set(limited_mechs)
|
||||||
|
enforce_limit = True
|
||||||
|
|
||||||
|
if self.use_mech:
|
||||||
|
limited_mechs.add(self.use_mech)
|
||||||
|
enforce_limit = True
|
||||||
|
|
||||||
|
if enforce_limit:
|
||||||
|
self.use_mechs = limited_mechs
|
||||||
|
|
||||||
|
self.mech_list = set(features['mechanisms'])
|
||||||
|
|
||||||
return self._send_auth()
|
return self._send_auth()
|
||||||
|
|
||||||
def _send_auth(self):
|
def _send_auth(self):
|
||||||
mech_list = self.mech_list - self.attempted_mechs
|
mech_list = self.mech_list - self.attempted_mechs
|
||||||
self.mech = self.sasl.choose_mechanism(mech_list)
|
try:
|
||||||
|
self.mech = sasl.choose(mech_list,
|
||||||
if mech_list and self.mech is not None:
|
self.sasl_callback,
|
||||||
resp = stanza.Auth(self.xmpp)
|
self.security_callback,
|
||||||
resp['mechanism'] = self.mech.name
|
limit=self.use_mechs,
|
||||||
try:
|
min_mech=self.min_mech)
|
||||||
resp['value'] = self.mech.process()
|
except sasl.SASLNoAppropriateMechanism:
|
||||||
except SASLCancelled:
|
|
||||||
self.attempted_mechs.add(self.mech.name)
|
|
||||||
self._send_auth()
|
|
||||||
except SASLError:
|
|
||||||
self.attempted_mechs.add(self.mech.name)
|
|
||||||
self._send_auth()
|
|
||||||
except SASLPrepFailure:
|
|
||||||
log.exception("A credential value did not pass SASLprep.")
|
|
||||||
self.xmpp.disconnect()
|
|
||||||
else:
|
|
||||||
resp.send(now=True)
|
|
||||||
else:
|
|
||||||
log.error("No appropriate login method.")
|
log.error("No appropriate login method.")
|
||||||
self.xmpp.event("no_auth", direct=True)
|
self.xmpp.event("no_auth", direct=True)
|
||||||
self.attempted_mechs = set()
|
self.attempted_mechs = set()
|
||||||
|
return self.xmpp.disconnect()
|
||||||
|
|
||||||
|
resp = stanza.Auth(self.xmpp)
|
||||||
|
resp['mechanism'] = self.mech.name
|
||||||
|
try:
|
||||||
|
resp['value'] = self.mech.process()
|
||||||
|
except sasl.SASLCancelled:
|
||||||
|
self.attempted_mechs.add(self.mech.name)
|
||||||
|
self._send_auth()
|
||||||
|
except sasl.SASLFailed:
|
||||||
|
self.attempted_mechs.add(self.mech.name)
|
||||||
|
self._send_auth()
|
||||||
|
except sasl.SASLMutualAuthFailed:
|
||||||
|
log.error("Mutual authentication failed! " + \
|
||||||
|
"A security breach is possible.")
|
||||||
|
self.attempted_mechs.add(self.mech.name)
|
||||||
self.xmpp.disconnect()
|
self.xmpp.disconnect()
|
||||||
|
except StringPrepError:
|
||||||
|
log.exception("A credential value did not pass SASLprep.")
|
||||||
|
self.xmpp.disconnect()
|
||||||
|
else:
|
||||||
|
resp.send(now=True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_challenge(self, stanza):
|
def _handle_challenge(self, stanza):
|
||||||
@@ -149,20 +205,33 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
resp = self.stanza.Response(self.xmpp)
|
resp = self.stanza.Response(self.xmpp)
|
||||||
try:
|
try:
|
||||||
resp['value'] = self.mech.process(stanza['value'])
|
resp['value'] = self.mech.process(stanza['value'])
|
||||||
except SASLCancelled:
|
except sasl.SASLCancelled:
|
||||||
self.stanza.Abort(self.xmpp).send()
|
self.stanza.Abort(self.xmpp).send()
|
||||||
except SASLError:
|
except sasl.SASLFailed:
|
||||||
self.stanza.Abort(self.xmpp).send()
|
self.stanza.Abort(self.xmpp).send()
|
||||||
|
except sasl.SASLMutualAuthFailed:
|
||||||
|
log.error("Mutual authentication failed! " + \
|
||||||
|
"A security breach is possible.")
|
||||||
|
self.attempted_mechs.add(self.mech.name)
|
||||||
|
self.xmpp.disconnect()
|
||||||
else:
|
else:
|
||||||
resp.send(now=True)
|
resp.send(now=True)
|
||||||
|
|
||||||
def _handle_success(self, stanza):
|
def _handle_success(self, stanza):
|
||||||
"""SASL authentication succeeded. Restart the stream."""
|
"""SASL authentication succeeded. Restart the stream."""
|
||||||
self.attempted_mechs = set()
|
try:
|
||||||
self.xmpp.authenticated = True
|
final = self.mech.process(stanza['value'])
|
||||||
self.xmpp.features.add('mechanisms')
|
except sasl.SASLMutualAuthFailed:
|
||||||
self.xmpp.event('auth_success', stanza, direct=True)
|
log.error("Mutual authentication failed! " + \
|
||||||
raise RestartStream()
|
"A security breach is possible.")
|
||||||
|
self.attempted_mechs.add(self.mech.name)
|
||||||
|
self.xmpp.disconnect()
|
||||||
|
else:
|
||||||
|
self.attempted_mechs = set()
|
||||||
|
self.xmpp.authenticated = True
|
||||||
|
self.xmpp.features.add('mechanisms')
|
||||||
|
self.xmpp.event('auth_success', stanza, direct=True)
|
||||||
|
raise RestartStream()
|
||||||
|
|
||||||
def _handle_fail(self, stanza):
|
def _handle_fail(self, stanza):
|
||||||
"""SASL authentication failed. Disconnect and shutdown."""
|
"""SASL authentication failed. Disconnect and shutdown."""
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
from sleekxmpp.util import bytes
|
||||||
|
|
||||||
from sleekxmpp.xmlstream import StanzaBase
|
from sleekxmpp.xmlstream import StanzaBase
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
from sleekxmpp.util import bytes
|
||||||
|
|
||||||
from sleekxmpp.xmlstream import StanzaBase
|
from sleekxmpp.xmlstream import StanzaBase
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
from sleekxmpp.util import bytes
|
||||||
|
|
||||||
from sleekxmpp.xmlstream import StanzaBase
|
from sleekxmpp.xmlstream import StanzaBase
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sleekxmpp.xmlstream import StanzaBase
|
import base64
|
||||||
|
|
||||||
|
from sleekxmpp.util import bytes
|
||||||
|
from sleekxmpp.xmlstream import StanzaBase
|
||||||
|
|
||||||
class Success(StanzaBase):
|
class Success(StanzaBase):
|
||||||
|
|
||||||
@@ -16,9 +18,21 @@ class Success(StanzaBase):
|
|||||||
|
|
||||||
name = 'success'
|
name = 'success'
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||||
interfaces = set()
|
interfaces = set(['value'])
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
|
||||||
def setup(self, xml):
|
def setup(self, xml):
|
||||||
StanzaBase.setup(self, xml)
|
StanzaBase.setup(self, xml)
|
||||||
self.xml.tag = self.tag_name()
|
self.xml.tag = self.tag_name()
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return base64.b64decode(bytes(self.xml.text))
|
||||||
|
|
||||||
|
def set_value(self, values):
|
||||||
|
if values:
|
||||||
|
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
|
||||||
|
else:
|
||||||
|
self.xml.text = '='
|
||||||
|
|
||||||
|
def del_value(self):
|
||||||
|
self.xml.text = ''
|
||||||
|
|||||||
15
sleekxmpp/features/feature_preapproval/__init__.py
Normal file
15
sleekxmpp/features/feature_preapproval/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.features.feature_preapproval.preapproval import FeaturePreApproval
|
||||||
|
from sleekxmpp.features.feature_preapproval.stanza import PreApproval
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(FeaturePreApproval)
|
||||||
42
sleekxmpp/features/feature_preapproval/preapproval.py
Normal file
42
sleekxmpp/features/feature_preapproval/preapproval.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Iq, StreamFeatures
|
||||||
|
from sleekxmpp.features.feature_preapproval import stanza
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins.base import BasePlugin
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FeaturePreApproval(BasePlugin):
|
||||||
|
|
||||||
|
name = 'feature_preapproval'
|
||||||
|
description = 'RFC 6121: Stream Feature: Subscription Pre-Approval'
|
||||||
|
dependences = set()
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
self.xmpp.register_feature('preapproval',
|
||||||
|
self._handle_preapproval,
|
||||||
|
restart=False,
|
||||||
|
order=9001)
|
||||||
|
|
||||||
|
register_stanza_plugin(StreamFeatures, stanza.PreApproval)
|
||||||
|
|
||||||
|
def _handle_preapproval(self, features):
|
||||||
|
"""Save notice that the server support subscription pre-approvals.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
features -- The stream features stanza.
|
||||||
|
"""
|
||||||
|
log.debug("Server supports subscription pre-approvals.")
|
||||||
|
self.xmpp.features.add('preapproval')
|
||||||
17
sleekxmpp/features/feature_preapproval/stanza.py
Normal file
17
sleekxmpp/features/feature_preapproval/stanza.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class PreApproval(ElementBase):
|
||||||
|
|
||||||
|
name = 'sub'
|
||||||
|
namespace = 'urn:xmpp:features:pre-approval'
|
||||||
|
interfaces = set()
|
||||||
|
plugin_attrib = 'preapproval'
|
||||||
@@ -54,13 +54,9 @@ class FeatureSTARTTLS(BasePlugin):
|
|||||||
return False
|
return False
|
||||||
elif not self.xmpp.use_tls:
|
elif not self.xmpp.use_tls:
|
||||||
return False
|
return False
|
||||||
elif self.xmpp.ssl_support:
|
else:
|
||||||
self.xmpp.send(features['starttls'], now=True)
|
self.xmpp.send(features['starttls'], now=True)
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
log.warning("The module tlslite is required to log in" + \
|
|
||||||
" to some servers, and has not been found.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _handle_starttls_proceed(self, proceed):
|
def _handle_starttls_proceed(self, proceed):
|
||||||
"""Restart the XML stream when TLS is accepted."""
|
"""Restart the XML stream when TLS is accepted."""
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import stringprep
|
import stringprep
|
||||||
|
import threading
|
||||||
import encodings.idna
|
import encodings.idna
|
||||||
|
|
||||||
from sleekxmpp.util import stringprep_profiles
|
from sleekxmpp.util import stringprep_profiles
|
||||||
|
from sleekxmpp.thirdparty import OrderedDict
|
||||||
|
|
||||||
#: These characters are not allowed to appear in a JID.
|
#: These characters are not allowed to appear in a JID.
|
||||||
ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \
|
ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \
|
||||||
@@ -63,6 +65,24 @@ JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
|
|||||||
'\\40': '@',
|
'\\40': '@',
|
||||||
'\\5c': '\\'}
|
'\\5c': '\\'}
|
||||||
|
|
||||||
|
JID_CACHE = OrderedDict()
|
||||||
|
JID_CACHE_LOCK = threading.Lock()
|
||||||
|
JID_CACHE_MAX_SIZE = 1024
|
||||||
|
|
||||||
|
def _cache(key, parts, locked):
|
||||||
|
JID_CACHE[key] = (parts, locked)
|
||||||
|
if len(JID_CACHE) > JID_CACHE_MAX_SIZE:
|
||||||
|
with JID_CACHE_LOCK:
|
||||||
|
while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
|
||||||
|
found = None
|
||||||
|
for key, item in JID_CACHE.iteritems():
|
||||||
|
if not item[1]: # if not locked
|
||||||
|
found = key
|
||||||
|
break
|
||||||
|
if not found: # more than MAX_SIZE locked
|
||||||
|
# warn?
|
||||||
|
break
|
||||||
|
del JID_CACHE[found]
|
||||||
|
|
||||||
# pylint: disable=c0103
|
# pylint: disable=c0103
|
||||||
#: The nodeprep profile of stringprep used to validate the local,
|
#: The nodeprep profile of stringprep used to validate the local,
|
||||||
@@ -72,7 +92,7 @@ nodeprep = stringprep_profiles.create(
|
|||||||
bidi=True,
|
bidi=True,
|
||||||
mappings=[
|
mappings=[
|
||||||
stringprep_profiles.b1_mapping,
|
stringprep_profiles.b1_mapping,
|
||||||
stringprep_profiles.c12_mapping],
|
stringprep.map_table_b2],
|
||||||
prohibited=[
|
prohibited=[
|
||||||
stringprep.in_table_c11,
|
stringprep.in_table_c11,
|
||||||
stringprep.in_table_c12,
|
stringprep.in_table_c12,
|
||||||
@@ -412,29 +432,48 @@ class JID(object):
|
|||||||
|
|
||||||
# pylint: disable=W0212
|
# pylint: disable=W0212
|
||||||
def __init__(self, jid=None, **kwargs):
|
def __init__(self, jid=None, **kwargs):
|
||||||
self._jid = (None, None, None)
|
locked = kwargs.get('cache_lock', False)
|
||||||
|
in_local = kwargs.get('local', None)
|
||||||
|
in_domain = kwargs.get('domain', None)
|
||||||
|
in_resource = kwargs.get('resource', None)
|
||||||
|
parts = None
|
||||||
|
if in_local or in_domain or in_resource:
|
||||||
|
parts = (in_local, in_domain, in_resource)
|
||||||
|
|
||||||
if jid is None or jid == '':
|
# only check cache if there is a jid string, or parts, not if there
|
||||||
jid = (None, None, None)
|
# are both
|
||||||
elif not isinstance(jid, JID):
|
self._jid = None
|
||||||
jid = _parse_jid(jid)
|
key = None
|
||||||
else:
|
if (jid is not None) and (parts is None):
|
||||||
jid = jid._jid
|
if isinstance(jid, JID):
|
||||||
|
# it's already good to go, and there are no additions
|
||||||
|
self._jid = jid._jid
|
||||||
|
return
|
||||||
|
key = jid
|
||||||
|
self._jid, locked = JID_CACHE.get(jid, (None, locked))
|
||||||
|
elif jid is None and parts is not None:
|
||||||
|
key = parts
|
||||||
|
self._jid, locked = JID_CACHE.get(parts, (None, locked))
|
||||||
|
if not self._jid:
|
||||||
|
if not jid:
|
||||||
|
parsed_jid = (None, None, None)
|
||||||
|
elif not isinstance(jid, JID):
|
||||||
|
parsed_jid = _parse_jid(jid)
|
||||||
|
else:
|
||||||
|
parsed_jid = jid._jid
|
||||||
|
|
||||||
local, domain, resource = jid
|
local, domain, resource = parsed_jid
|
||||||
|
|
||||||
local = kwargs.get('local', local)
|
if 'local' in kwargs:
|
||||||
domain = kwargs.get('domain', domain)
|
local = _escape_node(in_local)
|
||||||
resource = kwargs.get('resource', resource)
|
if 'domain' in kwargs:
|
||||||
|
domain = _validate_domain(in_domain)
|
||||||
|
if 'resource' in kwargs:
|
||||||
|
resource = _validate_resource(in_resource)
|
||||||
|
|
||||||
if 'local' in kwargs:
|
self._jid = (local, domain, resource)
|
||||||
local = _escape_node(local)
|
if key:
|
||||||
if 'domain' in kwargs:
|
_cache(key, self._jid, locked)
|
||||||
domain = _validate_domain(domain)
|
|
||||||
if 'resource' in kwargs:
|
|
||||||
resource = _validate_resource(resource)
|
|
||||||
|
|
||||||
self._jid = (local, domain, resource)
|
|
||||||
|
|
||||||
def unescape(self):
|
def unescape(self):
|
||||||
"""Return an unescaped JID object.
|
"""Return an unescaped JID object.
|
||||||
@@ -498,7 +537,9 @@ class JID(object):
|
|||||||
``resource``, ``full``, ``jid``, or ``bare``.
|
``resource``, ``full``, ``jid``, or ``bare``.
|
||||||
:param value: The new string value of the JID component.
|
:param value: The new string value of the JID component.
|
||||||
"""
|
"""
|
||||||
if name == 'resource':
|
if name == '_jid':
|
||||||
|
super(JID, self).__setattr__('_jid', value)
|
||||||
|
elif name == 'resource':
|
||||||
self._jid = JID(self, resource=value)._jid
|
self._jid = JID(self, resource=value)._jid
|
||||||
elif name in ('user', 'username', 'local', 'node'):
|
elif name in ('user', 'username', 'local', 'node'):
|
||||||
self._jid = JID(self, local=value)._jid
|
self._jid = JID(self, local=value)._jid
|
||||||
@@ -509,8 +550,6 @@ class JID(object):
|
|||||||
elif name == 'bare':
|
elif name == 'bare':
|
||||||
parsed = JID(value)._jid
|
parsed = JID(value)._jid
|
||||||
self._jid = (parsed[0], parsed[1], self._jid[2])
|
self._jid = (parsed[0], parsed[1], self._jid[2])
|
||||||
elif name == '_jid':
|
|
||||||
super(JID, self).__setattr__('_jid', value)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Use the full JID as the string value."""
|
"""Use the full JID as the string value."""
|
||||||
|
|||||||
@@ -18,15 +18,19 @@ __all__ = [
|
|||||||
'xep_0004', # Data Forms
|
'xep_0004', # Data Forms
|
||||||
'xep_0009', # Jabber-RPC
|
'xep_0009', # Jabber-RPC
|
||||||
'xep_0012', # Last Activity
|
'xep_0012', # Last Activity
|
||||||
|
'xep_0013', # Flexible Offline Message Retrieval
|
||||||
|
'xep_0016', # Privacy Lists
|
||||||
'xep_0027', # Current Jabber OpenPGP Usage
|
'xep_0027', # Current Jabber OpenPGP Usage
|
||||||
'xep_0030', # Service Discovery
|
'xep_0030', # Service Discovery
|
||||||
'xep_0033', # Extended Stanza Addresses
|
'xep_0033', # Extended Stanza Addresses
|
||||||
'xep_0045', # Multi-User Chat (Client)
|
'xep_0045', # Multi-User Chat (Client)
|
||||||
'xep_0047', # In-Band Bytestreams
|
'xep_0047', # In-Band Bytestreams
|
||||||
|
'xep_0049', # Private XML Storage
|
||||||
'xep_0050', # Ad-hoc Commands
|
'xep_0050', # Ad-hoc Commands
|
||||||
'xep_0054', # vcard-temp
|
'xep_0054', # vcard-temp
|
||||||
'xep_0059', # Result Set Management
|
'xep_0059', # Result Set Management
|
||||||
'xep_0060', # Pubsub (Client)
|
'xep_0060', # Pubsub (Client)
|
||||||
|
'xep_0065', # SOCKS5 Bytestreams
|
||||||
'xep_0066', # Out of Band Data
|
'xep_0066', # Out of Band Data
|
||||||
'xep_0077', # In-Band Registration
|
'xep_0077', # In-Band Registration
|
||||||
# 'xep_0078', # Non-SASL auth. Don't automatically load
|
# 'xep_0078', # Non-SASL auth. Don't automatically load
|
||||||
@@ -35,6 +39,7 @@ __all__ = [
|
|||||||
'xep_0084', # User Avatar
|
'xep_0084', # User Avatar
|
||||||
'xep_0085', # Chat State Notifications
|
'xep_0085', # Chat State Notifications
|
||||||
'xep_0086', # Legacy Error Codes
|
'xep_0086', # Legacy Error Codes
|
||||||
|
'xep_0091', # Legacy Delayed Delivery
|
||||||
'xep_0092', # Software Version
|
'xep_0092', # Software Version
|
||||||
'xep_0106', # JID Escaping
|
'xep_0106', # JID Escaping
|
||||||
'xep_0107', # User Mood
|
'xep_0107', # User Mood
|
||||||
@@ -59,9 +64,17 @@ __all__ = [
|
|||||||
'xep_0223', # Persistent Storage of Private Data via Pubsub
|
'xep_0223', # Persistent Storage of Private Data via Pubsub
|
||||||
'xep_0224', # Attention
|
'xep_0224', # Attention
|
||||||
'xep_0231', # Bits of Binary
|
'xep_0231', # Bits of Binary
|
||||||
|
'xep_0235', # OAuth Over XMPP
|
||||||
|
'xep_0242', # XMPP Client Compliance 2009
|
||||||
'xep_0249', # Direct MUC Invitations
|
'xep_0249', # Direct MUC Invitations
|
||||||
'xep_0256', # Last Activity in Presence
|
'xep_0256', # Last Activity in Presence
|
||||||
|
'xep_0257', # Client Certificate Management for SASL EXTERNAL
|
||||||
'xep_0258', # Security Labels in XMPP
|
'xep_0258', # Security Labels in XMPP
|
||||||
'xep_0270', # XMPP Compliance Suites 2010
|
'xep_0270', # XMPP Compliance Suites 2010
|
||||||
|
'xep_0279', # Server IP Check
|
||||||
|
'xep_0280', # Message Carbons
|
||||||
|
'xep_0297', # Stanza Forwarding
|
||||||
'xep_0302', # XMPP Compliance Suites 2012
|
'xep_0302', # XMPP Compliance Suites 2012
|
||||||
|
'xep_0308', # Last Message Correction
|
||||||
|
'xep_0313', # Message Archive Management
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -201,7 +201,8 @@ class Form(ElementBase):
|
|||||||
del self['instructions']
|
del self['instructions']
|
||||||
if instructions in [None, '']:
|
if instructions in [None, '']:
|
||||||
return
|
return
|
||||||
instructions = instructions.split('\n')
|
if not isinstance(instructions, list):
|
||||||
|
instructions = instructions.split('\n')
|
||||||
for instruction in instructions:
|
for instruction in instructions:
|
||||||
inst = ET.Element('{%s}instructions' % self.namespace)
|
inst = ET.Element('{%s}instructions' % self.namespace)
|
||||||
inst.text = instruction
|
inst.text = instruction
|
||||||
|
|||||||
15
sleekxmpp/plugins/xep_0013/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0013/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0013.stanza import Offline
|
||||||
|
from sleekxmpp.plugins.xep_0013.offline import XEP_0013
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0013)
|
||||||
134
sleekxmpp/plugins/xep_0013/offline.py
Normal file
134
sleekxmpp/plugins/xep_0013/offline.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.stanza import Message, Iq
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
from sleekxmpp.xmlstream.handler import Collector
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.xep_0013 import stanza
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0013(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
XEP-0013 Flexible Offline Message Retrieval
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0013'
|
||||||
|
description = 'XEP-0013: Flexible Offline Message Retrieval'
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, stanza.Offline)
|
||||||
|
register_stanza_plugin(Message, stanza.Offline)
|
||||||
|
|
||||||
|
def get_count(self, **kwargs):
|
||||||
|
return self.xmpp['xep_0030'].get_info(
|
||||||
|
node='http://jabber.org/protocol/offline',
|
||||||
|
local=False,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def get_headers(self, **kwargs):
|
||||||
|
return self.xmpp['xep_0030'].get_items(
|
||||||
|
node='http://jabber.org/protocol/offline',
|
||||||
|
local=False,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
if not isinstance(nodes, (list, set)):
|
||||||
|
nodes = [nodes]
|
||||||
|
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
offline = iq['offline']
|
||||||
|
for node in nodes:
|
||||||
|
item = stanza.Item()
|
||||||
|
item['node'] = node
|
||||||
|
item['action'] = 'view'
|
||||||
|
offline.append(item)
|
||||||
|
|
||||||
|
collector = Collector(
|
||||||
|
'Offline_Results_%s' % iq['id'],
|
||||||
|
StanzaPath('message/offline'))
|
||||||
|
self.xmpp.register_handler(collector)
|
||||||
|
|
||||||
|
if not block and callback is not None:
|
||||||
|
def wrapped_cb(iq):
|
||||||
|
results = collector.stop()
|
||||||
|
if iq['type'] == 'result':
|
||||||
|
iq['offline']['results'] = results
|
||||||
|
callback(iq)
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
resp = iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
resp['offline']['results'] = collector.stop()
|
||||||
|
return resp
|
||||||
|
except XMPPError as e:
|
||||||
|
collector.stop()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
if not isinstance(nodes, (list, set)):
|
||||||
|
nodes = [nodes]
|
||||||
|
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
offline = iq['offline']
|
||||||
|
for node in nodes:
|
||||||
|
item = stanza.Item()
|
||||||
|
item['node'] = node
|
||||||
|
item['action'] = 'remove'
|
||||||
|
offline.append(item)
|
||||||
|
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def fetch(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['offline']['fetch'] = True
|
||||||
|
|
||||||
|
collector = Collector(
|
||||||
|
'Offline_Results_%s' % iq['id'],
|
||||||
|
StanzaPath('message/offline'))
|
||||||
|
self.xmpp.register_handler(collector)
|
||||||
|
|
||||||
|
if not block and callback is not None:
|
||||||
|
def wrapped_cb(iq):
|
||||||
|
results = collector.stop()
|
||||||
|
if iq['type'] == 'result':
|
||||||
|
iq['offline']['results'] = results
|
||||||
|
callback(iq)
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
resp = iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
resp['offline']['results'] = collector.stop()
|
||||||
|
return resp
|
||||||
|
except XMPPError as e:
|
||||||
|
collector.stop()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def purge(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['offline']['purge'] = True
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
53
sleekxmpp/plugins/xep_0013/stanza.py
Normal file
53
sleekxmpp/plugins/xep_0013/stanza.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.jid import JID
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class Offline(ElementBase):
|
||||||
|
name = 'offline'
|
||||||
|
namespace = 'http://jabber.org/protocol/offline'
|
||||||
|
plugin_attrib = 'offline'
|
||||||
|
interfaces = set(['fetch', 'purge', 'results'])
|
||||||
|
bool_interfaces = interfaces
|
||||||
|
|
||||||
|
def setup(self, xml=None):
|
||||||
|
ElementBase.setup(self, xml)
|
||||||
|
self._results = []
|
||||||
|
|
||||||
|
# The results interface is meant only as an easy
|
||||||
|
# way to access the set of collected message responses
|
||||||
|
# from the query.
|
||||||
|
|
||||||
|
def get_results(self):
|
||||||
|
return self._results
|
||||||
|
|
||||||
|
def set_results(self, values):
|
||||||
|
self._results = values
|
||||||
|
|
||||||
|
def del_results(self):
|
||||||
|
self._results = []
|
||||||
|
|
||||||
|
|
||||||
|
class Item(ElementBase):
|
||||||
|
name = 'item'
|
||||||
|
namespace = 'http://jabber.org/protocol/offline'
|
||||||
|
plugin_attrib = 'item'
|
||||||
|
interfaces = set(['action', 'node', 'jid'])
|
||||||
|
|
||||||
|
actions = set(['view', 'remove'])
|
||||||
|
|
||||||
|
def get_jid(self):
|
||||||
|
return JID(self._get_attr('jid'))
|
||||||
|
|
||||||
|
def set_jid(self, value):
|
||||||
|
self._set_attr('jid', str(value))
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(Offline, Item, iterable=True)
|
||||||
16
sleekxmpp/plugins/xep_0016/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0016/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0016 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0016.stanza import Privacy
|
||||||
|
from sleekxmpp.plugins.xep_0016.privacy import XEP_0016
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0016)
|
||||||
110
sleekxmpp/plugins/xep_0016/privacy.py
Normal file
110
sleekxmpp/plugins/xep_0016/privacy.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp import Iq
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.xep_0016 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0016.stanza import Privacy, Item
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0016(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0016'
|
||||||
|
description = 'XEP-0016: Privacy Lists'
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, Privacy)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature=Privacy.namespace)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature(Privacy.namespace)
|
||||||
|
|
||||||
|
def get_privacy_lists(self, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq.enable('privacy')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def get_list(self, name, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['privacy']['list']['name'] = name
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def get_active(self, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['privacy'].enable('active')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def get_default(self, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['privacy'].enable('default')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def activate(self, name, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['privacy']['active']['name'] = name
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def deactivate(self, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['privacy'].enable('active')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def make_default(self, name, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['privacy']['default']['name'] = name
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def remove_default(self, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['privacy'].enable('default')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def edit_list(self, name, rules, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['privacy']['list']['name'] = name
|
||||||
|
priv_list = iq['privacy']['list']
|
||||||
|
|
||||||
|
if not rules:
|
||||||
|
rules = []
|
||||||
|
|
||||||
|
for rule in rules:
|
||||||
|
if isinstance(rule, Item):
|
||||||
|
priv_list.append(rule)
|
||||||
|
continue
|
||||||
|
|
||||||
|
priv_list.add_item(
|
||||||
|
rule['value'],
|
||||||
|
rule['action'],
|
||||||
|
rule['order'],
|
||||||
|
itype=rule.get('type', None),
|
||||||
|
iq=rule.get('iq', None),
|
||||||
|
message=rule.get('message', None),
|
||||||
|
presence_in=rule.get('presence_in',
|
||||||
|
rule.get('presence-in', None)),
|
||||||
|
presence_out=rule.get('presence_out',
|
||||||
|
rule.get('presence-out', None)))
|
||||||
|
|
||||||
|
def remove_list(self, name, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['privacy']['list']['name'] = name
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
103
sleekxmpp/plugins/xep_0016/stanza.py
Normal file
103
sleekxmpp/plugins/xep_0016/stanza.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class Privacy(ElementBase):
|
||||||
|
name = 'query'
|
||||||
|
namespace = 'jabber:iq:privacy'
|
||||||
|
plugin_attrib = 'privacy'
|
||||||
|
interfaces = set()
|
||||||
|
|
||||||
|
def add_list(self, name):
|
||||||
|
priv_list = List()
|
||||||
|
priv_list['name'] = name
|
||||||
|
self.append(priv_list)
|
||||||
|
return priv_list
|
||||||
|
|
||||||
|
|
||||||
|
class Active(ElementBase):
|
||||||
|
name = 'active'
|
||||||
|
namespace = 'jabber:iq:privacy'
|
||||||
|
plugin_attrib = name
|
||||||
|
interfaces = set(['name'])
|
||||||
|
|
||||||
|
|
||||||
|
class Default(ElementBase):
|
||||||
|
name = 'default'
|
||||||
|
namespace = 'jabber:iq:privacy'
|
||||||
|
plugin_attrib = name
|
||||||
|
interfaces = set(['name'])
|
||||||
|
|
||||||
|
|
||||||
|
class List(ElementBase):
|
||||||
|
name = 'list'
|
||||||
|
namespace = 'jabber:iq:privacy'
|
||||||
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'lists'
|
||||||
|
interfaces = set(['name'])
|
||||||
|
|
||||||
|
def add_item(self, value, action, order, itype=None, iq=False,
|
||||||
|
message=False, presence_in=False, presence_out=False):
|
||||||
|
item = Item()
|
||||||
|
item.values = {'type': itype,
|
||||||
|
'value': value,
|
||||||
|
'action': action,
|
||||||
|
'order': order,
|
||||||
|
'message': message,
|
||||||
|
'iq': iq,
|
||||||
|
'presence_in': presence_in,
|
||||||
|
'presence_out': presence_out}
|
||||||
|
self.append(item)
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
class Item(ElementBase):
|
||||||
|
name = 'item'
|
||||||
|
namespace = 'jabber:iq:privacy'
|
||||||
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'items'
|
||||||
|
interfaces = set(['type', 'value', 'action', 'order', 'iq',
|
||||||
|
'message', 'presence_in', 'presence_out'])
|
||||||
|
bool_interfaces = set(['message', 'iq', 'presence_in', 'presence_out'])
|
||||||
|
|
||||||
|
type_values = ('', 'jid', 'group', 'subscription')
|
||||||
|
action_values = ('allow', 'deny')
|
||||||
|
|
||||||
|
def set_type(self, value):
|
||||||
|
if value and value not in self.type_values:
|
||||||
|
raise ValueError('Unknown type value: %s' % value)
|
||||||
|
else:
|
||||||
|
self._set_attr('type', value)
|
||||||
|
|
||||||
|
def set_action(self, value):
|
||||||
|
if value not in self.action_values:
|
||||||
|
raise ValueError('Unknown action value: %s' % value)
|
||||||
|
else:
|
||||||
|
self._set_attr('action', value)
|
||||||
|
|
||||||
|
def set_presence_in(self, value):
|
||||||
|
keep = True if value else False
|
||||||
|
self._set_sub_text('presence-in', '', keep=keep)
|
||||||
|
|
||||||
|
def get_presence_in(self):
|
||||||
|
pres = self.xml.find('{%s}presence-in' % self.namespace)
|
||||||
|
return pres is not None
|
||||||
|
|
||||||
|
def del_presence_in(self):
|
||||||
|
self._del_sub('{%s}presence-in' % self.namespace)
|
||||||
|
|
||||||
|
def set_presence_out(self, value):
|
||||||
|
keep = True if value else False
|
||||||
|
self._set_sub_text('presence-in', '', keep=keep)
|
||||||
|
|
||||||
|
def get_presence_out(self):
|
||||||
|
pres = self.xml.find('{%s}presence-in' % self.namespace)
|
||||||
|
return pres is not None
|
||||||
|
|
||||||
|
def del_presence_out(self):
|
||||||
|
self._del_sub('{%s}presence-in' % self.namespace)
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(Privacy, Active)
|
||||||
|
register_stanza_plugin(Privacy, Default)
|
||||||
|
register_stanza_plugin(Privacy, List, iterable=True)
|
||||||
|
register_stanza_plugin(List, Item, iterable=True)
|
||||||
@@ -288,7 +288,7 @@ class XEP_0030(BasePlugin):
|
|||||||
'cached': cached}
|
'cached': cached}
|
||||||
return self.api['has_identity'](jid, node, ifrom, data)
|
return self.api['has_identity'](jid, node, ifrom, data)
|
||||||
|
|
||||||
def get_info(self, jid=None, node=None, local=False,
|
def get_info(self, jid=None, node=None, local=None,
|
||||||
cached=None, **kwargs):
|
cached=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Retrieve the disco#info results from a given JID/node combination.
|
Retrieve the disco#info results from a given JID/node combination.
|
||||||
@@ -324,18 +324,21 @@ class XEP_0030(BasePlugin):
|
|||||||
callback -- Optional callback to execute when a reply is
|
callback -- Optional callback to execute when a reply is
|
||||||
received instead of blocking and waiting for
|
received instead of blocking and waiting for
|
||||||
the reply.
|
the reply.
|
||||||
|
timeout_callback -- Optional callback to execute when no result
|
||||||
|
has been received in timeout seconds.
|
||||||
"""
|
"""
|
||||||
if jid is not None and not isinstance(jid, JID):
|
if local is None:
|
||||||
jid = JID(jid)
|
if jid is not None and not isinstance(jid, JID):
|
||||||
if self.xmpp.is_component:
|
jid = JID(jid)
|
||||||
if jid.domain == self.xmpp.boundjid.domain:
|
if self.xmpp.is_component:
|
||||||
local = True
|
if jid.domain == self.xmpp.boundjid.domain:
|
||||||
else:
|
local = True
|
||||||
if str(jid) == str(self.xmpp.boundjid):
|
else:
|
||||||
local = True
|
if str(jid) == str(self.xmpp.boundjid):
|
||||||
jid = jid.full
|
local = True
|
||||||
elif jid in (None, ''):
|
jid = jid.full
|
||||||
local = True
|
elif jid in (None, ''):
|
||||||
|
local = True
|
||||||
|
|
||||||
if local:
|
if local:
|
||||||
log.debug("Looking up local disco#info data " + \
|
log.debug("Looking up local disco#info data " + \
|
||||||
@@ -363,7 +366,8 @@ class XEP_0030(BasePlugin):
|
|||||||
iq['disco_info']['node'] = node if node else ''
|
iq['disco_info']['node'] = node if node else ''
|
||||||
return iq.send(timeout=kwargs.get('timeout', None),
|
return iq.send(timeout=kwargs.get('timeout', None),
|
||||||
block=kwargs.get('block', True),
|
block=kwargs.get('block', True),
|
||||||
callback=kwargs.get('callback', None))
|
callback=kwargs.get('callback', None),
|
||||||
|
timeout_callback=kwargs.get('timeout_callback', None))
|
||||||
|
|
||||||
def set_info(self, jid=None, node=None, info=None):
|
def set_info(self, jid=None, node=None, info=None):
|
||||||
"""
|
"""
|
||||||
@@ -404,8 +408,10 @@ class XEP_0030(BasePlugin):
|
|||||||
iterator -- If True, return a result set iterator using
|
iterator -- If True, return a result set iterator using
|
||||||
the XEP-0059 plugin, if the plugin is loaded.
|
the XEP-0059 plugin, if the plugin is loaded.
|
||||||
Otherwise the parameter is ignored.
|
Otherwise the parameter is ignored.
|
||||||
|
timeout_callback -- Optional callback to execute when no result
|
||||||
|
has been received in timeout seconds.
|
||||||
"""
|
"""
|
||||||
if local or jid is None:
|
if local or local is None and jid is None:
|
||||||
items = self.api['get_items'](jid, node,
|
items = self.api['get_items'](jid, node,
|
||||||
kwargs.get('ifrom', None),
|
kwargs.get('ifrom', None),
|
||||||
kwargs)
|
kwargs)
|
||||||
@@ -422,7 +428,8 @@ class XEP_0030(BasePlugin):
|
|||||||
else:
|
else:
|
||||||
return iq.send(timeout=kwargs.get('timeout', None),
|
return iq.send(timeout=kwargs.get('timeout', None),
|
||||||
block=kwargs.get('block', True),
|
block=kwargs.get('block', True),
|
||||||
callback=kwargs.get('callback', None))
|
callback=kwargs.get('callback', None),
|
||||||
|
timeout_callback=kwargs.get('timeout_callback', None))
|
||||||
|
|
||||||
def set_items(self, jid=None, node=None, **kwargs):
|
def set_items(self, jid=None, node=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ class XEP_0045(BasePlugin):
|
|||||||
entry = pr['muc'].getStanzaValues()
|
entry = pr['muc'].getStanzaValues()
|
||||||
entry['show'] = pr['show']
|
entry['show'] = pr['show']
|
||||||
entry['status'] = pr['status']
|
entry['status'] = pr['status']
|
||||||
|
entry['alt_nick'] = pr['nick']
|
||||||
if pr['type'] == 'unavailable':
|
if pr['type'] == 'unavailable':
|
||||||
if entry['nick'] in self.rooms[entry['room']]:
|
if entry['nick'] in self.rooms[entry['room']]:
|
||||||
del self.rooms[entry['room']][entry['nick']]
|
del self.rooms[entry['room']][entry['nick']]
|
||||||
@@ -244,11 +245,11 @@ class XEP_0045(BasePlugin):
|
|||||||
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom)
|
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom)
|
||||||
x = ET.Element('{http://jabber.org/protocol/muc}x')
|
x = ET.Element('{http://jabber.org/protocol/muc}x')
|
||||||
if password:
|
if password:
|
||||||
passelement = ET.Element('password')
|
passelement = ET.Element('{http://jabber.org/protocol/muc}password')
|
||||||
passelement.text = password
|
passelement.text = password
|
||||||
x.append(passelement)
|
x.append(passelement)
|
||||||
if maxhistory:
|
if maxhistory:
|
||||||
history = ET.Element('history')
|
history = ET.Element('{http://jabber.org/protocol/muc}history')
|
||||||
if maxhistory == "0":
|
if maxhistory == "0":
|
||||||
history.attrib['maxchars'] = maxhistory
|
history.attrib['maxchars'] = maxhistory
|
||||||
else:
|
else:
|
||||||
@@ -270,10 +271,10 @@ class XEP_0045(BasePlugin):
|
|||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq['to'] = room
|
iq['to'] = room
|
||||||
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
||||||
destroy = ET.Element('destroy')
|
destroy = ET.Element('{http://jabber.org/protocol/muc#owner}destroy')
|
||||||
if altroom:
|
if altroom:
|
||||||
destroy.attrib['jid'] = altroom
|
destroy.attrib['jid'] = altroom
|
||||||
xreason = ET.Element('reason')
|
xreason = ET.Element('{http://jabber.org/protocol/muc#owner}reason')
|
||||||
xreason.text = reason
|
xreason.text = reason
|
||||||
destroy.append(xreason)
|
destroy.append(xreason)
|
||||||
query.append(destroy)
|
query.append(destroy)
|
||||||
@@ -293,9 +294,9 @@ class XEP_0045(BasePlugin):
|
|||||||
raise TypeError
|
raise TypeError
|
||||||
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
||||||
if nick is not None:
|
if nick is not None:
|
||||||
item = ET.Element('item', {'affiliation':affiliation, 'nick':nick})
|
item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'nick':nick})
|
||||||
else:
|
else:
|
||||||
item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})
|
item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'jid':jid})
|
||||||
query.append(item)
|
query.append(item)
|
||||||
iq = self.xmpp.makeIqSet(query)
|
iq = self.xmpp.makeIqSet(query)
|
||||||
iq['to'] = room
|
iq['to'] = room
|
||||||
@@ -316,7 +317,7 @@ class XEP_0045(BasePlugin):
|
|||||||
x = ET.Element('{http://jabber.org/protocol/muc#user}x')
|
x = ET.Element('{http://jabber.org/protocol/muc#user}x')
|
||||||
invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
|
invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
|
||||||
if reason:
|
if reason:
|
||||||
rxml = ET.Element('reason')
|
rxml = ET.Element('{http://jabber.org/protocol/muc#user}reason')
|
||||||
rxml.text = reason
|
rxml.text = reason
|
||||||
invite.append(rxml)
|
invite.append(rxml)
|
||||||
x.append(invite)
|
x.append(invite)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import re
|
import re
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
from sleekxmpp.util import bytes
|
||||||
from sleekxmpp.exceptions import XMPPError
|
from sleekxmpp.exceptions import XMPPError
|
||||||
from sleekxmpp.xmlstream import ElementBase
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
|
||||||
|
|
||||||
|
|
||||||
VALID_B64 = re.compile(r'[A-Za-z0-9\+\/]*=*')
|
VALID_B64 = re.compile(r'[A-Za-z0-9\+\/]*=*')
|
||||||
@@ -14,7 +14,7 @@ def to_b64(data):
|
|||||||
|
|
||||||
|
|
||||||
def from_b64(data):
|
def from_b64(data):
|
||||||
return bytes(base64.b64decode(bytes(data))).decode('utf-8')
|
return bytes(base64.b64decode(bytes(data)))
|
||||||
|
|
||||||
|
|
||||||
class Open(ElementBase):
|
class Open(ElementBase):
|
||||||
|
|||||||
15
sleekxmpp/plugins/xep_0049/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0049/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0049.stanza import PrivateXML
|
||||||
|
from sleekxmpp.plugins.xep_0049.private_storage import XEP_0049
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0049)
|
||||||
53
sleekxmpp/plugins/xep_0049/private_storage.py
Normal file
53
sleekxmpp/plugins/xep_0049/private_storage.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp import Iq
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins.xep_0049 import stanza, PrivateXML
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0049(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0049'
|
||||||
|
description = 'XEP-0049: Private XML Storage'
|
||||||
|
dependencies = set([])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, PrivateXML)
|
||||||
|
|
||||||
|
def register(self, stanza):
|
||||||
|
register_stanza_plugin(PrivateXML, stanza, iterable=True)
|
||||||
|
|
||||||
|
def store(self, data, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
|
||||||
|
if not isinstance(data, list):
|
||||||
|
data = [data]
|
||||||
|
|
||||||
|
for elem in data:
|
||||||
|
iq['private'].append(elem)
|
||||||
|
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def retrieve(self, name, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['private'].enable(name)
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
17
sleekxmpp/plugins/xep_0049/stanza.py
Normal file
17
sleekxmpp/plugins/xep_0049/stanza.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ET, ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class PrivateXML(ElementBase):
|
||||||
|
|
||||||
|
name = 'query'
|
||||||
|
namespace = 'jabber:iq:private'
|
||||||
|
plugin_attrib = 'private'
|
||||||
|
interfaces = set()
|
||||||
@@ -187,12 +187,6 @@ class XEP_0050(BasePlugin):
|
|||||||
jid = JID(jid)
|
jid = JID(jid)
|
||||||
item_jid = jid.full
|
item_jid = jid.full
|
||||||
|
|
||||||
# Client disco uses only the bare JID
|
|
||||||
if self.xmpp.is_component:
|
|
||||||
jid = jid.full
|
|
||||||
else:
|
|
||||||
jid = jid.bare
|
|
||||||
|
|
||||||
self.xmpp['xep_0030'].add_identity(category='automation',
|
self.xmpp['xep_0030'].add_identity(category='automation',
|
||||||
itype='command-list',
|
itype='command-list',
|
||||||
name='Ad-Hoc commands',
|
name='Ad-Hoc commands',
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import base64
|
import base64
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
from sleekxmpp.util import bytes
|
||||||
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, JID
|
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, JID
|
||||||
from sleekxmpp.plugins import xep_0082
|
from sleekxmpp.plugins import xep_0082
|
||||||
|
|
||||||
@@ -542,6 +541,7 @@ register_stanza_plugin(VCardTemp, Logo, iterable=True)
|
|||||||
register_stanza_plugin(VCardTemp, Mailer, iterable=True)
|
register_stanza_plugin(VCardTemp, Mailer, iterable=True)
|
||||||
register_stanza_plugin(VCardTemp, Note, iterable=True)
|
register_stanza_plugin(VCardTemp, Note, iterable=True)
|
||||||
register_stanza_plugin(VCardTemp, Nickname, iterable=True)
|
register_stanza_plugin(VCardTemp, Nickname, iterable=True)
|
||||||
|
register_stanza_plugin(VCardTemp, Org, iterable=True)
|
||||||
register_stanza_plugin(VCardTemp, Photo, iterable=True)
|
register_stanza_plugin(VCardTemp, Photo, iterable=True)
|
||||||
register_stanza_plugin(VCardTemp, ProdID, iterable=True)
|
register_stanza_plugin(VCardTemp, ProdID, iterable=True)
|
||||||
register_stanza_plugin(VCardTemp, Rev, iterable=True)
|
register_stanza_plugin(VCardTemp, Rev, iterable=True)
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ class XEP_0054(BasePlugin):
|
|||||||
|
|
||||||
def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
|
def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
|
||||||
callback=None, timeout=None):
|
callback=None, timeout=None):
|
||||||
|
self.api['set_vcard'](jid, None, ifrom, vcard)
|
||||||
if self.xmpp.is_component:
|
if self.xmpp.is_component:
|
||||||
self.api['set_vcard'](jid, None, ifrom, vcard)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
|
|||||||
@@ -25,11 +25,14 @@ class ResultIterator():
|
|||||||
An iterator for Result Set Managment
|
An iterator for Result Set Managment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, query, interface, amount=10, start=None, reverse=False):
|
def __init__(self, query, interface, results='substanzas', amount=10,
|
||||||
|
start=None, reverse=False):
|
||||||
"""
|
"""
|
||||||
Arguments:
|
Arguments:
|
||||||
query -- The template query
|
query -- The template query
|
||||||
interface -- The substanza of the query, for example disco_items
|
interface -- The substanza of the query, for example disco_items
|
||||||
|
results -- The query stanza's interface which provides a
|
||||||
|
countable list of query results.
|
||||||
amount -- The max amounts of items to request per iteration
|
amount -- The max amounts of items to request per iteration
|
||||||
start -- From which item id to start
|
start -- From which item id to start
|
||||||
reverse -- If True, page backwards through the results
|
reverse -- If True, page backwards through the results
|
||||||
@@ -46,6 +49,7 @@ class ResultIterator():
|
|||||||
self.amount = amount
|
self.amount = amount
|
||||||
self.start = start
|
self.start = start
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
|
self.results = results
|
||||||
self.reverse = reverse
|
self.reverse = reverse
|
||||||
self._stop = False
|
self._stop = False
|
||||||
|
|
||||||
@@ -85,7 +89,7 @@ class ResultIterator():
|
|||||||
r[self.interface]['rsm']['first_index']:
|
r[self.interface]['rsm']['first_index']:
|
||||||
count = int(r[self.interface]['rsm']['count'])
|
count = int(r[self.interface]['rsm']['count'])
|
||||||
first = int(r[self.interface]['rsm']['first_index'])
|
first = int(r[self.interface]['rsm']['first_index'])
|
||||||
num_items = len(r[self.interface]['substanzas'])
|
num_items = len(r[self.interface][self.results])
|
||||||
if first + num_items == count:
|
if first + num_items == count:
|
||||||
self._stop = True
|
self._stop = True
|
||||||
|
|
||||||
@@ -123,7 +127,7 @@ class XEP_0059(BasePlugin):
|
|||||||
def session_bind(self, jid):
|
def session_bind(self, jid):
|
||||||
self.xmpp['xep_0030'].add_feature(Set.namespace)
|
self.xmpp['xep_0030'].add_feature(Set.namespace)
|
||||||
|
|
||||||
def iterate(self, stanza, interface):
|
def iterate(self, stanza, interface, results='substanzas'):
|
||||||
"""
|
"""
|
||||||
Create a new result set iterator for a given stanza query.
|
Create a new result set iterator for a given stanza query.
|
||||||
|
|
||||||
@@ -135,5 +139,7 @@ class XEP_0059(BasePlugin):
|
|||||||
result set management stanza should be
|
result set management stanza should be
|
||||||
appended. For example, for disco#items queries
|
appended. For example, for disco#items queries
|
||||||
the interface 'disco_items' should be used.
|
the interface 'disco_items' should be used.
|
||||||
|
results -- The name of the interface containing the
|
||||||
|
query results (typically just 'substanzas').
|
||||||
"""
|
"""
|
||||||
return ResultIterator(stanza, interface)
|
return ResultIterator(stanza, interface, results)
|
||||||
|
|||||||
5
sleekxmpp/plugins/xep_0065/__init__.py
Normal file
5
sleekxmpp/plugins/xep_0065/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
from sleekxmpp.plugins.xep_0065.proxy import XEP_0065
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0065)
|
||||||
359
sleekxmpp/plugins/xep_0065/proxy.py
Normal file
359
sleekxmpp/plugins/xep_0065/proxy.py
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from threading import Thread, Event
|
||||||
|
from hashlib import sha1
|
||||||
|
from select import select
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0065 import stanza
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import base_plugin
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5
|
||||||
|
|
||||||
|
# Registers the sleekxmpp logger
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0065(base_plugin):
|
||||||
|
"""
|
||||||
|
XEP-0065 Socks5 Bytestreams
|
||||||
|
"""
|
||||||
|
|
||||||
|
description = "Socks5 Bytestreams"
|
||||||
|
dependencies = set(['xep_0030', ])
|
||||||
|
xep = '0065'
|
||||||
|
name = 'xep_0065'
|
||||||
|
|
||||||
|
# A dict contains for each SID, the proxy thread currently
|
||||||
|
# running.
|
||||||
|
proxy_threads = {}
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
""" Initializes the xep_0065 plugin and all event callbacks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Shortcuts to access to the xep_0030 plugin.
|
||||||
|
self.disco = self.xmpp['xep_0030']
|
||||||
|
|
||||||
|
# Handler for the streamhost stanza.
|
||||||
|
self.xmpp.registerHandler(
|
||||||
|
Callback('Socks5 Bytestreams',
|
||||||
|
StanzaPath('iq@type=set/socks/streamhost'),
|
||||||
|
self._handle_streamhost))
|
||||||
|
|
||||||
|
# Handler for the streamhost-used stanza.
|
||||||
|
self.xmpp.registerHandler(
|
||||||
|
Callback('Socks5 Bytestreams',
|
||||||
|
StanzaPath('iq@type=result/socks/streamhost-used'),
|
||||||
|
self._handle_streamhost_used))
|
||||||
|
|
||||||
|
def get_socket(self, sid):
|
||||||
|
""" Returns the socket associated to the SID.
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxy = self.proxy_threads.get(sid)
|
||||||
|
if proxy:
|
||||||
|
return proxy.s
|
||||||
|
|
||||||
|
def handshake(self, to, streamer=None):
|
||||||
|
""" Starts the handshake to establish the socks5 bytestreams
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Discovers the proxy.
|
||||||
|
self.streamer = streamer or self.discover_proxy()
|
||||||
|
|
||||||
|
# Requester requests network address from the proxy.
|
||||||
|
streamhost = self.get_network_address(self.streamer)
|
||||||
|
self.proxy_host = streamhost['socks']['streamhost']['host']
|
||||||
|
self.proxy_port = streamhost['socks']['streamhost']['port']
|
||||||
|
|
||||||
|
# Generates the SID for this new handshake.
|
||||||
|
sid = uuid4().hex
|
||||||
|
|
||||||
|
# Requester initiates S5B negotation with Target by sending
|
||||||
|
# IQ-set that includes the JabberID and network address of
|
||||||
|
# StreamHost as well as the StreamID (SID) of the proposed
|
||||||
|
# bytestream.
|
||||||
|
iq = self.xmpp.Iq(sto=to, stype='set')
|
||||||
|
iq['socks']['sid'] = sid
|
||||||
|
iq['socks']['streamhost']['jid'] = self.streamer
|
||||||
|
iq['socks']['streamhost']['host'] = self.proxy_host
|
||||||
|
iq['socks']['streamhost']['port'] = self.proxy_port
|
||||||
|
|
||||||
|
# Sends the new IQ.
|
||||||
|
return iq.send()
|
||||||
|
|
||||||
|
def discover_proxy(self):
|
||||||
|
""" Auto-discovers (using XEP 0030) the available bytestream
|
||||||
|
proxy on the XMPP server.
|
||||||
|
|
||||||
|
Returns the JID of the proxy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Gets all disco items.
|
||||||
|
disco_items = self.disco.get_items(self.xmpp.server)
|
||||||
|
|
||||||
|
for item in disco_items['disco_items']['items']:
|
||||||
|
# For each items, gets the disco info.
|
||||||
|
disco_info = self.disco.get_info(item[0])
|
||||||
|
|
||||||
|
# Gets and verifies if the identity is a bytestream proxy.
|
||||||
|
identities = disco_info['disco_info']['identities']
|
||||||
|
for identity in identities:
|
||||||
|
if identity[0] == 'proxy' and identity[1] == 'bytestreams':
|
||||||
|
# Returns when the first occurence is found.
|
||||||
|
return '%s' % disco_info['from']
|
||||||
|
|
||||||
|
def get_network_address(self, streamer):
|
||||||
|
""" Gets the streamhost information of the proxy.
|
||||||
|
|
||||||
|
streamer : The jid of the proxy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
iq = self.xmpp.Iq(sto=streamer, stype='get')
|
||||||
|
iq['socks'] # Adds the query eleme to the iq.
|
||||||
|
|
||||||
|
return iq.send()
|
||||||
|
|
||||||
|
def _handle_streamhost(self, iq):
|
||||||
|
""" Handles all streamhost stanzas.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Registers the streamhost info.
|
||||||
|
self.streamer = iq['socks']['streamhost']['jid']
|
||||||
|
self.proxy_host = iq['socks']['streamhost']['host']
|
||||||
|
self.proxy_port = iq['socks']['streamhost']['port']
|
||||||
|
|
||||||
|
# Sets the SID, the requester and the target.
|
||||||
|
sid = iq['socks']['sid']
|
||||||
|
requester = '%s' % iq['from']
|
||||||
|
target = '%s' % self.xmpp.boundjid
|
||||||
|
|
||||||
|
# Next the Target attempts to open a standard TCP socket on
|
||||||
|
# the network address of the Proxy.
|
||||||
|
self.proxy_thread = Proxy(sid, requester, target, self.proxy_host,
|
||||||
|
self.proxy_port, self.on_recv)
|
||||||
|
self.proxy_thread.start()
|
||||||
|
|
||||||
|
# Registers the new thread in the proxy_thread dict.
|
||||||
|
self.proxy_threads[sid] = self.proxy_thread
|
||||||
|
|
||||||
|
# Wait until the proxy is connected
|
||||||
|
self.proxy_thread.connected.wait()
|
||||||
|
|
||||||
|
# Replies to the incoming iq with a streamhost-used stanza.
|
||||||
|
res_iq = iq.reply()
|
||||||
|
res_iq['socks']['sid'] = sid
|
||||||
|
res_iq['socks']['streamhost-used']['jid'] = self.streamer
|
||||||
|
|
||||||
|
# Sends the IQ
|
||||||
|
return res_iq.send()
|
||||||
|
|
||||||
|
def _handle_streamhost_used(self, iq):
|
||||||
|
""" Handles all streamhost-used stanzas.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Sets the SID, the requester and the target.
|
||||||
|
sid = iq['socks']['sid']
|
||||||
|
requester = '%s' % self.xmpp.boundjid
|
||||||
|
target = '%s' % iq['from']
|
||||||
|
|
||||||
|
# The Requester will establish a connection to the SOCKS5
|
||||||
|
# proxy in the same way the Target did.
|
||||||
|
self.proxy_thread = Proxy(sid, requester, target, self.proxy_host,
|
||||||
|
self.proxy_port, self.on_recv)
|
||||||
|
self.proxy_thread.start()
|
||||||
|
|
||||||
|
# Registers the new thread in the proxy_thread dict.
|
||||||
|
self.proxy_threads[sid] = self.proxy_thread
|
||||||
|
|
||||||
|
# Wait until the proxy is connected
|
||||||
|
self.proxy_thread.connected.wait()
|
||||||
|
|
||||||
|
# Requester sends IQ-set to StreamHost requesting that
|
||||||
|
# StreamHost activate the bytestream associated with the
|
||||||
|
# StreamID.
|
||||||
|
self.activate(iq['socks']['sid'], target)
|
||||||
|
|
||||||
|
def activate(self, sid, to):
|
||||||
|
""" IQ-set to StreamHost requesting that StreamHost activate
|
||||||
|
the bytestream associated with the StreamID.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Creates the activate IQ.
|
||||||
|
act_iq = self.xmpp.Iq(sto=self.streamer, stype='set')
|
||||||
|
act_iq['socks']['sid'] = sid
|
||||||
|
act_iq['socks']['activate'] = to
|
||||||
|
|
||||||
|
# Send the IQ.
|
||||||
|
act_iq.send()
|
||||||
|
|
||||||
|
def deactivate(self, sid):
|
||||||
|
""" Closes the Proxy thread associated to this SID.
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxy = self.proxy_threads.get(sid)
|
||||||
|
if proxy:
|
||||||
|
proxy.s.close()
|
||||||
|
del self.proxy_threads[sid]
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
""" Closes all Proxy threads.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for sid, proxy in self.proxy_threads.items():
|
||||||
|
proxy.s.close()
|
||||||
|
del self.proxy_threads[sid]
|
||||||
|
|
||||||
|
def send(self, sid, data):
|
||||||
|
""" Sends the data over the Proxy socket associated to the
|
||||||
|
SID.
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxy = self.proxy_threads.get(sid)
|
||||||
|
if proxy:
|
||||||
|
proxy.s.sendall(data)
|
||||||
|
|
||||||
|
def on_recv(self, sid, data):
|
||||||
|
""" Calls when data is recv from the Proxy socket associated
|
||||||
|
to the SID.
|
||||||
|
|
||||||
|
Triggers a socks_closed event if the socket is closed. The sid
|
||||||
|
is passed to this event.
|
||||||
|
|
||||||
|
Triggers a socks_recv event if there's available data. A dict
|
||||||
|
that contains the sid and the data is passed to this event.
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxy = self.proxy_threads.get(sid)
|
||||||
|
if proxy:
|
||||||
|
if not data:
|
||||||
|
self.xmpp.event('socks_closed', sid)
|
||||||
|
else:
|
||||||
|
self.xmpp.event('socks_recv', {'sid': sid, 'data': data})
|
||||||
|
|
||||||
|
|
||||||
|
class Proxy(Thread):
|
||||||
|
""" Establishes in a thread a connection between the client and
|
||||||
|
the server-side Socks5 proxy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, sid, requester, target, proxy, proxy_port,
|
||||||
|
on_recv):
|
||||||
|
""" Initializes the proxy thread.
|
||||||
|
|
||||||
|
sid : The StreamID. <str>
|
||||||
|
requester : The JID of the requester. <str>
|
||||||
|
target : The JID of the target. <str>
|
||||||
|
proxy_host : The hostname or the IP of the proxy. <str>
|
||||||
|
proxy_port : The port of the proxy. <str> or <int>
|
||||||
|
on_recv : A callback called when data are received from the
|
||||||
|
socket. <Callable>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Initializes the thread.
|
||||||
|
Thread.__init__(self)
|
||||||
|
|
||||||
|
# Because the xep_0065 plugin uses the proxy_port as string,
|
||||||
|
# the Proxy class accepts the proxy_port argument as a string
|
||||||
|
# or an integer. Here, we force to use the port as an integer.
|
||||||
|
proxy_port = int(proxy_port)
|
||||||
|
|
||||||
|
# Creates a connected event to warn when to proxy is
|
||||||
|
# connected.
|
||||||
|
self.connected = Event()
|
||||||
|
|
||||||
|
# Registers the arguments.
|
||||||
|
self.sid = sid
|
||||||
|
self.requester = requester
|
||||||
|
self.target = target
|
||||||
|
self.proxy = proxy
|
||||||
|
self.proxy_port = proxy_port
|
||||||
|
self.on_recv = on_recv
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
""" Starts the thread.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Creates the socks5 proxy socket
|
||||||
|
self.s = socksocket()
|
||||||
|
self.s.setproxy(PROXY_TYPE_SOCKS5, self.proxy, port=self.proxy_port)
|
||||||
|
|
||||||
|
# The hostname MUST be SHA1(SID + Requester JID + Target JID)
|
||||||
|
# where the output is hexadecimal-encoded (not binary).
|
||||||
|
digest = sha1()
|
||||||
|
digest.update(self.sid) # SID
|
||||||
|
digest.update(self.requester) # Requester JID
|
||||||
|
digest.update(self.target) # Target JID
|
||||||
|
|
||||||
|
# Computes the digest in hex.
|
||||||
|
dest = '%s' % digest.hexdigest()
|
||||||
|
|
||||||
|
# The port MUST be 0.
|
||||||
|
self.s.connect((dest, 0))
|
||||||
|
log.info('Socket connected.')
|
||||||
|
self.connected.set()
|
||||||
|
|
||||||
|
# Blocks until the socket need to be closed.
|
||||||
|
self.listen()
|
||||||
|
|
||||||
|
# Closes the socket.
|
||||||
|
self.s.close()
|
||||||
|
log.info('Socket closed.')
|
||||||
|
|
||||||
|
def listen(self):
|
||||||
|
""" Listen for data on the socket. When receiving data, call
|
||||||
|
the callback on_recv callable.
|
||||||
|
"""
|
||||||
|
|
||||||
|
socket_open = True
|
||||||
|
while socket_open:
|
||||||
|
ins = []
|
||||||
|
try:
|
||||||
|
# Wait any read available data on socket. Timeout
|
||||||
|
# after 5 secs.
|
||||||
|
ins, out, err = select([self.s, ], [], [], 5)
|
||||||
|
except Exception as e:
|
||||||
|
# There's an error with the socket (maybe the socket
|
||||||
|
# has been closed and the file descriptor is bad).
|
||||||
|
log.debug('Socket error: %s' % e)
|
||||||
|
break
|
||||||
|
|
||||||
|
for s in ins:
|
||||||
|
data = self.recv_size(self.s)
|
||||||
|
if not data:
|
||||||
|
socket_open = False
|
||||||
|
|
||||||
|
self.on_recv(self.sid, data)
|
||||||
|
|
||||||
|
def recv_size(self, the_socket):
|
||||||
|
total_len = 0
|
||||||
|
total_data = []
|
||||||
|
size = sys.maxint
|
||||||
|
size_data = sock_data = ''
|
||||||
|
recv_size = 8192
|
||||||
|
|
||||||
|
while total_len < size:
|
||||||
|
sock_data = the_socket.recv(recv_size)
|
||||||
|
if not sock_data:
|
||||||
|
return ''.join(total_data)
|
||||||
|
|
||||||
|
if not total_data:
|
||||||
|
if len(sock_data) > 4:
|
||||||
|
size_data += sock_data
|
||||||
|
size = struct.unpack('>i', size_data[:4])[0]
|
||||||
|
recv_size = size
|
||||||
|
if recv_size > 524288:
|
||||||
|
recv_size = 524288
|
||||||
|
total_data.append(size_data[4:])
|
||||||
|
else:
|
||||||
|
size_data += sock_data
|
||||||
|
else:
|
||||||
|
total_data.append(sock_data)
|
||||||
|
total_len = sum([len(i) for i in total_data])
|
||||||
|
return ''.join(total_data)
|
||||||
41
sleekxmpp/plugins/xep_0065/stanza.py
Normal file
41
sleekxmpp/plugins/xep_0065/stanza.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from sleekxmpp import Iq
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
# The protocol namespace defined in the Socks5Bytestream (0065) spec.
|
||||||
|
namespace = 'http://jabber.org/protocol/bytestreams'
|
||||||
|
|
||||||
|
|
||||||
|
class StreamHost(ElementBase):
|
||||||
|
""" The streamhost xml element.
|
||||||
|
"""
|
||||||
|
|
||||||
|
namespace = namespace
|
||||||
|
name = 'streamhost'
|
||||||
|
plugin_attrib = 'streamhost'
|
||||||
|
interfaces = set(('host', 'jid', 'port'))
|
||||||
|
|
||||||
|
|
||||||
|
class StreamHostUsed(ElementBase):
|
||||||
|
""" The streamhost-used xml element.
|
||||||
|
"""
|
||||||
|
|
||||||
|
namespace = namespace
|
||||||
|
name = 'streamhost-used'
|
||||||
|
plugin_attrib = 'streamhost-used'
|
||||||
|
interfaces = set(('jid',))
|
||||||
|
|
||||||
|
|
||||||
|
class Socks5(ElementBase):
|
||||||
|
""" The query xml element.
|
||||||
|
"""
|
||||||
|
|
||||||
|
namespace = namespace
|
||||||
|
name = 'query'
|
||||||
|
plugin_attrib = 'socks'
|
||||||
|
interfaces = set(('sid', 'activate'))
|
||||||
|
sub_interfaces = set(('activate',))
|
||||||
|
|
||||||
|
register_stanza_plugin(Iq, Socks5)
|
||||||
|
register_stanza_plugin(Socks5, StreamHost)
|
||||||
|
register_stanza_plugin(Socks5, StreamHostUsed)
|
||||||
@@ -11,6 +11,7 @@ import hashlib
|
|||||||
import random
|
import random
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from sleekxmpp.jid import JID
|
||||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
from sleekxmpp.stanza import Iq, StreamFeatures
|
||||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
|
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
|
||||||
@@ -44,16 +45,27 @@ class XEP_0078(BasePlugin):
|
|||||||
restart=False,
|
restart=False,
|
||||||
order=self.order)
|
order=self.order)
|
||||||
|
|
||||||
|
self.xmpp.add_event_handler('legacy_protocol',
|
||||||
|
self._handle_legacy_protocol)
|
||||||
|
|
||||||
register_stanza_plugin(Iq, stanza.IqAuth)
|
register_stanza_plugin(Iq, stanza.IqAuth)
|
||||||
register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
|
register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
|
||||||
|
|
||||||
def plugin_end(self):
|
def plugin_end(self):
|
||||||
|
self.xmpp.del_event_handler('legacy_protocol',
|
||||||
|
self._handle_legacy_protocol)
|
||||||
self.xmpp.unregister_feature('auth', self.order)
|
self.xmpp.unregister_feature('auth', self.order)
|
||||||
|
|
||||||
def _handle_auth(self, features):
|
def _handle_auth(self, features):
|
||||||
# If we can or have already authenticated with SASL, do nothing.
|
# If we can or have already authenticated with SASL, do nothing.
|
||||||
if 'mechanisms' in features['features']:
|
if 'mechanisms' in features['features']:
|
||||||
return False
|
return False
|
||||||
|
return self.authenticate()
|
||||||
|
|
||||||
|
def _handle_legacy_protocol(self, event):
|
||||||
|
self.authenticate()
|
||||||
|
|
||||||
|
def authenticate(self):
|
||||||
if self.xmpp.authenticated:
|
if self.xmpp.authenticated:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -62,13 +74,13 @@ class XEP_0078(BasePlugin):
|
|||||||
# Step 1: Request the auth form
|
# Step 1: Request the auth form
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq['to'] = self.xmpp.boundjid.host
|
iq['to'] = self.xmpp.requested_jid.host
|
||||||
iq['auth']['username'] = self.xmpp.boundjid.user
|
iq['auth']['username'] = self.xmpp.requested_jid.user
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = iq.send(now=True)
|
resp = iq.send(now=True)
|
||||||
except IqError:
|
except IqError as err:
|
||||||
log.info("Authentication failed: %s", resp['error']['condition'])
|
log.info("Authentication failed: %s", err.iq['error']['condition'])
|
||||||
self.xmpp.event('failed_auth', direct=True)
|
self.xmpp.event('failed_auth', direct=True)
|
||||||
self.xmpp.disconnect()
|
self.xmpp.disconnect()
|
||||||
return True
|
return True
|
||||||
@@ -81,13 +93,14 @@ class XEP_0078(BasePlugin):
|
|||||||
# Step 2: Fill out auth form for either password or digest auth
|
# Step 2: Fill out auth form for either password or digest auth
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['auth']['username'] = self.xmpp.boundjid.user
|
iq['auth']['username'] = self.xmpp.requested_jid.user
|
||||||
|
|
||||||
# A resource is required, so create a random one if necessary
|
# A resource is required, so create a random one if necessary
|
||||||
if self.xmpp.boundjid.resource:
|
resource = self.xmpp.requested_jid.resource
|
||||||
iq['auth']['resource'] = self.xmpp.boundjid.resource
|
if not resource:
|
||||||
else:
|
resource = uuid.uuid4()
|
||||||
iq['auth']['resource'] = '%s' % random.random()
|
|
||||||
|
iq['auth']['resource'] = resource
|
||||||
|
|
||||||
if 'digest' in resp['auth']['fields']:
|
if 'digest' in resp['auth']['fields']:
|
||||||
log.debug('Authenticating via jabber:iq:auth Digest')
|
log.debug('Authenticating via jabber:iq:auth Digest')
|
||||||
@@ -109,16 +122,22 @@ class XEP_0078(BasePlugin):
|
|||||||
result = iq.send(now=True)
|
result = iq.send(now=True)
|
||||||
except IqError as err:
|
except IqError as err:
|
||||||
log.info("Authentication failed")
|
log.info("Authentication failed")
|
||||||
self.xmpp.disconnect()
|
|
||||||
self.xmpp.event("failed_auth", direct=True)
|
self.xmpp.event("failed_auth", direct=True)
|
||||||
|
self.xmpp.disconnect()
|
||||||
except IqTimeout:
|
except IqTimeout:
|
||||||
log.info("Authentication failed")
|
log.info("Authentication failed")
|
||||||
self.xmpp.disconnect()
|
|
||||||
self.xmpp.event("failed_auth", direct=True)
|
self.xmpp.event("failed_auth", direct=True)
|
||||||
|
self.xmpp.disconnect()
|
||||||
|
|
||||||
self.xmpp.features.add('auth')
|
self.xmpp.features.add('auth')
|
||||||
|
|
||||||
self.xmpp.authenticated = True
|
self.xmpp.authenticated = True
|
||||||
|
|
||||||
|
self.xmpp.boundjid = JID(self.xmpp.requested_jid,
|
||||||
|
resource=resource,
|
||||||
|
cache_lock=True)
|
||||||
|
self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True)
|
||||||
|
|
||||||
log.debug("Established Session")
|
log.debug("Established Session")
|
||||||
self.xmpp.sessionstarted = True
|
self.xmpp.sessionstarted = True
|
||||||
self.xmpp.session_started_event.set()
|
self.xmpp.session_started_event.set()
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class XEP_0084(BasePlugin):
|
|||||||
metadata = MetaData()
|
metadata = MetaData()
|
||||||
if items is None:
|
if items is None:
|
||||||
items = []
|
items = []
|
||||||
|
if not isinstance(items, (list, set)):
|
||||||
|
items = [items]
|
||||||
for info in items:
|
for info in items:
|
||||||
metadata.add_info(info['id'], info['type'], info['bytes'],
|
metadata.add_info(info['id'], info['type'], info['bytes'],
|
||||||
height=info.get('height', ''),
|
height=info.get('height', ''),
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
|
||||||
|
|
||||||
|
from sleekxmpp.util import bytes
|
||||||
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,4 +52,5 @@ class XEP_0085(BasePlugin):
|
|||||||
def _handle_chat_state(self, msg):
|
def _handle_chat_state(self, msg):
|
||||||
state = msg['chat_state']
|
state = msg['chat_state']
|
||||||
log.debug("Chat State: %s, %s", state, msg['from'].jid)
|
log.debug("Chat State: %s, %s", state, msg['from'].jid)
|
||||||
|
self.xmpp.event('chatstate', msg)
|
||||||
self.xmpp.event('chatstate_%s' % state, msg)
|
self.xmpp.event('chatstate_%s' % state, msg)
|
||||||
|
|||||||
16
sleekxmpp/plugins/xep_0091/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0091/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0091 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0091.stanza import LegacyDelay
|
||||||
|
from sleekxmpp.plugins.xep_0091.legacy_delay import XEP_0091
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0091)
|
||||||
29
sleekxmpp/plugins/xep_0091/legacy_delay.py
Normal file
29
sleekxmpp/plugins/xep_0091/legacy_delay.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Message, Presence
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.xep_0091 import stanza
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0091(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
XEP-0091: Legacy Delayed Delivery
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0091'
|
||||||
|
description = 'XEP-0091: Legacy Delayed Delivery'
|
||||||
|
dependencies = set()
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Message, stanza.LegacyDelay)
|
||||||
|
register_stanza_plugin(Presence, stanza.LegacyDelay)
|
||||||
46
sleekxmpp/plugins/xep_0091/stanza.py
Normal file
46
sleekxmpp/plugins/xep_0091/stanza.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
from sleekxmpp.jid import JID
|
||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
from sleekxmpp.plugins import xep_0082
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyDelay(ElementBase):
|
||||||
|
|
||||||
|
name = 'x'
|
||||||
|
namespace = 'jabber:x:delay'
|
||||||
|
plugin_attrib = 'legacy_delay'
|
||||||
|
interfaces = set(('from', 'stamp', 'text'))
|
||||||
|
|
||||||
|
def get_from(self):
|
||||||
|
return JID(self._get_attr('from'))
|
||||||
|
|
||||||
|
def set_from(self, value):
|
||||||
|
self._set_attr('from', str(value))
|
||||||
|
|
||||||
|
def get_stamp(self):
|
||||||
|
timestamp = self._get_attr('stamp')
|
||||||
|
return xep_0082.parse('%sZ' % timestamp)
|
||||||
|
|
||||||
|
def set_stamp(self, value):
|
||||||
|
if isinstance(value, dt.datetime):
|
||||||
|
value = value.astimezone(xep_0082.tzutc)
|
||||||
|
value = xep_0082.format_datetime(value)
|
||||||
|
self._set_attr('stamp', value[0:19].replace('-', ''))
|
||||||
|
|
||||||
|
def get_text(self):
|
||||||
|
return self.xml.text
|
||||||
|
|
||||||
|
def set_text(self, value):
|
||||||
|
self.xml.text = value
|
||||||
|
|
||||||
|
def del_text(self):
|
||||||
|
self.xml.text = ''
|
||||||
@@ -143,6 +143,11 @@ class XEP_0115(BasePlugin):
|
|||||||
if str(existing_verstring) == str(pres['caps']['ver']):
|
if str(existing_verstring) == str(pres['caps']['ver']):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
existing_caps = self.get_caps(verstring=pres['caps']['ver'])
|
||||||
|
if existing_caps is not None:
|
||||||
|
self.assign_verstring(pres['from'], pres['caps']['ver'])
|
||||||
|
return
|
||||||
|
|
||||||
if pres['caps']['hash'] not in self.hashes:
|
if pres['caps']['hash'] not in self.hashes:
|
||||||
try:
|
try:
|
||||||
log.debug("Unknown caps hash: %s", pres['caps']['hash'])
|
log.debug("Unknown caps hash: %s", pres['caps']['hash'])
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import threading
|
|||||||
|
|
||||||
from sleekxmpp import JID
|
from sleekxmpp import JID
|
||||||
from sleekxmpp.stanza import Presence
|
from sleekxmpp.stanza import Presence
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
from sleekxmpp.xmlstream.handler import Callback
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
@@ -103,15 +104,18 @@ class XEP_0153(BasePlugin):
|
|||||||
if own_jid:
|
if own_jid:
|
||||||
self.xmpp.roster[jid].send_last_presence()
|
self.xmpp.roster[jid].send_last_presence()
|
||||||
|
|
||||||
iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom)
|
try:
|
||||||
|
iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom)
|
||||||
|
|
||||||
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
||||||
if not data:
|
if not data:
|
||||||
new_hash = ''
|
new_hash = ''
|
||||||
else:
|
else:
|
||||||
new_hash = hashlib.sha1(data).hexdigest()
|
new_hash = hashlib.sha1(data).hexdigest()
|
||||||
|
|
||||||
self.api['set_hash'](jid, args=new_hash)
|
self.api['set_hash'](jid, args=new_hash)
|
||||||
|
except XMPPError:
|
||||||
|
log.debug('Could not retrieve vCard for %s' % jid)
|
||||||
|
|
||||||
def _recv_presence(self, pres):
|
def _recv_presence(self, pres):
|
||||||
if not pres.match('presence/vcard_temp_update'):
|
if not pres.match('presence/vcard_temp_update'):
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class XEP_0191(BasePlugin):
|
|||||||
def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None):
|
def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq['from'] = 'ifrom'
|
iq['from'] = ifrom
|
||||||
iq.enable('blocklist')
|
iq.enable('blocklist')
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,17 @@ from sleekxmpp.plugins import xep_0082
|
|||||||
|
|
||||||
class Delay(ElementBase):
|
class Delay(ElementBase):
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'delay'
|
name = 'delay'
|
||||||
namespace = 'urn:xmpp:delay'
|
namespace = 'urn:xmpp:delay'
|
||||||
plugin_attrib = 'delay'
|
plugin_attrib = 'delay'
|
||||||
interfaces = set(('from', 'stamp', 'text'))
|
interfaces = set(('from', 'stamp', 'text'))
|
||||||
|
|
||||||
|
def get_from(self):
|
||||||
|
return JID(self._get_attr('from'))
|
||||||
|
|
||||||
|
def set_from(self, value):
|
||||||
|
self._set_attr('from', str(value))
|
||||||
|
|
||||||
def get_stamp(self):
|
def get_stamp(self):
|
||||||
timestamp = self._get_attr('stamp')
|
timestamp = self._get_attr('stamp')
|
||||||
return xep_0082.parse(timestamp)
|
return xep_0082.parse(timestamp)
|
||||||
|
|||||||
16
sleekxmpp/plugins/xep_0235/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0235/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0235 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0235.stanza import OAuth
|
||||||
|
from sleekxmpp.plugins.xep_0235.oauth import XEP_0235
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0235)
|
||||||
32
sleekxmpp/plugins/xep_0235/oauth.py
Normal file
32
sleekxmpp/plugins/xep_0235/oauth.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp import Message
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins.xep_0235 import stanza, OAuth
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0235(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0235'
|
||||||
|
description = 'XEP-0235: OAuth Over XMPP'
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Message, OAuth)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature('urn:xmpp:oauth:0')
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:oauth:0')
|
||||||
80
sleekxmpp/plugins/xep_0235/stanza.py
Normal file
80
sleekxmpp/plugins/xep_0235/stanza.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import urllib
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ET, ElementBase, JID
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth(ElementBase):
|
||||||
|
|
||||||
|
name = 'oauth'
|
||||||
|
namespace = 'urn:xmpp:oauth:0'
|
||||||
|
plugin_attrib = 'oauth'
|
||||||
|
interfaces = set(['oauth_consumer_key', 'oauth_nonce', 'oauth_signature',
|
||||||
|
'oauth_signature_method', 'oauth_timestamp',
|
||||||
|
'oauth_token', 'oauth_version'])
|
||||||
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
|
def generate_signature(self, stanza, sfrom, sto, consumer_secret,
|
||||||
|
token_secret, method='HMAC-SHA1'):
|
||||||
|
self['oauth_signature_method'] = method
|
||||||
|
|
||||||
|
request = urllib.quote('%s&%s' % (sfrom, sto), '')
|
||||||
|
parameters = urllib.quote('&'.join([
|
||||||
|
'oauth_consumer_key=%s' % self['oauth_consumer_key'],
|
||||||
|
'oauth_nonce=%s' % self['oauth_nonce'],
|
||||||
|
'oauth_signature_method=%s' % self['oauth_signature_method'],
|
||||||
|
'oauth_timestamp=%s' % self['oauth_timestamp'],
|
||||||
|
'oauth_token=%s' % self['oauth_token'],
|
||||||
|
'oauth_version=%s' % self['oauth_version']
|
||||||
|
]), '')
|
||||||
|
|
||||||
|
sigbase = '%s&%s&%s' % (stanza, request, parameters)
|
||||||
|
|
||||||
|
consumer_secret = urllib.quote(consumer_secret, '')
|
||||||
|
token_secret = urllib.quote(token_secret, '')
|
||||||
|
key = '%s&%s' % (consumer_secret, token_secret)
|
||||||
|
|
||||||
|
if method == 'HMAC-SHA1':
|
||||||
|
sig = base64.b64encode(hmac.new(key, sigbase, hashlib.sha1).digest())
|
||||||
|
elif method == 'PLAINTEXT':
|
||||||
|
sig = key
|
||||||
|
|
||||||
|
self['oauth_signature'] = sig
|
||||||
|
return sig
|
||||||
|
|
||||||
|
def verify_signature(self, stanza, sfrom, sto, consumer_secret,
|
||||||
|
token_secret):
|
||||||
|
method = self['oauth_signature_method']
|
||||||
|
|
||||||
|
request = urllib.quote('%s&%s' % (sfrom, sto), '')
|
||||||
|
parameters = urllib.quote('&'.join([
|
||||||
|
'oauth_consumer_key=%s' % self['oauth_consumer_key'],
|
||||||
|
'oauth_nonce=%s' % self['oauth_nonce'],
|
||||||
|
'oauth_signature_method=%s' % self['oauth_signature_method'],
|
||||||
|
'oauth_timestamp=%s' % self['oauth_timestamp'],
|
||||||
|
'oauth_token=%s' % self['oauth_token'],
|
||||||
|
'oauth_version=%s' % self['oauth_version']
|
||||||
|
]), '')
|
||||||
|
|
||||||
|
sigbase = '%s&%s&%s' % (stanza, request, parameters)
|
||||||
|
|
||||||
|
consumer_secret = urllib.quote(consumer_secret, '')
|
||||||
|
token_secret = urllib.quote(token_secret, '')
|
||||||
|
key = '%s&%s' % (consumer_secret, token_secret)
|
||||||
|
|
||||||
|
if method == 'HMAC-SHA1':
|
||||||
|
sig = base64.b64encode(hmac.new(key, sigbase, hashlib.sha1).digest())
|
||||||
|
elif method == 'PLAINTEXT':
|
||||||
|
sig = key
|
||||||
|
|
||||||
|
return self['oauth_signature'] == sig
|
||||||
21
sleekxmpp/plugins/xep_0242.py
Normal file
21
sleekxmpp/plugins/xep_0242.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins import BasePlugin, register_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0242(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0242'
|
||||||
|
description = 'XEP-0242: XMPP Client Compliance 2009'
|
||||||
|
dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
|
||||||
|
'xep_0045', 'xep_0085', 'xep_0016',
|
||||||
|
'xep_0191'])
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0242)
|
||||||
17
sleekxmpp/plugins/xep_0257/__init__.py
Normal file
17
sleekxmpp/plugins/xep_0257/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0257 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0257.stanza import Certs, AppendCert
|
||||||
|
from sleekxmpp.plugins.xep_0257.stanza import DisableCert, RevokeCert
|
||||||
|
from sleekxmpp.plugins.xep_0257.client_cert_management import XEP_0257
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0257)
|
||||||
65
sleekxmpp/plugins/xep_0257/client_cert_management.py
Normal file
65
sleekxmpp/plugins/xep_0257/client_cert_management.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp import Iq
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins.xep_0257 import stanza, Certs
|
||||||
|
from sleekxmpp.plugins.xep_0257 import AppendCert, DisableCert, RevokeCert
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0257(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0257'
|
||||||
|
description = 'XEP-0258: Client Certificate Management for SASL EXTERNAL'
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, Certs)
|
||||||
|
register_stanza_plugin(Iq, AppendCert)
|
||||||
|
register_stanza_plugin(Iq, DisableCert)
|
||||||
|
register_stanza_plugin(Iq, RevokeCert)
|
||||||
|
|
||||||
|
def get_certs(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq.enable('sasl_certs')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def add_cert(self, name, cert, allow_management=True, ifrom=None,
|
||||||
|
block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['sasl_cert_append']['name'] = name
|
||||||
|
iq['sasl_cert_append']['x509cert'] = cert
|
||||||
|
iq['sasl_cert_append']['cert_management'] = allow_management
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def disable_cert(self, name, ifrom=None, block=True,
|
||||||
|
timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['sasl_cert_disable']['name'] = name
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def revoke_cert(self, name, ifrom=None, block=True,
|
||||||
|
timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['sasl_cert_revoke']['name'] = name
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
87
sleekxmpp/plugins/xep_0257/stanza.py
Normal file
87
sleekxmpp/plugins/xep_0257/stanza.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class Certs(ElementBase):
|
||||||
|
name = 'query'
|
||||||
|
namespace = 'urn:xmpp:saslcert:1'
|
||||||
|
plugin_attrib = 'sasl_certs'
|
||||||
|
interfaces = set()
|
||||||
|
|
||||||
|
|
||||||
|
class CertItem(ElementBase):
|
||||||
|
name = 'item'
|
||||||
|
namespace = 'urn:xmpp:saslcert:1'
|
||||||
|
plugin_attrib = 'item'
|
||||||
|
plugin_multi_attrib = 'items'
|
||||||
|
interfaces = set(['name', 'x509cert', 'users'])
|
||||||
|
sub_interfaces = set(['name', 'x509cert'])
|
||||||
|
|
||||||
|
def get_users(self):
|
||||||
|
resources = self.xml.findall('{%s}users/{%s}resource' % (
|
||||||
|
self.namespace, self.namespace))
|
||||||
|
return set([res.text for res in resources])
|
||||||
|
|
||||||
|
def set_users(self, values):
|
||||||
|
users = self.xml.find('{%s}users' % self.namespace)
|
||||||
|
if users is None:
|
||||||
|
users = ET.Element('{%s}users' % self.namespace)
|
||||||
|
self.xml.append(users)
|
||||||
|
for resource in values:
|
||||||
|
res = ET.Element('{%s}resource' % self.namespace)
|
||||||
|
res.text = resource
|
||||||
|
users.append(res)
|
||||||
|
|
||||||
|
def del_users(self):
|
||||||
|
users = self.xml.find('{%s}users' % self.namespace)
|
||||||
|
if users is not None:
|
||||||
|
self.xml.remove(users)
|
||||||
|
|
||||||
|
|
||||||
|
class AppendCert(ElementBase):
|
||||||
|
name = 'append'
|
||||||
|
namespace = 'urn:xmpp:saslcert:1'
|
||||||
|
plugin_attrib = 'sasl_cert_append'
|
||||||
|
interfaces = set(['name', 'x509cert', 'cert_management'])
|
||||||
|
sub_interfaces = set(['name', 'x509cert'])
|
||||||
|
|
||||||
|
def get_cert_management(self):
|
||||||
|
manage = self.xml.find('{%s}no-cert-management' % self.namespace)
|
||||||
|
return manage is None
|
||||||
|
|
||||||
|
def set_cert_management(self, value):
|
||||||
|
self.del_cert_management()
|
||||||
|
if not value:
|
||||||
|
manage = ET.Element('{%s}no-cert-management' % self.namespace)
|
||||||
|
self.xml.append(manage)
|
||||||
|
|
||||||
|
def del_cert_management(self):
|
||||||
|
manage = self.xml.find('{%s}no-cert-management' % self.namespace)
|
||||||
|
if manage is not None:
|
||||||
|
self.xml.remove(manage)
|
||||||
|
|
||||||
|
|
||||||
|
class DisableCert(ElementBase):
|
||||||
|
name = 'disable'
|
||||||
|
namespace = 'urn:xmpp:saslcert:1'
|
||||||
|
plugin_attrib = 'sasl_cert_disable'
|
||||||
|
interfaces = set(['name'])
|
||||||
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
|
|
||||||
|
class RevokeCert(ElementBase):
|
||||||
|
name = 'revoke'
|
||||||
|
namespace = 'urn:xmpp:saslcert:1'
|
||||||
|
plugin_attrib = 'sasl_cert_revoke'
|
||||||
|
interfaces = set(['name'])
|
||||||
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(Certs, CertItem, iterable=True)
|
||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
|
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
from sleekxmpp.util import bytes
|
||||||
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
|
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
sleekxmpp/plugins/xep_0279/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0279/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0279 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0279.stanza import IPCheck
|
||||||
|
from sleekxmpp.plugins.xep_0279.ipcheck import XEP_0279
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0279)
|
||||||
39
sleekxmpp/plugins/xep_0279/ipcheck.py
Normal file
39
sleekxmpp/plugins/xep_0279/ipcheck.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp import Iq
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins.xep_0279 import stanza, IPCheck
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0279(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0279'
|
||||||
|
description = 'XEP-0279: Server IP Check'
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, IPCheck)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature('urn:xmpp:sic:0')
|
||||||
|
|
||||||
|
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):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq.enable('ip_check')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
30
sleekxmpp/plugins/xep_0279/stanza.py
Normal file
30
sleekxmpp/plugins/xep_0279/stanza.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class IPCheck(ElementBase):
|
||||||
|
|
||||||
|
name = 'ip'
|
||||||
|
namespace = 'urn:xmpp:sic:0'
|
||||||
|
plugin_attrib = 'ip_check'
|
||||||
|
interfaces = set(['ip_check'])
|
||||||
|
is_extension = True
|
||||||
|
|
||||||
|
def get_ip_check(self):
|
||||||
|
return self.xml.text
|
||||||
|
|
||||||
|
def set_ip_check(self, value):
|
||||||
|
if value:
|
||||||
|
self.xml.text = value
|
||||||
|
else:
|
||||||
|
self.xml.text = ''
|
||||||
|
|
||||||
|
def del_ip_check(self):
|
||||||
|
self.xml.text = ''
|
||||||
17
sleekxmpp/plugins/xep_0280/__init__.py
Normal file
17
sleekxmpp/plugins/xep_0280/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0280.stanza import ReceivedCarbon, SentCarbon
|
||||||
|
from sleekxmpp.plugins.xep_0280.stanza import PrivateCarbon
|
||||||
|
from sleekxmpp.plugins.xep_0280.stanza import CarbonEnable, CarbonDisable
|
||||||
|
from sleekxmpp.plugins.xep_0280.carbons import XEP_0280
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0280)
|
||||||
81
sleekxmpp/plugins/xep_0280/carbons.py
Normal file
81
sleekxmpp/plugins/xep_0280/carbons.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.stanza import Message, Iq
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.xep_0280 import stanza
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0280(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
XEP-0280 Message Carbons
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0280'
|
||||||
|
description = 'XEP-0280: Message Carbons'
|
||||||
|
dependencies = set(['xep_0030', 'xep_0297'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Carbon Received',
|
||||||
|
StanzaPath('message/carbon_received'),
|
||||||
|
self._handle_carbon_received))
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Carbon Sent',
|
||||||
|
StanzaPath('message/carbon_sent'),
|
||||||
|
self._handle_carbon_sent))
|
||||||
|
|
||||||
|
register_stanza_plugin(Message, stanza.ReceivedCarbon)
|
||||||
|
register_stanza_plugin(Message, stanza.SentCarbon)
|
||||||
|
register_stanza_plugin(Message, stanza.PrivateCarbon)
|
||||||
|
register_stanza_plugin(Iq, stanza.CarbonEnable)
|
||||||
|
register_stanza_plugin(Iq, stanza.CarbonDisable)
|
||||||
|
|
||||||
|
register_stanza_plugin(stanza.ReceivedCarbon,
|
||||||
|
self.xmpp['xep_0297'].stanza.Forwarded)
|
||||||
|
register_stanza_plugin(stanza.SentCarbon,
|
||||||
|
self.xmpp['xep_0297'].stanza.Forwarded)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('Carbon Received')
|
||||||
|
self.xmpp.remove_handler('Carbon Sent')
|
||||||
|
self.xmpp.plugin['xep_0030'].del_feature(feature='urn:xmpp:carbons:2')
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:carbons:2')
|
||||||
|
|
||||||
|
def _handle_carbon_received(self, msg):
|
||||||
|
self.xmpp.event('carbon_received', msg)
|
||||||
|
|
||||||
|
def _handle_carbon_sent(self, msg):
|
||||||
|
self.xmpp.event('carbon_sent', msg)
|
||||||
|
|
||||||
|
def enable(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq.enable('carbon_enable')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def disable(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq.enable('carbon_disable')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
64
sleekxmpp/plugins/xep_0280/stanza.py
Normal file
64
sleekxmpp/plugins/xep_0280/stanza.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class ReceivedCarbon(ElementBase):
|
||||||
|
name = 'received'
|
||||||
|
namespace = 'urn:xmpp:carbons:2'
|
||||||
|
plugin_attrib = 'carbon_received'
|
||||||
|
interfaces = set(['carbon_received'])
|
||||||
|
is_extension = True
|
||||||
|
|
||||||
|
def get_carbon_received(self):
|
||||||
|
return self['forwarded']['stanza']
|
||||||
|
|
||||||
|
def del_carbon_received(self):
|
||||||
|
del self['forwarded']['stanza']
|
||||||
|
|
||||||
|
def set_carbon_received(self, stanza):
|
||||||
|
self['forwarded']['stanza'] = stanza
|
||||||
|
|
||||||
|
|
||||||
|
class SentCarbon(ElementBase):
|
||||||
|
name = 'sent'
|
||||||
|
namespace = 'urn:xmpp:carbons:2'
|
||||||
|
plugin_attrib = 'carbon_sent'
|
||||||
|
interfaces = set(['carbon_sent'])
|
||||||
|
is_extension = True
|
||||||
|
|
||||||
|
def get_carbon_sent(self):
|
||||||
|
return self['forwarded']['stanza']
|
||||||
|
|
||||||
|
def del_carbon_sent(self):
|
||||||
|
del self['forwarded']['stanza']
|
||||||
|
|
||||||
|
def set_carbon_sent(self, stanza):
|
||||||
|
self['forwarded']['stanza'] = stanza
|
||||||
|
|
||||||
|
|
||||||
|
class PrivateCarbon(ElementBase):
|
||||||
|
name = 'private'
|
||||||
|
namespace = 'urn:xmpp:carbons:2'
|
||||||
|
plugin_attrib = 'carbon_private'
|
||||||
|
interfaces = set()
|
||||||
|
|
||||||
|
|
||||||
|
class CarbonEnable(ElementBase):
|
||||||
|
name = 'enable'
|
||||||
|
namespace = 'urn:xmpp:carbons:2'
|
||||||
|
plugin_attrib = 'carbon_enable'
|
||||||
|
interfaces = set()
|
||||||
|
|
||||||
|
|
||||||
|
class CarbonDisable(ElementBase):
|
||||||
|
name = 'disable'
|
||||||
|
namespace = 'urn:xmpp:carbons:2'
|
||||||
|
plugin_attrib = 'carbon_disable'
|
||||||
|
interfaces = set()
|
||||||
16
sleekxmpp/plugins/xep_0297/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0297/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0297 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0297.stanza import Forwarded
|
||||||
|
from sleekxmpp.plugins.xep_0297.forwarded import XEP_0297
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0297)
|
||||||
64
sleekxmpp/plugins/xep_0297/forwarded.py
Normal file
64
sleekxmpp/plugins/xep_0297/forwarded.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp import Iq, Message, Presence
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.plugins.xep_0297 import stanza, Forwarded
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0297(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0297'
|
||||||
|
description = 'XEP-0297: Stanza Forwarding'
|
||||||
|
dependencies = set(['xep_0030', 'xep_0203'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Message, Forwarded)
|
||||||
|
|
||||||
|
# While these are marked as iterable, that is just for
|
||||||
|
# making it easier to extract the forwarded stanza. There
|
||||||
|
# still can be only a single forwarded stanza.
|
||||||
|
register_stanza_plugin(Forwarded, Message, iterable=True)
|
||||||
|
register_stanza_plugin(Forwarded, Presence, iterable=True)
|
||||||
|
register_stanza_plugin(Forwarded, Iq, iterable=True)
|
||||||
|
|
||||||
|
register_stanza_plugin(Forwarded, self.xmpp['xep_0203'].stanza.Delay)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Forwarded Stanza',
|
||||||
|
StanzaPath('message/forwarded'),
|
||||||
|
self._handle_forwarded))
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature('urn:xmpp:forward:0')
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:forward:0')
|
||||||
|
self.xmpp.remove_handler('Forwarded Stanza')
|
||||||
|
|
||||||
|
def forward(self, stanza=None, mto=None, mbody=None, mfrom=None, delay=None):
|
||||||
|
stanza.stream = None
|
||||||
|
|
||||||
|
msg = self.xmpp.Message()
|
||||||
|
msg['to'] = mto
|
||||||
|
msg['from'] = mfrom
|
||||||
|
msg['body'] = mbody
|
||||||
|
msg['forwarded']['stanza'] = stanza
|
||||||
|
if delay is not None:
|
||||||
|
msg['forwarded']['delay']['stamp'] = delay
|
||||||
|
msg.send()
|
||||||
|
|
||||||
|
def _handle_forwarded(self, msg):
|
||||||
|
self.xmpp.event('forwarded_stanza', msg)
|
||||||
36
sleekxmpp/plugins/xep_0297/stanza.py
Normal file
36
sleekxmpp/plugins/xep_0297/stanza.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Message, Presence, Iq
|
||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class Forwarded(ElementBase):
|
||||||
|
name = 'forwarded'
|
||||||
|
namespace = 'urn:xmpp:forward:0'
|
||||||
|
plugin_attrib = 'forwarded'
|
||||||
|
interfaces = set(['stanza'])
|
||||||
|
|
||||||
|
def get_stanza(self):
|
||||||
|
for stanza in self:
|
||||||
|
if isinstance(stanza, (Message, Presence, Iq)):
|
||||||
|
return stanza
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def set_stanza(self, value):
|
||||||
|
self.del_stanza()
|
||||||
|
self.append(value)
|
||||||
|
|
||||||
|
def del_stanza(self):
|
||||||
|
found_stanzas = []
|
||||||
|
for stanza in self:
|
||||||
|
if isinstance(stanza, (Message, Presence, Iq)):
|
||||||
|
found_stanzas.append(stanza)
|
||||||
|
for stanza in found_stanzas:
|
||||||
|
self.iterables.remove(stanza)
|
||||||
|
self.xml.remove(stanza.xml)
|
||||||
15
sleekxmpp/plugins/xep_0308/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0308/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0308.stanza import Replace
|
||||||
|
from sleekxmpp.plugins.xep_0308.correction import XEP_0308
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0308)
|
||||||
52
sleekxmpp/plugins/xep_0308/correction.py
Normal file
52
sleekxmpp/plugins/xep_0308/correction.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.stanza import Message
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.xep_0308 import stanza, Replace
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0308(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
XEP-0308 Last Message Correction
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0308'
|
||||||
|
description = 'XEP-0308: Last Message Correction'
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Message Correction',
|
||||||
|
StanzaPath('message/replace'),
|
||||||
|
self._handle_correction))
|
||||||
|
|
||||||
|
register_stanza_plugin(Message, Replace)
|
||||||
|
|
||||||
|
self.xmpp.use_message_ids = True
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('Message Correction')
|
||||||
|
self.xmpp.plugin['xep_0030'].del_feature(feature=Replace.namespace)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp.plugin['xep_0030'].add_feature(Replace.namespace)
|
||||||
|
|
||||||
|
def _handle_correction(self, msg):
|
||||||
|
self.xmpp.event('message_correction', msg)
|
||||||
16
sleekxmpp/plugins/xep_0308/stanza.py
Normal file
16
sleekxmpp/plugins/xep_0308/stanza.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class Replace(ElementBase):
|
||||||
|
name = 'replace'
|
||||||
|
namespace = 'urn:xmpp:message-correct:0'
|
||||||
|
plugin_attrib = 'replace'
|
||||||
|
interfaces = set(['id'])
|
||||||
15
sleekxmpp/plugins/xep_0313/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0313/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0313.stanza import Result, MAM, Preferences
|
||||||
|
from sleekxmpp.plugins.xep_0313.mam import XEP_0313
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0313)
|
||||||
92
sleekxmpp/plugins/xep_0313/mam.py
Normal file
92
sleekxmpp/plugins/xep_0313/mam.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.stanza import Message, Iq
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
from sleekxmpp.xmlstream.handler import Collector
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.xep_0313 import stanza
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0313(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
XEP-0313 Message Archive Management
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0313'
|
||||||
|
description = 'XEP-0313: Message Archive Management'
|
||||||
|
dependencies = set(['xep_0030', 'xep_0050', 'xep_0059', 'xep_0297'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, stanza.MAM)
|
||||||
|
register_stanza_plugin(Iq, stanza.Preferences)
|
||||||
|
register_stanza_plugin(Message, stanza.Result)
|
||||||
|
register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
|
||||||
|
|
||||||
|
def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None,
|
||||||
|
block=True, timeout=None, callback=None, iterator=False):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
query_id = iq['id']
|
||||||
|
|
||||||
|
iq['to'] = jid
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['mam']['queryid'] = query_id
|
||||||
|
iq['mam']['start'] = start
|
||||||
|
iq['mam']['end'] = end
|
||||||
|
iq['mam']['with'] = with_jid
|
||||||
|
|
||||||
|
collector = Collector(
|
||||||
|
'MAM_Results_%s' % query_id,
|
||||||
|
StanzaPath('message/mam_result@queryid=%s' % query_id))
|
||||||
|
self.xmpp.register_handler(collector)
|
||||||
|
|
||||||
|
if iterator:
|
||||||
|
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results')
|
||||||
|
elif not block and callback is not None:
|
||||||
|
def wrapped_cb(iq):
|
||||||
|
results = collector.stop()
|
||||||
|
if iq['type'] == 'result':
|
||||||
|
iq['mam']['results'] = results
|
||||||
|
callback(iq)
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
resp = iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
resp['mam']['results'] = collector.stop()
|
||||||
|
return resp
|
||||||
|
except XMPPError as e:
|
||||||
|
collector.stop()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def set_preferences(self, jid=None, default=None, always=None, never=None,
|
||||||
|
ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['to'] = jid
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['mam_prefs']['default'] = default
|
||||||
|
iq['mam_prefs']['always'] = always
|
||||||
|
iq['mam_prefs']['never'] = never
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def get_configuration_commands(self, jid, **kwargs):
|
||||||
|
return self.xmpp['xep_0030'].get_items(
|
||||||
|
jid=jid,
|
||||||
|
node='urn:xmpp:mam#configure',
|
||||||
|
**kwargs)
|
||||||
131
sleekxmpp/plugins/xep_0313/stanza.py
Normal file
131
sleekxmpp/plugins/xep_0313/stanza.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
from sleekxmpp.jid import JID
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, ET
|
||||||
|
from sleekxmpp.plugins import xep_0082
|
||||||
|
|
||||||
|
|
||||||
|
class MAM(ElementBase):
|
||||||
|
name = 'query'
|
||||||
|
namespace = 'urn:xmpp:mam:tmp'
|
||||||
|
plugin_attrib = 'mam'
|
||||||
|
interfaces = set(['queryid', 'start', 'end', 'with', 'results'])
|
||||||
|
sub_interfaces = set(['start', 'end', 'with'])
|
||||||
|
|
||||||
|
def setup(self, xml=None):
|
||||||
|
ElementBase.setup(self, xml)
|
||||||
|
self._results = []
|
||||||
|
|
||||||
|
def get_start(self):
|
||||||
|
timestamp = self._get_attr('start')
|
||||||
|
return xep_0082.parse(timestamp)
|
||||||
|
|
||||||
|
def set_start(self, value):
|
||||||
|
if isinstance(value, dt.datetime):
|
||||||
|
value = xep_0082.format_datetime(value)
|
||||||
|
self._set_attr('start', value)
|
||||||
|
|
||||||
|
def get_end(self):
|
||||||
|
timestamp = self._get_sub_text('end')
|
||||||
|
return xep_0082.parse(timestamp)
|
||||||
|
|
||||||
|
def set_end(self, value):
|
||||||
|
if isinstance(value, dt.datetime):
|
||||||
|
value = xep_0082.format_datetime(value)
|
||||||
|
self._set_sub_text('end', value)
|
||||||
|
|
||||||
|
def get_with(self):
|
||||||
|
return JID(self._get_sub_text('with'))
|
||||||
|
|
||||||
|
def set_with(self, value):
|
||||||
|
self._set_sub_text('with', str(value))
|
||||||
|
|
||||||
|
# The results interface is meant only as an easy
|
||||||
|
# way to access the set of collected message responses
|
||||||
|
# from the query.
|
||||||
|
|
||||||
|
def get_results(self):
|
||||||
|
return self._results
|
||||||
|
|
||||||
|
def set_results(self, values):
|
||||||
|
self._results = values
|
||||||
|
|
||||||
|
def del_results(self):
|
||||||
|
self._results = []
|
||||||
|
|
||||||
|
|
||||||
|
class Preferences(ElementBase):
|
||||||
|
name = 'prefs'
|
||||||
|
namespace = 'urn:xmpp:mam:tmp'
|
||||||
|
plugin_attrib = 'mam_prefs'
|
||||||
|
interfaces = set(['default', 'always', 'never'])
|
||||||
|
sub_interfaces = set(['always', 'never'])
|
||||||
|
|
||||||
|
def get_always(self):
|
||||||
|
results = set()
|
||||||
|
|
||||||
|
jids = self.xml.findall('{%s}always/{%s}jid' % (
|
||||||
|
self.namespace, self.namespace))
|
||||||
|
|
||||||
|
for jid in jids:
|
||||||
|
results.add(JID(jid.text))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def set_always(self, value):
|
||||||
|
self._set_sub_text('always', '', keep=True)
|
||||||
|
always = self.xml.find('{%s}always' % self.namespace)
|
||||||
|
always.clear()
|
||||||
|
|
||||||
|
if not isinstance(value, (list, set)):
|
||||||
|
value = [value]
|
||||||
|
|
||||||
|
for jid in value:
|
||||||
|
jid_xml = ET.Element('{%s}jid' % self.namespace)
|
||||||
|
jid_xml.text = str(jid)
|
||||||
|
always.append(jid_xml)
|
||||||
|
|
||||||
|
def get_never(self):
|
||||||
|
results = set()
|
||||||
|
|
||||||
|
jids = self.xml.findall('{%s}never/{%s}jid' % (
|
||||||
|
self.namespace, self.namespace))
|
||||||
|
|
||||||
|
for jid in jids:
|
||||||
|
results.add(JID(jid.text))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def set_never(self, value):
|
||||||
|
self._set_sub_text('never', '', keep=True)
|
||||||
|
never = self.xml.find('{%s}never' % self.namespace)
|
||||||
|
never.clear()
|
||||||
|
|
||||||
|
if not isinstance(value, (list, set)):
|
||||||
|
value = [value]
|
||||||
|
|
||||||
|
for jid in value:
|
||||||
|
jid_xml = ET.Element('{%s}jid' % self.namespace)
|
||||||
|
jid_xml.text = str(jid)
|
||||||
|
never.append(jid_xml)
|
||||||
|
|
||||||
|
|
||||||
|
class Result(ElementBase):
|
||||||
|
name = 'result'
|
||||||
|
namespace = 'urn:xmpp:mam:tmp'
|
||||||
|
plugin_attrib = 'mam_result'
|
||||||
|
interfaces = set(['forwarded', 'queryid', 'id'])
|
||||||
|
|
||||||
|
def get_forwarded(self):
|
||||||
|
return self.parent()['forwarded']
|
||||||
|
|
||||||
|
def del_forwarded(self):
|
||||||
|
del self.parent()['forwarded']
|
||||||
@@ -479,11 +479,11 @@ class RosterItem(object):
|
|||||||
self.xmpp.event('roster_subscription_removed', presence)
|
self.xmpp.event('roster_subscription_removed', presence)
|
||||||
|
|
||||||
def handle_probe(self, presence):
|
def handle_probe(self, presence):
|
||||||
if self['to']:
|
if self['from']:
|
||||||
self.send_last_presence()
|
self.send_last_presence()
|
||||||
if self['pending_out']:
|
if self['pending_out']:
|
||||||
self.subscribe()
|
self.subscribe()
|
||||||
if not self['to']:
|
if not self['from']:
|
||||||
self._unsubscribed()
|
self._unsubscribed()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ class Iq(RootStanza):
|
|||||||
StanzaBase.reply(self, clear)
|
StanzaBase.reply(self, clear)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def send(self, block=True, timeout=None, callback=None, now=False):
|
def send(self, block=True, timeout=None, callback=None, now=False, timeout_callback=None):
|
||||||
"""
|
"""
|
||||||
Send an <iq> stanza over the XML stream.
|
Send an <iq> stanza over the XML stream.
|
||||||
|
|
||||||
@@ -181,15 +181,32 @@ class Iq(RootStanza):
|
|||||||
now -- Indicates if the send queue should be skipped and send
|
now -- Indicates if the send queue should be skipped and send
|
||||||
the stanza immediately. Used during stream
|
the stanza immediately. Used during stream
|
||||||
initialization. Defaults to False.
|
initialization. Defaults to False.
|
||||||
|
timeout_callback -- Optional reference to a stream handler function.
|
||||||
|
Will be executed when the timeout expires before a
|
||||||
|
response has been received with the originally-sent IQ
|
||||||
|
stanza. Only called if there is a callback parameter
|
||||||
|
(and therefore are in async mode).
|
||||||
"""
|
"""
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
timeout = self.stream.response_timeout
|
timeout = self.stream.response_timeout
|
||||||
if callback is not None and self['type'] in ('get', 'set'):
|
if callback is not None and self['type'] in ('get', 'set'):
|
||||||
handler_name = 'IqCallback_%s' % self['id']
|
handler_name = 'IqCallback_%s' % self['id']
|
||||||
handler = Callback(handler_name,
|
if timeout_callback:
|
||||||
MatcherId(self['id']),
|
self.callback = callback
|
||||||
callback,
|
self.timeout_callback = timeout_callback
|
||||||
once=True)
|
self.stream.schedule('IqTimeout_%s' % self['id'],
|
||||||
|
timeout,
|
||||||
|
self._fire_timeout,
|
||||||
|
repeat=False)
|
||||||
|
handler = Callback(handler_name,
|
||||||
|
MatcherId(self['id']),
|
||||||
|
self._handle_result,
|
||||||
|
once=True)
|
||||||
|
else:
|
||||||
|
handler = Callback(handler_name,
|
||||||
|
MatcherId(self['id']),
|
||||||
|
callback,
|
||||||
|
once=True)
|
||||||
self.stream.register_handler(handler)
|
self.stream.register_handler(handler)
|
||||||
StanzaBase.send(self, now=now)
|
StanzaBase.send(self, now=now)
|
||||||
return handler_name
|
return handler_name
|
||||||
@@ -206,6 +223,16 @@ class Iq(RootStanza):
|
|||||||
else:
|
else:
|
||||||
return StanzaBase.send(self, now=now)
|
return StanzaBase.send(self, now=now)
|
||||||
|
|
||||||
|
def _handle_result(self, iq):
|
||||||
|
# we got the IQ, so don't fire the timeout
|
||||||
|
self.stream.scheduler.remove('IqTimeout_%s' % self['id'])
|
||||||
|
self.callback(iq)
|
||||||
|
|
||||||
|
def _fire_timeout(self):
|
||||||
|
# don't fire the handler for the IQ, if it finally does come in
|
||||||
|
self.stream.remove_handler('IqCallback_%s' % self['id'])
|
||||||
|
self.timeout_callback(self)
|
||||||
|
|
||||||
def _set_stanza_values(self, values):
|
def _set_stanza_values(self, values):
|
||||||
"""
|
"""
|
||||||
Set multiple stanza interface values using a dictionary.
|
Set multiple stanza interface values using a dictionary.
|
||||||
|
|||||||
@@ -63,6 +63,17 @@ class Message(RootStanza):
|
|||||||
lang_interfaces = sub_interfaces
|
lang_interfaces = sub_interfaces
|
||||||
types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
|
types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize a new <message /> stanza with an optional 'id' value.
|
||||||
|
|
||||||
|
Overrides StanzaBase.__init__.
|
||||||
|
"""
|
||||||
|
StanzaBase.__init__(self, *args, **kwargs)
|
||||||
|
if self['id'] == '':
|
||||||
|
if self.stream is not None and self.stream.use_message_ids:
|
||||||
|
self['id'] = self.stream.new_id()
|
||||||
|
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
"""
|
"""
|
||||||
Return the message type.
|
Return the message type.
|
||||||
|
|||||||
@@ -72,6 +72,17 @@ class Presence(RootStanza):
|
|||||||
'subscribed', 'unsubscribe', 'unsubscribed'])
|
'subscribed', 'unsubscribe', 'unsubscribed'])
|
||||||
showtypes = set(['dnd', 'chat', 'xa', 'away'])
|
showtypes = set(['dnd', 'chat', 'xa', 'away'])
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize a new <presence /> stanza with an optional 'id' value.
|
||||||
|
|
||||||
|
Overrides StanzaBase.__init__.
|
||||||
|
"""
|
||||||
|
StanzaBase.__init__(self, *args, **kwargs)
|
||||||
|
if self['id'] == '':
|
||||||
|
if self.stream is not None and self.stream.use_presence_ids:
|
||||||
|
self['id'] = self.stream.new_id()
|
||||||
|
|
||||||
def exception(self, e):
|
def exception(self, e):
|
||||||
"""
|
"""
|
||||||
Override exception passback for presence.
|
Override exception passback for presence.
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ class RosterItem(ElementBase):
|
|||||||
def get_groups(self):
|
def get_groups(self):
|
||||||
groups = []
|
groups = []
|
||||||
for group in self.xml.findall('{%s}group' % self.namespace):
|
for group in self.xml.findall('{%s}group' % self.namespace):
|
||||||
groups.append(group.text)
|
if group.text:
|
||||||
|
groups.append(group.text)
|
||||||
|
else:
|
||||||
|
groups.append('')
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
def set_groups(self, values):
|
def set_groups(self, values):
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user