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/
|
||||
.coverage
|
||||
sleekxmpp.egg-info/
|
||||
.ropeproject/
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -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
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
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
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
|
||||
.. warning::
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ from stanza import Action
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ from stanza import Action
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import getpass
|
||||
from optparse import OptionParser
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||
|
||||
|
||||
# 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
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
@@ -83,6 +84,7 @@ class Disco(sleekxmpp.ClientXMPP):
|
||||
self.get_roster()
|
||||
self.send_presence()
|
||||
|
||||
try:
|
||||
if self.get in self.info_types:
|
||||
# By using block=True, the result stanza will be
|
||||
# returned. Execution will block until the reply is
|
||||
@@ -92,7 +94,7 @@ class Disco(sleekxmpp.ClientXMPP):
|
||||
info = self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
block=True)
|
||||
if self.get in self.items_types:
|
||||
elif self.get in self.items_types:
|
||||
# The same applies from above. Listen for the
|
||||
# disco_items event or pass a callback function
|
||||
# if you need to process a non-blocking request.
|
||||
@@ -101,9 +103,12 @@ class Disco(sleekxmpp.ClientXMPP):
|
||||
block=True)
|
||||
else:
|
||||
logging.error("Invalid disco request type.")
|
||||
self.disconnect()
|
||||
return
|
||||
|
||||
except IqError as e:
|
||||
logging.error("Entity returned an error: %s" % e.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
logging.error("No response received.")
|
||||
else:
|
||||
header = 'XMPP Service Discovery: %s' % self.target_jid
|
||||
print(header)
|
||||
print('-' * len(header))
|
||||
@@ -125,7 +130,7 @@ class Disco(sleekxmpp.ClientXMPP):
|
||||
print('Items:')
|
||||
for item in items['disco_items']['items']:
|
||||
print(' - %s' % str(item))
|
||||
|
||||
finally:
|
||||
self.disconnect()
|
||||
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ from sleekxmpp.exceptions import XMPPError
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ from sleekxmpp.componentxmpp import ComponentXMPP
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ from sleekxmpp.xmlstream import cert
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
@@ -85,7 +85,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
|
||||
def stream_opened(self, stream):
|
||||
# NOTE: IBB streams are bi-directional, so the original sender is
|
||||
# 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(),
|
||||
# or use the ibb_stream_data event.
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ from sleekxmpp.xmlstream import ET, tostring
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ from sleekxmpp.xmlstream.handler import Callback
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import sleekxmpp
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ from sleekxmpp.exceptions import XMPPError
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ from sleekxmpp.xmlstream import JID
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
16
setup.py
16
setup.py
@@ -50,6 +50,7 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/test',
|
||||
'sleekxmpp/roster',
|
||||
'sleekxmpp/util',
|
||||
'sleekxmpp/util/sasl',
|
||||
'sleekxmpp/xmlstream',
|
||||
'sleekxmpp/xmlstream/matcher',
|
||||
'sleekxmpp/xmlstream/handler',
|
||||
@@ -59,16 +60,20 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins/xep_0009',
|
||||
'sleekxmpp/plugins/xep_0009/stanza',
|
||||
'sleekxmpp/plugins/xep_0012',
|
||||
'sleekxmpp/plugins/xep_0013',
|
||||
'sleekxmpp/plugins/xep_0016',
|
||||
'sleekxmpp/plugins/xep_0027',
|
||||
'sleekxmpp/plugins/xep_0030',
|
||||
'sleekxmpp/plugins/xep_0030/stanza',
|
||||
'sleekxmpp/plugins/xep_0033',
|
||||
'sleekxmpp/plugins/xep_0047',
|
||||
'sleekxmpp/plugins/xep_0049',
|
||||
'sleekxmpp/plugins/xep_0050',
|
||||
'sleekxmpp/plugins/xep_0054',
|
||||
'sleekxmpp/plugins/xep_0059',
|
||||
'sleekxmpp/plugins/xep_0060',
|
||||
'sleekxmpp/plugins/xep_0060/stanza',
|
||||
'sleekxmpp/plugins/xep_0065',
|
||||
'sleekxmpp/plugins/xep_0066',
|
||||
'sleekxmpp/plugins/xep_0077',
|
||||
'sleekxmpp/plugins/xep_0078',
|
||||
@@ -76,6 +81,7 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins/xep_0084',
|
||||
'sleekxmpp/plugins/xep_0085',
|
||||
'sleekxmpp/plugins/xep_0086',
|
||||
'sleekxmpp/plugins/xep_0091',
|
||||
'sleekxmpp/plugins/xep_0092',
|
||||
'sleekxmpp/plugins/xep_0107',
|
||||
'sleekxmpp/plugins/xep_0108',
|
||||
@@ -95,8 +101,15 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins/xep_0221',
|
||||
'sleekxmpp/plugins/xep_0224',
|
||||
'sleekxmpp/plugins/xep_0231',
|
||||
'sleekxmpp/plugins/xep_0235',
|
||||
'sleekxmpp/plugins/xep_0249',
|
||||
'sleekxmpp/plugins/xep_0257',
|
||||
'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/feature_mechanisms',
|
||||
'sleekxmpp/features/feature_mechanisms/stanza',
|
||||
@@ -104,9 +117,8 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/features/feature_bind',
|
||||
'sleekxmpp/features/feature_session',
|
||||
'sleekxmpp/features/feature_rosterver',
|
||||
'sleekxmpp/features/feature_preapproval',
|
||||
'sleekxmpp/thirdparty',
|
||||
'sleekxmpp/thirdparty/suelta',
|
||||
'sleekxmpp/thirdparty/suelta/mechanisms',
|
||||
]
|
||||
|
||||
setup(
|
||||
|
||||
@@ -43,8 +43,8 @@ log = logging.getLogger(__name__)
|
||||
# In order to make sure that Unicode is handled properly
|
||||
# in Python 2.x, reset the default encoding.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||
setdefaultencoding('utf8')
|
||||
|
||||
|
||||
class BaseXMPP(XMLStream):
|
||||
@@ -68,8 +68,13 @@ class BaseXMPP(XMLStream):
|
||||
#: An identifier for the stream as given by the server.
|
||||
self.stream_id = None
|
||||
|
||||
#: The JabberID (JID) used by this connection.
|
||||
self.boundjid = JID(jid)
|
||||
#: The JabberID (JID) requested for this connection.
|
||||
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._redirect_attempts = 0
|
||||
@@ -109,6 +114,17 @@ class BaseXMPP(XMLStream):
|
||||
#: ``'to'`` and ``'from'`` JIDs of stanzas.
|
||||
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
|
||||
#: JID+node combinations. Each callback in the registry is
|
||||
#: marked with:
|
||||
@@ -196,6 +212,10 @@ class BaseXMPP(XMLStream):
|
||||
self.stream_version = xml.get('version', '')
|
||||
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):
|
||||
"""Initialize plugins and begin processing the XML stream.
|
||||
|
||||
@@ -221,13 +241,6 @@ class BaseXMPP(XMLStream):
|
||||
- The send queue processor
|
||||
- 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:
|
||||
if not hasattr(self.plugin[name], 'post_inited'):
|
||||
if hasattr(self.plugin[name], 'post_init'):
|
||||
@@ -652,7 +665,7 @@ class BaseXMPP(XMLStream):
|
||||
def set_jid(self, jid):
|
||||
"""Rip a JID apart and claim it as our own."""
|
||||
log.debug("setting jid to %s", jid)
|
||||
self.boundjid.full = jid
|
||||
self.boundjid = JID(jid, cache_lock=True)
|
||||
|
||||
def getjidresource(self, fulljid):
|
||||
if '/' in fulljid:
|
||||
@@ -733,6 +746,8 @@ class BaseXMPP(XMLStream):
|
||||
item = self.roster[pres['to']][pres['from']]
|
||||
if item['whitelisted']:
|
||||
item.authorize()
|
||||
if roster.auto_subscribe:
|
||||
item.subscribe()
|
||||
elif roster.auto_authorize:
|
||||
item.authorize()
|
||||
if roster.auto_subscribe:
|
||||
|
||||
@@ -64,7 +64,6 @@ class ClientXMPP(BaseXMPP):
|
||||
escape_quotes=True, sasl_mech=None, lang='en'):
|
||||
BaseXMPP.__init__(self, jid, 'jabber:client')
|
||||
|
||||
self.set_jid(jid)
|
||||
self.escape_quotes = escape_quotes
|
||||
self.plugin_config = plugin_config
|
||||
self.plugin_whitelist = plugin_whitelist
|
||||
@@ -95,7 +94,7 @@ class ClientXMPP(BaseXMPP):
|
||||
self.bound = 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.register_stanza(StreamFeatures)
|
||||
@@ -113,9 +112,10 @@ class ClientXMPP(BaseXMPP):
|
||||
self.register_plugin('feature_starttls')
|
||||
self.register_plugin('feature_bind')
|
||||
self.register_plugin('feature_session')
|
||||
self.register_plugin('feature_rosterver')
|
||||
self.register_plugin('feature_preapproval')
|
||||
self.register_plugin('feature_mechanisms',
|
||||
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
|
||||
self.register_plugin('feature_rosterver')
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
@@ -252,7 +252,7 @@ class ClientXMPP(BaseXMPP):
|
||||
self._handle_roster(response)
|
||||
return response
|
||||
|
||||
def _handle_connected(self, event=None):
|
||||
def _reset_connection_state(self, event=None):
|
||||
#TODO: Use stream state here
|
||||
self.authenticated = False
|
||||
self.sessionstarted = False
|
||||
@@ -272,6 +272,8 @@ class ClientXMPP(BaseXMPP):
|
||||
# Don't continue if the feature requires
|
||||
# restarting the XML stream.
|
||||
return True
|
||||
log.debug('Finished processing stream features.')
|
||||
self.event('stream_negotiated')
|
||||
|
||||
def _handle_roster(self, iq):
|
||||
"""Update the roster after receiving a roster stanza.
|
||||
@@ -286,12 +288,14 @@ class ClientXMPP(BaseXMPP):
|
||||
if iq['roster']['ver']:
|
||||
roster.version = iq['roster']['ver']
|
||||
items = iq['roster']['items']
|
||||
for jid in items:
|
||||
item = items[jid]
|
||||
|
||||
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]['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'))
|
||||
|
||||
@@ -11,5 +11,6 @@ __all__ = [
|
||||
'feature_mechanisms',
|
||||
'feature_bind',
|
||||
'feature_session',
|
||||
'feature_rosterver'
|
||||
'feature_rosterver',
|
||||
'feature_preapproval'
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
from sleekxmpp.jid import JID
|
||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
||||
from sleekxmpp.features.feature_bind import stanza
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
@@ -48,7 +49,7 @@ class FeatureBind(BasePlugin):
|
||||
iq['bind']['resource'] = self.xmpp.boundjid.resource
|
||||
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.event('session_bind', self.xmpp.boundjid, direct=True)
|
||||
self.xmpp.session_bind_event.set()
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import ssl
|
||||
import logging
|
||||
|
||||
from sleekxmpp.thirdparty import suelta
|
||||
from sleekxmpp.thirdparty.suelta.exceptions import SASLCancelled, SASLError
|
||||
from sleekxmpp.thirdparty.suelta.exceptions import SASLPrepFailure
|
||||
|
||||
from sleekxmpp.util import sasl
|
||||
from sleekxmpp.util.stringprep_profiles import StringPrepError
|
||||
from sleekxmpp.stanza import StreamFeatures
|
||||
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
@@ -31,42 +31,29 @@ class FeatureMechanisms(BasePlugin):
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'use_mech': None,
|
||||
'use_mechs': None,
|
||||
'min_mech': None,
|
||||
'sasl_callback': None,
|
||||
'security_callback': None,
|
||||
'encrypted_plain': True,
|
||||
'unencrypted_plain': False,
|
||||
'unencrypted_digest': False,
|
||||
'unencrypted_cram': False,
|
||||
'unencrypted_scram': True,
|
||||
'order': 100
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
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:
|
||||
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.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.attempted_mechs = set()
|
||||
|
||||
@@ -99,6 +86,51 @@ class FeatureMechanisms(BasePlugin):
|
||||
restart=True,
|
||||
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):
|
||||
"""
|
||||
Handle authenticating using SASL.
|
||||
@@ -111,37 +143,61 @@ class FeatureMechanisms(BasePlugin):
|
||||
# server has incorrectly offered it again.
|
||||
return False
|
||||
|
||||
if not self.use_mech:
|
||||
enforce_limit = False
|
||||
limited_mechs = self.use_mechs
|
||||
|
||||
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'])
|
||||
else:
|
||||
self.mech_list = set([self.use_mech])
|
||||
|
||||
return self._send_auth()
|
||||
|
||||
def _send_auth(self):
|
||||
mech_list = self.mech_list - self.attempted_mechs
|
||||
self.mech = self.sasl.choose_mechanism(mech_list)
|
||||
try:
|
||||
self.mech = sasl.choose(mech_list,
|
||||
self.sasl_callback,
|
||||
self.security_callback,
|
||||
limit=self.use_mechs,
|
||||
min_mech=self.min_mech)
|
||||
except sasl.SASLNoAppropriateMechanism:
|
||||
log.error("No appropriate login method.")
|
||||
self.xmpp.event("no_auth", direct=True)
|
||||
self.attempted_mechs = set()
|
||||
return self.xmpp.disconnect()
|
||||
|
||||
if mech_list and self.mech is not None:
|
||||
resp = stanza.Auth(self.xmpp)
|
||||
resp['mechanism'] = self.mech.name
|
||||
try:
|
||||
resp['value'] = self.mech.process()
|
||||
except SASLCancelled:
|
||||
except sasl.SASLCancelled:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except SASLError:
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except SASLPrepFailure:
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except StringPrepError:
|
||||
log.exception("A credential value did not pass SASLprep.")
|
||||
self.xmpp.disconnect()
|
||||
else:
|
||||
resp.send(now=True)
|
||||
else:
|
||||
log.error("No appropriate login method.")
|
||||
self.xmpp.event("no_auth", direct=True)
|
||||
self.attempted_mechs = set()
|
||||
self.xmpp.disconnect()
|
||||
|
||||
return True
|
||||
|
||||
def _handle_challenge(self, stanza):
|
||||
@@ -149,15 +205,28 @@ class FeatureMechanisms(BasePlugin):
|
||||
resp = self.stanza.Response(self.xmpp)
|
||||
try:
|
||||
resp['value'] = self.mech.process(stanza['value'])
|
||||
except SASLCancelled:
|
||||
except sasl.SASLCancelled:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except SASLError:
|
||||
except sasl.SASLFailed:
|
||||
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:
|
||||
resp.send(now=True)
|
||||
|
||||
def _handle_success(self, stanza):
|
||||
"""SASL authentication succeeded. Restart the stream."""
|
||||
try:
|
||||
final = self.mech.process(stanza['value'])
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"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')
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
import base64
|
||||
|
||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
||||
|
||||
from sleekxmpp.util import bytes
|
||||
from sleekxmpp.xmlstream import StanzaBase
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
import base64
|
||||
|
||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
||||
|
||||
from sleekxmpp.util import bytes
|
||||
from sleekxmpp.xmlstream import StanzaBase
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
import base64
|
||||
|
||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
||||
|
||||
from sleekxmpp.util import bytes
|
||||
from sleekxmpp.xmlstream import StanzaBase
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
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):
|
||||
|
||||
@@ -16,9 +18,21 @@ class Success(StanzaBase):
|
||||
|
||||
name = 'success'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||
interfaces = set()
|
||||
interfaces = set(['value'])
|
||||
plugin_attrib = name
|
||||
|
||||
def setup(self, xml):
|
||||
StanzaBase.setup(self, xml)
|
||||
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
|
||||
elif not self.xmpp.use_tls:
|
||||
return False
|
||||
elif self.xmpp.ssl_support:
|
||||
else:
|
||||
self.xmpp.send(features['starttls'], now=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):
|
||||
"""Restart the XML stream when TLS is accepted."""
|
||||
|
||||
@@ -16,9 +16,11 @@ from __future__ import unicode_literals
|
||||
import re
|
||||
import socket
|
||||
import stringprep
|
||||
import threading
|
||||
import encodings.idna
|
||||
|
||||
from sleekxmpp.util import stringprep_profiles
|
||||
from sleekxmpp.thirdparty import OrderedDict
|
||||
|
||||
#: 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' + \
|
||||
@@ -63,6 +65,24 @@ JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
|
||||
'\\40': '@',
|
||||
'\\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
|
||||
#: The nodeprep profile of stringprep used to validate the local,
|
||||
@@ -72,7 +92,7 @@ nodeprep = stringprep_profiles.create(
|
||||
bidi=True,
|
||||
mappings=[
|
||||
stringprep_profiles.b1_mapping,
|
||||
stringprep_profiles.c12_mapping],
|
||||
stringprep.map_table_b2],
|
||||
prohibited=[
|
||||
stringprep.in_table_c11,
|
||||
stringprep.in_table_c12,
|
||||
@@ -412,29 +432,48 @@ class JID(object):
|
||||
|
||||
# pylint: disable=W0212
|
||||
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 == '':
|
||||
jid = (None, None, None)
|
||||
# only check cache if there is a jid string, or parts, not if there
|
||||
# are both
|
||||
self._jid = None
|
||||
key = None
|
||||
if (jid is not None) and (parts is None):
|
||||
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):
|
||||
jid = _parse_jid(jid)
|
||||
parsed_jid = _parse_jid(jid)
|
||||
else:
|
||||
jid = jid._jid
|
||||
parsed_jid = jid._jid
|
||||
|
||||
local, domain, resource = jid
|
||||
|
||||
local = kwargs.get('local', local)
|
||||
domain = kwargs.get('domain', domain)
|
||||
resource = kwargs.get('resource', resource)
|
||||
local, domain, resource = parsed_jid
|
||||
|
||||
if 'local' in kwargs:
|
||||
local = _escape_node(local)
|
||||
local = _escape_node(in_local)
|
||||
if 'domain' in kwargs:
|
||||
domain = _validate_domain(domain)
|
||||
domain = _validate_domain(in_domain)
|
||||
if 'resource' in kwargs:
|
||||
resource = _validate_resource(resource)
|
||||
resource = _validate_resource(in_resource)
|
||||
|
||||
self._jid = (local, domain, resource)
|
||||
if key:
|
||||
_cache(key, self._jid, locked)
|
||||
|
||||
def unescape(self):
|
||||
"""Return an unescaped JID object.
|
||||
@@ -498,7 +537,9 @@ class JID(object):
|
||||
``resource``, ``full``, ``jid``, or ``bare``.
|
||||
: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
|
||||
elif name in ('user', 'username', 'local', 'node'):
|
||||
self._jid = JID(self, local=value)._jid
|
||||
@@ -509,8 +550,6 @@ class JID(object):
|
||||
elif name == 'bare':
|
||||
parsed = JID(value)._jid
|
||||
self._jid = (parsed[0], parsed[1], self._jid[2])
|
||||
elif name == '_jid':
|
||||
super(JID, self).__setattr__('_jid', value)
|
||||
|
||||
def __str__(self):
|
||||
"""Use the full JID as the string value."""
|
||||
|
||||
@@ -18,15 +18,19 @@ __all__ = [
|
||||
'xep_0004', # Data Forms
|
||||
'xep_0009', # Jabber-RPC
|
||||
'xep_0012', # Last Activity
|
||||
'xep_0013', # Flexible Offline Message Retrieval
|
||||
'xep_0016', # Privacy Lists
|
||||
'xep_0027', # Current Jabber OpenPGP Usage
|
||||
'xep_0030', # Service Discovery
|
||||
'xep_0033', # Extended Stanza Addresses
|
||||
'xep_0045', # Multi-User Chat (Client)
|
||||
'xep_0047', # In-Band Bytestreams
|
||||
'xep_0049', # Private XML Storage
|
||||
'xep_0050', # Ad-hoc Commands
|
||||
'xep_0054', # vcard-temp
|
||||
'xep_0059', # Result Set Management
|
||||
'xep_0060', # Pubsub (Client)
|
||||
'xep_0065', # SOCKS5 Bytestreams
|
||||
'xep_0066', # Out of Band Data
|
||||
'xep_0077', # In-Band Registration
|
||||
# 'xep_0078', # Non-SASL auth. Don't automatically load
|
||||
@@ -35,6 +39,7 @@ __all__ = [
|
||||
'xep_0084', # User Avatar
|
||||
'xep_0085', # Chat State Notifications
|
||||
'xep_0086', # Legacy Error Codes
|
||||
'xep_0091', # Legacy Delayed Delivery
|
||||
'xep_0092', # Software Version
|
||||
'xep_0106', # JID Escaping
|
||||
'xep_0107', # User Mood
|
||||
@@ -59,9 +64,17 @@ __all__ = [
|
||||
'xep_0223', # Persistent Storage of Private Data via Pubsub
|
||||
'xep_0224', # Attention
|
||||
'xep_0231', # Bits of Binary
|
||||
'xep_0235', # OAuth Over XMPP
|
||||
'xep_0242', # XMPP Client Compliance 2009
|
||||
'xep_0249', # Direct MUC Invitations
|
||||
'xep_0256', # Last Activity in Presence
|
||||
'xep_0257', # Client Certificate Management for SASL EXTERNAL
|
||||
'xep_0258', # Security Labels in XMPP
|
||||
'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_0308', # Last Message Correction
|
||||
'xep_0313', # Message Archive Management
|
||||
]
|
||||
|
||||
@@ -201,6 +201,7 @@ class Form(ElementBase):
|
||||
del self['instructions']
|
||||
if instructions in [None, '']:
|
||||
return
|
||||
if not isinstance(instructions, list):
|
||||
instructions = instructions.split('\n')
|
||||
for instruction in instructions:
|
||||
inst = ET.Element('{%s}instructions' % self.namespace)
|
||||
|
||||
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}
|
||||
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):
|
||||
"""
|
||||
Retrieve the disco#info results from a given JID/node combination.
|
||||
@@ -324,7 +324,10 @@ class XEP_0030(BasePlugin):
|
||||
callback -- Optional callback to execute when a reply is
|
||||
received instead of blocking and waiting for
|
||||
the reply.
|
||||
timeout_callback -- Optional callback to execute when no result
|
||||
has been received in timeout seconds.
|
||||
"""
|
||||
if local is None:
|
||||
if jid is not None and not isinstance(jid, JID):
|
||||
jid = JID(jid)
|
||||
if self.xmpp.is_component:
|
||||
@@ -363,7 +366,8 @@ class XEP_0030(BasePlugin):
|
||||
iq['disco_info']['node'] = node if node else ''
|
||||
return iq.send(timeout=kwargs.get('timeout', None),
|
||||
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):
|
||||
"""
|
||||
@@ -404,8 +408,10 @@ class XEP_0030(BasePlugin):
|
||||
iterator -- If True, return a result set iterator using
|
||||
the XEP-0059 plugin, if the plugin is loaded.
|
||||
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,
|
||||
kwargs.get('ifrom', None),
|
||||
kwargs)
|
||||
@@ -422,7 +428,8 @@ class XEP_0030(BasePlugin):
|
||||
else:
|
||||
return iq.send(timeout=kwargs.get('timeout', None),
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -156,6 +156,7 @@ class XEP_0045(BasePlugin):
|
||||
entry = pr['muc'].getStanzaValues()
|
||||
entry['show'] = pr['show']
|
||||
entry['status'] = pr['status']
|
||||
entry['alt_nick'] = pr['nick']
|
||||
if pr['type'] == 'unavailable':
|
||||
if entry['nick'] in self.rooms[entry['room']]:
|
||||
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)
|
||||
x = ET.Element('{http://jabber.org/protocol/muc}x')
|
||||
if password:
|
||||
passelement = ET.Element('password')
|
||||
passelement = ET.Element('{http://jabber.org/protocol/muc}password')
|
||||
passelement.text = password
|
||||
x.append(passelement)
|
||||
if maxhistory:
|
||||
history = ET.Element('history')
|
||||
history = ET.Element('{http://jabber.org/protocol/muc}history')
|
||||
if maxhistory == "0":
|
||||
history.attrib['maxchars'] = maxhistory
|
||||
else:
|
||||
@@ -270,10 +271,10 @@ class XEP_0045(BasePlugin):
|
||||
iq['from'] = ifrom
|
||||
iq['to'] = room
|
||||
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:
|
||||
destroy.attrib['jid'] = altroom
|
||||
xreason = ET.Element('reason')
|
||||
xreason = ET.Element('{http://jabber.org/protocol/muc#owner}reason')
|
||||
xreason.text = reason
|
||||
destroy.append(xreason)
|
||||
query.append(destroy)
|
||||
@@ -293,9 +294,9 @@ class XEP_0045(BasePlugin):
|
||||
raise TypeError
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
||||
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:
|
||||
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)
|
||||
iq = self.xmpp.makeIqSet(query)
|
||||
iq['to'] = room
|
||||
@@ -316,7 +317,7 @@ class XEP_0045(BasePlugin):
|
||||
x = ET.Element('{http://jabber.org/protocol/muc#user}x')
|
||||
invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
|
||||
if reason:
|
||||
rxml = ET.Element('reason')
|
||||
rxml = ET.Element('{http://jabber.org/protocol/muc#user}reason')
|
||||
rxml.text = reason
|
||||
invite.append(rxml)
|
||||
x.append(invite)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import re
|
||||
import base64
|
||||
|
||||
from sleekxmpp.util import bytes
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
from sleekxmpp.xmlstream import ElementBase
|
||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
||||
|
||||
|
||||
VALID_B64 = re.compile(r'[A-Za-z0-9\+\/]*=*')
|
||||
@@ -14,7 +14,7 @@ def to_b64(data):
|
||||
|
||||
|
||||
def from_b64(data):
|
||||
return bytes(base64.b64decode(bytes(data))).decode('utf-8')
|
||||
return bytes(base64.b64decode(bytes(data)))
|
||||
|
||||
|
||||
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)
|
||||
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',
|
||||
itype='command-list',
|
||||
name='Ad-Hoc commands',
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import base64
|
||||
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.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, Note, 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, ProdID, 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,
|
||||
callback=None, timeout=None):
|
||||
if self.xmpp.is_component:
|
||||
self.api['set_vcard'](jid, None, ifrom, vcard)
|
||||
if self.xmpp.is_component:
|
||||
return
|
||||
|
||||
iq = self.xmpp.Iq()
|
||||
|
||||
@@ -25,11 +25,14 @@ class ResultIterator():
|
||||
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:
|
||||
query -- The template query
|
||||
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
|
||||
start -- From which item id to start
|
||||
reverse -- If True, page backwards through the results
|
||||
@@ -46,6 +49,7 @@ class ResultIterator():
|
||||
self.amount = amount
|
||||
self.start = start
|
||||
self.interface = interface
|
||||
self.results = results
|
||||
self.reverse = reverse
|
||||
self._stop = False
|
||||
|
||||
@@ -85,7 +89,7 @@ class ResultIterator():
|
||||
r[self.interface]['rsm']['first_index']:
|
||||
count = int(r[self.interface]['rsm']['count'])
|
||||
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:
|
||||
self._stop = True
|
||||
|
||||
@@ -123,7 +127,7 @@ class XEP_0059(BasePlugin):
|
||||
def session_bind(self, jid):
|
||||
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.
|
||||
|
||||
@@ -135,5 +139,7 @@ class XEP_0059(BasePlugin):
|
||||
result set management stanza should be
|
||||
appended. For example, for disco#items queries
|
||||
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 sys
|
||||
|
||||
from sleekxmpp.jid import JID
|
||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
|
||||
@@ -44,16 +45,27 @@ class XEP_0078(BasePlugin):
|
||||
restart=False,
|
||||
order=self.order)
|
||||
|
||||
self.xmpp.add_event_handler('legacy_protocol',
|
||||
self._handle_legacy_protocol)
|
||||
|
||||
register_stanza_plugin(Iq, stanza.IqAuth)
|
||||
register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.del_event_handler('legacy_protocol',
|
||||
self._handle_legacy_protocol)
|
||||
self.xmpp.unregister_feature('auth', self.order)
|
||||
|
||||
def _handle_auth(self, features):
|
||||
# If we can or have already authenticated with SASL, do nothing.
|
||||
if 'mechanisms' in features['features']:
|
||||
return False
|
||||
return self.authenticate()
|
||||
|
||||
def _handle_legacy_protocol(self, event):
|
||||
self.authenticate()
|
||||
|
||||
def authenticate(self):
|
||||
if self.xmpp.authenticated:
|
||||
return False
|
||||
|
||||
@@ -62,13 +74,13 @@ class XEP_0078(BasePlugin):
|
||||
# Step 1: Request the auth form
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['to'] = self.xmpp.boundjid.host
|
||||
iq['auth']['username'] = self.xmpp.boundjid.user
|
||||
iq['to'] = self.xmpp.requested_jid.host
|
||||
iq['auth']['username'] = self.xmpp.requested_jid.user
|
||||
|
||||
try:
|
||||
resp = iq.send(now=True)
|
||||
except IqError:
|
||||
log.info("Authentication failed: %s", resp['error']['condition'])
|
||||
except IqError as err:
|
||||
log.info("Authentication failed: %s", err.iq['error']['condition'])
|
||||
self.xmpp.event('failed_auth', direct=True)
|
||||
self.xmpp.disconnect()
|
||||
return True
|
||||
@@ -81,13 +93,14 @@ class XEP_0078(BasePlugin):
|
||||
# Step 2: Fill out auth form for either password or digest auth
|
||||
iq = self.xmpp.Iq()
|
||||
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
|
||||
if self.xmpp.boundjid.resource:
|
||||
iq['auth']['resource'] = self.xmpp.boundjid.resource
|
||||
else:
|
||||
iq['auth']['resource'] = '%s' % random.random()
|
||||
resource = self.xmpp.requested_jid.resource
|
||||
if not resource:
|
||||
resource = uuid.uuid4()
|
||||
|
||||
iq['auth']['resource'] = resource
|
||||
|
||||
if 'digest' in resp['auth']['fields']:
|
||||
log.debug('Authenticating via jabber:iq:auth Digest')
|
||||
@@ -109,16 +122,22 @@ class XEP_0078(BasePlugin):
|
||||
result = iq.send(now=True)
|
||||
except IqError as err:
|
||||
log.info("Authentication failed")
|
||||
self.xmpp.disconnect()
|
||||
self.xmpp.event("failed_auth", direct=True)
|
||||
self.xmpp.disconnect()
|
||||
except IqTimeout:
|
||||
log.info("Authentication failed")
|
||||
self.xmpp.disconnect()
|
||||
self.xmpp.event("failed_auth", direct=True)
|
||||
self.xmpp.disconnect()
|
||||
|
||||
self.xmpp.features.add('auth')
|
||||
|
||||
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")
|
||||
self.xmpp.sessionstarted = True
|
||||
self.xmpp.session_started_event.set()
|
||||
|
||||
@@ -69,6 +69,8 @@ class XEP_0084(BasePlugin):
|
||||
metadata = MetaData()
|
||||
if items is None:
|
||||
items = []
|
||||
if not isinstance(items, (list, set)):
|
||||
items = [items]
|
||||
for info in items:
|
||||
metadata.add_info(info['id'], info['type'], info['bytes'],
|
||||
height=info.get('height', ''),
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -52,4 +52,5 @@ class XEP_0085(BasePlugin):
|
||||
def _handle_chat_state(self, msg):
|
||||
state = msg['chat_state']
|
||||
log.debug("Chat State: %s, %s", state, msg['from'].jid)
|
||||
self.xmpp.event('chatstate', 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']):
|
||||
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:
|
||||
try:
|
||||
log.debug("Unknown caps hash: %s", pres['caps']['hash'])
|
||||
|
||||
@@ -12,6 +12,7 @@ import threading
|
||||
|
||||
from sleekxmpp import JID
|
||||
from sleekxmpp.stanza import Presence
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
from sleekxmpp.xmlstream.handler import Callback
|
||||
@@ -103,6 +104,7 @@ class XEP_0153(BasePlugin):
|
||||
if own_jid:
|
||||
self.xmpp.roster[jid].send_last_presence()
|
||||
|
||||
try:
|
||||
iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom)
|
||||
|
||||
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
||||
@@ -112,6 +114,8 @@ class XEP_0153(BasePlugin):
|
||||
new_hash = hashlib.sha1(data).hexdigest()
|
||||
|
||||
self.api['set_hash'](jid, args=new_hash)
|
||||
except XMPPError:
|
||||
log.debug('Could not retrieve vCard for %s' % jid)
|
||||
|
||||
def _recv_presence(self, pres):
|
||||
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):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['from'] = 'ifrom'
|
||||
iq['from'] = ifrom
|
||||
iq.enable('blocklist')
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
|
||||
@@ -14,14 +14,17 @@ from sleekxmpp.plugins import xep_0082
|
||||
|
||||
class Delay(ElementBase):
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
name = 'delay'
|
||||
namespace = 'urn:xmpp:delay'
|
||||
plugin_attrib = '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(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 sleekxmpp.thirdparty.suelta.util import bytes
|
||||
|
||||
from sleekxmpp.util import bytes
|
||||
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)
|
||||
|
||||
def handle_probe(self, presence):
|
||||
if self['to']:
|
||||
if self['from']:
|
||||
self.send_last_presence()
|
||||
if self['pending_out']:
|
||||
self.subscribe()
|
||||
if not self['to']:
|
||||
if not self['from']:
|
||||
self._unsubscribed()
|
||||
|
||||
def reset(self):
|
||||
|
||||
@@ -154,7 +154,7 @@ class Iq(RootStanza):
|
||||
StanzaBase.reply(self, clear)
|
||||
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.
|
||||
|
||||
@@ -181,11 +181,28 @@ class Iq(RootStanza):
|
||||
now -- Indicates if the send queue should be skipped and send
|
||||
the stanza immediately. Used during stream
|
||||
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:
|
||||
timeout = self.stream.response_timeout
|
||||
if callback is not None and self['type'] in ('get', 'set'):
|
||||
handler_name = 'IqCallback_%s' % self['id']
|
||||
if timeout_callback:
|
||||
self.callback = callback
|
||||
self.timeout_callback = timeout_callback
|
||||
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,
|
||||
@@ -206,6 +223,16 @@ class Iq(RootStanza):
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
Set multiple stanza interface values using a dictionary.
|
||||
|
||||
@@ -63,6 +63,17 @@ class Message(RootStanza):
|
||||
lang_interfaces = sub_interfaces
|
||||
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):
|
||||
"""
|
||||
Return the message type.
|
||||
|
||||
@@ -72,6 +72,17 @@ class Presence(RootStanza):
|
||||
'subscribed', 'unsubscribe', 'unsubscribed'])
|
||||
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):
|
||||
"""
|
||||
Override exception passback for presence.
|
||||
|
||||
@@ -130,7 +130,10 @@ class RosterItem(ElementBase):
|
||||
def get_groups(self):
|
||||
groups = []
|
||||
for group in self.xml.findall('{%s}group' % self.namespace):
|
||||
if group.text:
|
||||
groups.append(group.text)
|
||||
else:
|
||||
groups.append('')
|
||||
return groups
|
||||
|
||||
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