Compare commits
88 Commits
sleek-1.1.
...
sleek-1.1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
846f2bac5f | ||
![]() |
2229ad8d8e | ||
![]() |
61aff9f49a | ||
![]() |
67235c4214 | ||
![]() |
12e8bb6ddc | ||
![]() |
52feabbe76 | ||
![]() |
a22ca228cc | ||
![]() |
d0666a5eb6 | ||
![]() |
2a4e435228 | ||
![]() |
c5046b9c91 | ||
![]() |
4598031dd2 | ||
![]() |
5e9266ba90 | ||
![]() |
e6c95f0a2a | ||
![]() |
63b58edda1 | ||
![]() |
af9632519c | ||
![]() |
d367fb938d | ||
![]() |
4190027a78 | ||
![]() |
ef48a8c4d9 | ||
![]() |
829b225053 | ||
![]() |
747a6e94e6 | ||
![]() |
cebc798e72 | ||
![]() |
e2e8c4b5dc | ||
![]() |
675c0112ac | ||
![]() |
4dd2c15775 | ||
![]() |
9f6decdbc1 | ||
![]() |
4ea328b9f2 | ||
![]() |
098714b3c4 | ||
![]() |
cf2c94d974 | ||
![]() |
657102e938 | ||
![]() |
94488fa2ea | ||
![]() |
ee9c4abd08 | ||
![]() |
b5b1c932c7 | ||
![]() |
b8f04983e1 | ||
![]() |
90807dd973 | ||
![]() |
ef974114ea | ||
![]() |
f6e1fecdf8 | ||
![]() |
94e8b2becf | ||
![]() |
a6ca6701a0 | ||
![]() |
c4edb9724b | ||
![]() |
b5c669bdff | ||
![]() |
e449dce65c | ||
![]() |
671f680bb3 | ||
![]() |
dfff19ffbf | ||
![]() |
a4abdf9fa6 | ||
![]() |
6c57bb0553 | ||
![]() |
c2ae1ee891 | ||
![]() |
fb3e6b7e35 | ||
![]() |
cf28d4586d | ||
![]() |
f65eb5eeea | ||
![]() |
26fa9bd87e | ||
![]() |
0016d9a638 | ||
![]() |
a88b9737ff | ||
![]() |
357406d801 | ||
![]() |
c7ec6a72cd | ||
![]() |
e68b07dbce | ||
![]() |
1ca0c46333 | ||
![]() |
e510875f64 | ||
![]() |
8a03bd72ae | ||
![]() |
f0e1fc5aad | ||
![]() |
c6ac64ed2d | ||
![]() |
92be051450 | ||
![]() |
779c258e27 | ||
![]() |
f7a710e55b | ||
![]() |
814a50e36f | ||
![]() |
230465b946 | ||
![]() |
d11a67702e | ||
![]() |
4e12e228cb | ||
![]() |
4a94aeba49 | ||
![]() |
295d23ccf3 | ||
![]() |
aebcf6ff82 | ||
![]() |
8c2ece3bca | ||
![]() |
80a90a6221 | ||
![]() |
2324c90232 | ||
![]() |
59ff08174f | ||
![]() |
b84e359770 | ||
![]() |
475ccfa8dc | ||
![]() |
267c24c8ef | ||
![]() |
1383ca19b5 | ||
![]() |
4c3ff2abab | ||
![]() |
7c6ef18e4f | ||
![]() |
f8856467d5 | ||
![]() |
3bd84b8d27 | ||
![]() |
bc8b5774ac | ||
![]() |
8009b0485e | ||
![]() |
8742a56b3e | ||
![]() |
a792bcdafe | ||
![]() |
167d1ce97b | ||
![]() |
695cd95657 |
@@ -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
|
||||
|
||||
@@ -56,8 +56,8 @@ class ActionBot(sleekxmpp.ClientXMPP):
|
||||
StanzaPath('iq@type=set/action'),
|
||||
self._handle_action))
|
||||
|
||||
self.add_event_handler('custom_action',
|
||||
self._handle_action_event,
|
||||
self.add_event_handler('custom_action',
|
||||
self._handle_action_event,
|
||||
threaded=True)
|
||||
|
||||
register_stanza_plugin(Iq, Action)
|
||||
|
@@ -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,50 +84,54 @@ class Disco(sleekxmpp.ClientXMPP):
|
||||
self.get_roster()
|
||||
self.send_presence()
|
||||
|
||||
if self.get in self.info_types:
|
||||
# By using block=True, the result stanza will be
|
||||
# returned. Execution will block until the reply is
|
||||
# received. Non-blocking options would be to listen
|
||||
# for the disco_info event, or passing a handler
|
||||
# function using the callback parameter.
|
||||
info = self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
block=True)
|
||||
if 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.
|
||||
items = self['xep_0030'].get_items(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
block=True)
|
||||
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
|
||||
# received. Non-blocking options would be to listen
|
||||
# for the disco_info event, or passing a handler
|
||||
# function using the callback parameter.
|
||||
info = self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
block=True)
|
||||
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.
|
||||
items = self['xep_0030'].get_items(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
block=True)
|
||||
else:
|
||||
logging.error("Invalid disco request type.")
|
||||
return
|
||||
except IqError as e:
|
||||
logging.error("Entity returned an error: %s" % e.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
logging.error("No response received.")
|
||||
else:
|
||||
logging.error("Invalid disco request type.")
|
||||
self.disconnect()
|
||||
return
|
||||
|
||||
header = 'XMPP Service Discovery: %s' % self.target_jid
|
||||
print(header)
|
||||
print('-' * len(header))
|
||||
if self.target_node != '':
|
||||
print('Node: %s' % self.target_node)
|
||||
header = 'XMPP Service Discovery: %s' % self.target_jid
|
||||
print(header)
|
||||
print('-' * len(header))
|
||||
if self.target_node != '':
|
||||
print('Node: %s' % self.target_node)
|
||||
print('-' * len(header))
|
||||
|
||||
if self.get in self.identity_types:
|
||||
print('Identities:')
|
||||
for identity in info['disco_info']['identities']:
|
||||
print(' - %s' % str(identity))
|
||||
if self.get in self.identity_types:
|
||||
print('Identities:')
|
||||
for identity in info['disco_info']['identities']:
|
||||
print(' - %s' % str(identity))
|
||||
|
||||
if self.get in self.feature_types:
|
||||
print('Features:')
|
||||
for feature in info['disco_info']['features']:
|
||||
print(' - %s' % feature)
|
||||
if self.get in self.feature_types:
|
||||
print('Features:')
|
||||
for feature in info['disco_info']['features']:
|
||||
print(' - %s' % feature)
|
||||
|
||||
if self.get in self.items_types:
|
||||
print('Items:')
|
||||
for item in items['disco_items']['items']:
|
||||
print(' - %s' % str(item))
|
||||
|
||||
self.disconnect()
|
||||
if self.get in self.items_types:
|
||||
print('Items:')
|
||||
for item in items['disco_items']['items']:
|
||||
print(' - %s' % str(item))
|
||||
finally:
|
||||
self.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@@ -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
|
||||
|
||||
|
15
setup.py
15
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,11 +60,14 @@ 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',
|
||||
@@ -76,6 +80,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 +100,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 +116,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,15 +288,17 @@ class ClientXMPP(BaseXMPP):
|
||||
if iq['roster']['ver']:
|
||||
roster.version = iq['roster']['ver']
|
||||
items = iq['roster']['items']
|
||||
for jid in items:
|
||||
item = items[jid]
|
||||
roster[jid]['name'] = item['name']
|
||||
roster[jid]['groups'] = item['groups']
|
||||
roster[jid]['from'] = item['subscription'] in ['from', 'both']
|
||||
roster[jid]['to'] = item['subscription'] in ['to', 'both']
|
||||
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
||||
|
||||
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
||||
valid_subscriptions = ('to', 'from', 'both', 'none', 'remove')
|
||||
for jid, item in items.items():
|
||||
if item['subscription'] in valid_subscriptions:
|
||||
roster[jid]['name'] = item['name']
|
||||
roster[jid]['groups'] = item['groups']
|
||||
roster[jid]['from'] = item['subscription'] in ('from', 'both')
|
||||
roster[jid]['to'] = item['subscription'] in ('to', 'both')
|
||||
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
||||
|
||||
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
||||
|
||||
self.event("roster_update", iq)
|
||||
if iq['type'] == 'set':
|
||||
|
@@ -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:
|
||||
self.mech_list = set(features['mechanisms'])
|
||||
else:
|
||||
self.mech_list = set([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'])
|
||||
|
||||
return self._send_auth()
|
||||
|
||||
def _send_auth(self):
|
||||
mech_list = self.mech_list - self.attempted_mechs
|
||||
self.mech = self.sasl.choose_mechanism(mech_list)
|
||||
|
||||
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:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except SASLError:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except SASLPrepFailure:
|
||||
log.exception("A credential value did not pass SASLprep.")
|
||||
self.xmpp.disconnect()
|
||||
else:
|
||||
resp.send(now=True)
|
||||
else:
|
||||
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()
|
||||
|
||||
resp = stanza.Auth(self.xmpp)
|
||||
resp['mechanism'] = self.mech.name
|
||||
try:
|
||||
resp['value'] = self.mech.process()
|
||||
except sasl.SASLCancelled:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except StringPrepError:
|
||||
log.exception("A credential value did not pass SASLprep.")
|
||||
self.xmpp.disconnect()
|
||||
else:
|
||||
resp.send(now=True)
|
||||
|
||||
return True
|
||||
|
||||
def _handle_challenge(self, stanza):
|
||||
@@ -149,20 +205,33 @@ 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."""
|
||||
self.attempted_mechs = set()
|
||||
self.xmpp.authenticated = True
|
||||
self.xmpp.features.add('mechanisms')
|
||||
self.xmpp.event('auth_success', stanza, direct=True)
|
||||
raise RestartStream()
|
||||
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')
|
||||
self.xmpp.event('auth_success', stanza, direct=True)
|
||||
raise RestartStream()
|
||||
|
||||
def _handle_fail(self, stanza):
|
||||
"""SASL authentication failed. Disconnect and shutdown."""
|
||||
|
@@ -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)
|
||||
elif not isinstance(jid, JID):
|
||||
jid = _parse_jid(jid)
|
||||
else:
|
||||
jid = jid._jid
|
||||
# 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):
|
||||
parsed_jid = _parse_jid(jid)
|
||||
else:
|
||||
parsed_jid = jid._jid
|
||||
|
||||
local, domain, resource = jid
|
||||
local, domain, resource = parsed_jid
|
||||
|
||||
local = kwargs.get('local', local)
|
||||
domain = kwargs.get('domain', domain)
|
||||
resource = kwargs.get('resource', resource)
|
||||
if 'local' in kwargs:
|
||||
local = _escape_node(in_local)
|
||||
if 'domain' in kwargs:
|
||||
domain = _validate_domain(in_domain)
|
||||
if 'resource' in kwargs:
|
||||
resource = _validate_resource(in_resource)
|
||||
|
||||
if 'local' in kwargs:
|
||||
local = _escape_node(local)
|
||||
if 'domain' in kwargs:
|
||||
domain = _validate_domain(domain)
|
||||
if 'resource' in kwargs:
|
||||
resource = _validate_resource(resource)
|
||||
|
||||
self._jid = (local, domain, 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,11 +18,14 @@ __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
|
||||
@@ -35,6 +38,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 +63,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,7 +201,8 @@ class Form(ElementBase):
|
||||
del self['instructions']
|
||||
if instructions in [None, '']:
|
||||
return
|
||||
instructions = instructions.split('\n')
|
||||
if not isinstance(instructions, list):
|
||||
instructions = instructions.split('\n')
|
||||
for instruction in instructions:
|
||||
inst = ET.Element('{%s}instructions' % self.namespace)
|
||||
inst.text = instruction
|
||||
|
15
sleekxmpp/plugins/xep_0013/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0013/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
from sleekxmpp.plugins.base import register_plugin
|
||||
|
||||
from sleekxmpp.plugins.xep_0013.stanza import Offline
|
||||
from sleekxmpp.plugins.xep_0013.offline import XEP_0013
|
||||
|
||||
|
||||
register_plugin(XEP_0013)
|
134
sleekxmpp/plugins/xep_0013/offline.py
Normal file
134
sleekxmpp/plugins/xep_0013/offline.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.stanza import Message, Iq
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
from sleekxmpp.xmlstream.handler import Collector
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
from sleekxmpp.plugins.xep_0013 import stanza
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XEP_0013(BasePlugin):
|
||||
|
||||
"""
|
||||
XEP-0013 Flexible Offline Message Retrieval
|
||||
"""
|
||||
|
||||
name = 'xep_0013'
|
||||
description = 'XEP-0013: Flexible Offline Message Retrieval'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, stanza.Offline)
|
||||
register_stanza_plugin(Message, stanza.Offline)
|
||||
|
||||
def get_count(self, **kwargs):
|
||||
return self.xmpp['xep_0030'].get_info(
|
||||
node='http://jabber.org/protocol/offline',
|
||||
local=False,
|
||||
**kwargs)
|
||||
|
||||
def get_headers(self, **kwargs):
|
||||
return self.xmpp['xep_0030'].get_items(
|
||||
node='http://jabber.org/protocol/offline',
|
||||
local=False,
|
||||
**kwargs)
|
||||
|
||||
def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
|
||||
if not isinstance(nodes, (list, set)):
|
||||
nodes = [nodes]
|
||||
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['from'] = ifrom
|
||||
offline = iq['offline']
|
||||
for node in nodes:
|
||||
item = stanza.Item()
|
||||
item['node'] = node
|
||||
item['action'] = 'view'
|
||||
offline.append(item)
|
||||
|
||||
collector = Collector(
|
||||
'Offline_Results_%s' % iq['id'],
|
||||
StanzaPath('message/offline'))
|
||||
self.xmpp.register_handler(collector)
|
||||
|
||||
if not block and callback is not None:
|
||||
def wrapped_cb(iq):
|
||||
results = collector.stop()
|
||||
if iq['type'] == 'result':
|
||||
iq['offline']['results'] = results
|
||||
callback(iq)
|
||||
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
|
||||
else:
|
||||
try:
|
||||
resp = iq.send(block=block, timeout=timeout, callback=callback)
|
||||
resp['offline']['results'] = collector.stop()
|
||||
return resp
|
||||
except XMPPError as e:
|
||||
collector.stop()
|
||||
raise e
|
||||
|
||||
def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
|
||||
if not isinstance(nodes, (list, set)):
|
||||
nodes = [nodes]
|
||||
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
offline = iq['offline']
|
||||
for node in nodes:
|
||||
item = stanza.Item()
|
||||
item['node'] = node
|
||||
item['action'] = 'remove'
|
||||
offline.append(item)
|
||||
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def fetch(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq['offline']['fetch'] = True
|
||||
|
||||
collector = Collector(
|
||||
'Offline_Results_%s' % iq['id'],
|
||||
StanzaPath('message/offline'))
|
||||
self.xmpp.register_handler(collector)
|
||||
|
||||
if not block and callback is not None:
|
||||
def wrapped_cb(iq):
|
||||
results = collector.stop()
|
||||
if iq['type'] == 'result':
|
||||
iq['offline']['results'] = results
|
||||
callback(iq)
|
||||
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
|
||||
else:
|
||||
try:
|
||||
resp = iq.send(block=block, timeout=timeout, callback=callback)
|
||||
resp['offline']['results'] = collector.stop()
|
||||
return resp
|
||||
except XMPPError as e:
|
||||
collector.stop()
|
||||
raise e
|
||||
|
||||
def purge(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq['offline']['purge'] = True
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
53
sleekxmpp/plugins/xep_0013/stanza.py
Normal file
53
sleekxmpp/plugins/xep_0013/stanza.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
from sleekxmpp.jid import JID
|
||||
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||
|
||||
|
||||
class Offline(ElementBase):
|
||||
name = 'offline'
|
||||
namespace = 'http://jabber.org/protocol/offline'
|
||||
plugin_attrib = 'offline'
|
||||
interfaces = set(['fetch', 'purge', 'results'])
|
||||
bool_interfaces = interfaces
|
||||
|
||||
def setup(self, xml=None):
|
||||
ElementBase.setup(self, xml)
|
||||
self._results = []
|
||||
|
||||
# The results interface is meant only as an easy
|
||||
# way to access the set of collected message responses
|
||||
# from the query.
|
||||
|
||||
def get_results(self):
|
||||
return self._results
|
||||
|
||||
def set_results(self, values):
|
||||
self._results = values
|
||||
|
||||
def del_results(self):
|
||||
self._results = []
|
||||
|
||||
|
||||
class Item(ElementBase):
|
||||
name = 'item'
|
||||
namespace = 'http://jabber.org/protocol/offline'
|
||||
plugin_attrib = 'item'
|
||||
interfaces = set(['action', 'node', 'jid'])
|
||||
|
||||
actions = set(['view', 'remove'])
|
||||
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
|
||||
register_stanza_plugin(Offline, Item, iterable=True)
|
16
sleekxmpp/plugins/xep_0016/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0016/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp.plugins.base import register_plugin
|
||||
|
||||
from sleekxmpp.plugins.xep_0016 import stanza
|
||||
from sleekxmpp.plugins.xep_0016.stanza import Privacy
|
||||
from sleekxmpp.plugins.xep_0016.privacy import XEP_0016
|
||||
|
||||
|
||||
register_plugin(XEP_0016)
|
110
sleekxmpp/plugins/xep_0016/privacy.py
Normal file
110
sleekxmpp/plugins/xep_0016/privacy.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp import Iq
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
from sleekxmpp.plugins.xep_0016 import stanza
|
||||
from sleekxmpp.plugins.xep_0016.stanza import Privacy, Item
|
||||
|
||||
|
||||
class XEP_0016(BasePlugin):
|
||||
|
||||
name = 'xep_0016'
|
||||
description = 'XEP-0016: Privacy Lists'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, Privacy)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp['xep_0030'].del_feature(feature=Privacy.namespace)
|
||||
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0030'].add_feature(Privacy.namespace)
|
||||
|
||||
def get_privacy_lists(self, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq.enable('privacy')
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def get_list(self, name, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['privacy']['list']['name'] = name
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def get_active(self, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['privacy'].enable('active')
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def get_default(self, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['privacy'].enable('default')
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def activate(self, name, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['privacy']['active']['name'] = name
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def deactivate(self, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['privacy'].enable('active')
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def make_default(self, name, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['privacy']['default']['name'] = name
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def remove_default(self, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['privacy'].enable('default')
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def edit_list(self, name, rules, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['privacy']['list']['name'] = name
|
||||
priv_list = iq['privacy']['list']
|
||||
|
||||
if not rules:
|
||||
rules = []
|
||||
|
||||
for rule in rules:
|
||||
if isinstance(rule, Item):
|
||||
priv_list.append(rule)
|
||||
continue
|
||||
|
||||
priv_list.add_item(
|
||||
rule['value'],
|
||||
rule['action'],
|
||||
rule['order'],
|
||||
itype=rule.get('type', None),
|
||||
iq=rule.get('iq', None),
|
||||
message=rule.get('message', None),
|
||||
presence_in=rule.get('presence_in',
|
||||
rule.get('presence-in', None)),
|
||||
presence_out=rule.get('presence_out',
|
||||
rule.get('presence-out', None)))
|
||||
|
||||
def remove_list(self, name, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['privacy']['list']['name'] = name
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
103
sleekxmpp/plugins/xep_0016/stanza.py
Normal file
103
sleekxmpp/plugins/xep_0016/stanza.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
||||
|
||||
|
||||
class Privacy(ElementBase):
|
||||
name = 'query'
|
||||
namespace = 'jabber:iq:privacy'
|
||||
plugin_attrib = 'privacy'
|
||||
interfaces = set()
|
||||
|
||||
def add_list(self, name):
|
||||
priv_list = List()
|
||||
priv_list['name'] = name
|
||||
self.append(priv_list)
|
||||
return priv_list
|
||||
|
||||
|
||||
class Active(ElementBase):
|
||||
name = 'active'
|
||||
namespace = 'jabber:iq:privacy'
|
||||
plugin_attrib = name
|
||||
interfaces = set(['name'])
|
||||
|
||||
|
||||
class Default(ElementBase):
|
||||
name = 'default'
|
||||
namespace = 'jabber:iq:privacy'
|
||||
plugin_attrib = name
|
||||
interfaces = set(['name'])
|
||||
|
||||
|
||||
class List(ElementBase):
|
||||
name = 'list'
|
||||
namespace = 'jabber:iq:privacy'
|
||||
plugin_attrib = name
|
||||
plugin_multi_attrib = 'lists'
|
||||
interfaces = set(['name'])
|
||||
|
||||
def add_item(self, value, action, order, itype=None, iq=False,
|
||||
message=False, presence_in=False, presence_out=False):
|
||||
item = Item()
|
||||
item.values = {'type': itype,
|
||||
'value': value,
|
||||
'action': action,
|
||||
'order': order,
|
||||
'message': message,
|
||||
'iq': iq,
|
||||
'presence_in': presence_in,
|
||||
'presence_out': presence_out}
|
||||
self.append(item)
|
||||
return item
|
||||
|
||||
|
||||
class Item(ElementBase):
|
||||
name = 'item'
|
||||
namespace = 'jabber:iq:privacy'
|
||||
plugin_attrib = name
|
||||
plugin_multi_attrib = 'items'
|
||||
interfaces = set(['type', 'value', 'action', 'order', 'iq',
|
||||
'message', 'presence_in', 'presence_out'])
|
||||
bool_interfaces = set(['message', 'iq', 'presence_in', 'presence_out'])
|
||||
|
||||
type_values = ('', 'jid', 'group', 'subscription')
|
||||
action_values = ('allow', 'deny')
|
||||
|
||||
def set_type(self, value):
|
||||
if value and value not in self.type_values:
|
||||
raise ValueError('Unknown type value: %s' % value)
|
||||
else:
|
||||
self._set_attr('type', value)
|
||||
|
||||
def set_action(self, value):
|
||||
if value not in self.action_values:
|
||||
raise ValueError('Unknown action value: %s' % value)
|
||||
else:
|
||||
self._set_attr('action', value)
|
||||
|
||||
def set_presence_in(self, value):
|
||||
keep = True if value else False
|
||||
self._set_sub_text('presence-in', '', keep=keep)
|
||||
|
||||
def get_presence_in(self):
|
||||
pres = self.xml.find('{%s}presence-in' % self.namespace)
|
||||
return pres is not None
|
||||
|
||||
def del_presence_in(self):
|
||||
self._del_sub('{%s}presence-in' % self.namespace)
|
||||
|
||||
def set_presence_out(self, value):
|
||||
keep = True if value else False
|
||||
self._set_sub_text('presence-in', '', keep=keep)
|
||||
|
||||
def get_presence_out(self):
|
||||
pres = self.xml.find('{%s}presence-in' % self.namespace)
|
||||
return pres is not None
|
||||
|
||||
def del_presence_out(self):
|
||||
self._del_sub('{%s}presence-in' % self.namespace)
|
||||
|
||||
|
||||
register_stanza_plugin(Privacy, Active)
|
||||
register_stanza_plugin(Privacy, Default)
|
||||
register_stanza_plugin(Privacy, List, iterable=True)
|
||||
register_stanza_plugin(List, Item, iterable=True)
|
@@ -288,7 +288,7 @@ class XEP_0030(BasePlugin):
|
||||
'cached': cached}
|
||||
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,18 +324,21 @@ 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 jid is not None and not isinstance(jid, JID):
|
||||
jid = JID(jid)
|
||||
if self.xmpp.is_component:
|
||||
if jid.domain == self.xmpp.boundjid.domain:
|
||||
local = True
|
||||
else:
|
||||
if str(jid) == str(self.xmpp.boundjid):
|
||||
local = True
|
||||
jid = jid.full
|
||||
elif jid in (None, ''):
|
||||
local = True
|
||||
if local is None:
|
||||
if jid is not None and not isinstance(jid, JID):
|
||||
jid = JID(jid)
|
||||
if self.xmpp.is_component:
|
||||
if jid.domain == self.xmpp.boundjid.domain:
|
||||
local = True
|
||||
else:
|
||||
if str(jid) == str(self.xmpp.boundjid):
|
||||
local = True
|
||||
jid = jid.full
|
||||
elif jid in (None, ''):
|
||||
local = True
|
||||
|
||||
if local:
|
||||
log.debug("Looking up local disco#info data " + \
|
||||
@@ -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):
|
||||
self.api['set_vcard'](jid, None, ifrom, vcard)
|
||||
if self.xmpp.is_component:
|
||||
self.api['set_vcard'](jid, None, ifrom, vcard)
|
||||
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)
|
||||
|
@@ -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,15 +104,18 @@ class XEP_0153(BasePlugin):
|
||||
if own_jid:
|
||||
self.xmpp.roster[jid].send_last_presence()
|
||||
|
||||
iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom)
|
||||
try:
|
||||
iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom)
|
||||
|
||||
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
||||
if not data:
|
||||
new_hash = ''
|
||||
else:
|
||||
new_hash = hashlib.sha1(data).hexdigest()
|
||||
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
||||
if not data:
|
||||
new_hash = ''
|
||||
else:
|
||||
new_hash = hashlib.sha1(data).hexdigest()
|
||||
|
||||
self.api['set_hash'](jid, args=new_hash)
|
||||
self.api['set_hash'](jid, args=new_hash)
|
||||
except XMPPError:
|
||||
log.debug('Could not retrieve vCard for %s' % jid)
|
||||
|
||||
def _recv_presence(self, pres):
|
||||
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,15 +181,32 @@ 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']
|
||||
handler = Callback(handler_name,
|
||||
MatcherId(self['id']),
|
||||
callback,
|
||||
once=True)
|
||||
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,
|
||||
once=True)
|
||||
self.stream.register_handler(handler)
|
||||
StanzaBase.send(self, now=now)
|
||||
return handler_name
|
||||
@@ -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):
|
||||
groups.append(group.text)
|
||||
if group.text:
|
||||
groups.append(group.text)
|
||||
else:
|
||||
groups.append('')
|
||||
return groups
|
||||
|
||||
def set_groups(self, values):
|
||||
|
@@ -368,6 +368,11 @@ class SleekTest(unittest.TestCase):
|
||||
else:
|
||||
for plugin in plugins:
|
||||
self.xmpp.register_plugin(plugin)
|
||||
|
||||
# Some plugins require messages to have ID values. Set
|
||||
# this to True in tests related to those plugins.
|
||||
self.xmpp.use_message_ids = False
|
||||
|
||||
self.xmpp.process(threaded=True)
|
||||
if skip:
|
||||
if socket != 'live':
|
||||
|
1
sleekxmpp/thirdparty/__init__.py
vendored
1
sleekxmpp/thirdparty/__init__.py
vendored
@@ -8,5 +8,4 @@ try:
|
||||
except:
|
||||
from sleekxmpp.thirdparty.gnupg import GPG
|
||||
|
||||
from sleekxmpp.thirdparty import suelta
|
||||
from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
|
||||
|
49
sleekxmpp/thirdparty/mini_dateutil.py
vendored
49
sleekxmpp/thirdparty/mini_dateutil.py
vendored
@@ -166,32 +166,34 @@ except:
|
||||
(?P<month>[0-9]{2})?(?P=ymdsep)?
|
||||
(?P<day> [0-9]{2})?
|
||||
|
||||
(?: # time part... optional... at least hour must be specified
|
||||
(?:T|\s+)?
|
||||
(?P<hour>[0-9]{2})
|
||||
(?:
|
||||
# minutes, separated with :, or none, from hours
|
||||
(?P<hmssep>[:]?)
|
||||
(?P<minute>[0-9]{2})
|
||||
(?P<time>
|
||||
(?: # time part... optional... at least hour must be specified
|
||||
(?:T|\s+)?
|
||||
(?P<hour>[0-9]{2})
|
||||
(?:
|
||||
# same for seconds, separated with :, or none, from hours
|
||||
(?P=hmssep)
|
||||
(?P<second>[0-9]{2})
|
||||
# minutes, separated with :, or none, from hours
|
||||
(?P<hmssep>[:]?)
|
||||
(?P<minute>[0-9]{2})
|
||||
(?:
|
||||
# same for seconds, separated with :, or none, from hours
|
||||
(?P=hmssep)
|
||||
(?P<second>[0-9]{2})
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
|
||||
# fractions
|
||||
(?: [,.] (?P<frac>[0-9]{1,10}))?
|
||||
# fractions
|
||||
(?: [,.] (?P<frac>[0-9]{1,10}))?
|
||||
|
||||
# timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there.
|
||||
(
|
||||
(?P<tzempty>Z)
|
||||
|
|
||||
(?P<tzh>[+-][0-9]{2})
|
||||
(?: :? # optional separator
|
||||
(?P<tzm>[0-9]{2})
|
||||
# timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there.
|
||||
(
|
||||
(?P<tzempty>Z)
|
||||
|
|
||||
(?P<tzh>[+-][0-9]{2})
|
||||
(?: :? # optional separator
|
||||
(?P<tzm>[0-9]{2})
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
)
|
||||
)?
|
||||
$
|
||||
""", re.X) # """
|
||||
@@ -211,13 +213,16 @@ except:
|
||||
for key in vals:
|
||||
if vals[key] is None:
|
||||
vals[key] = def_vals.get(key, 0)
|
||||
elif key not in ['ymdsep', 'hmssep', 'tzempty']:
|
||||
elif key not in ['time', 'ymdsep', 'hmssep', 'tzempty']:
|
||||
vals[key] = int(vals[key])
|
||||
|
||||
year = vals['year']
|
||||
month = vals['month']
|
||||
day = vals['day']
|
||||
|
||||
if m.group('time') is None:
|
||||
return datetime.date(year, month, day)
|
||||
|
||||
h, min, s, us = None, None, None, 0
|
||||
frac = 0
|
||||
if m.group('tzempty') == None and m.group('tzh') == None:
|
||||
|
15
sleekxmpp/thirdparty/statemachine.py
vendored
15
sleekxmpp/thirdparty/statemachine.py
vendored
@@ -29,7 +29,7 @@ class StateMachine(object):
|
||||
if state in self.__states:
|
||||
raise IndexError("The state '%s' is already in the StateMachine." % state)
|
||||
self.__states.append(state)
|
||||
finally:
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
|
||||
@@ -83,11 +83,14 @@ class StateMachine(object):
|
||||
if not to_state in self.__states:
|
||||
raise ValueError("StateMachine does not contain to_state %s." % to_state)
|
||||
|
||||
if self.__current_state == to_state:
|
||||
return True
|
||||
|
||||
start = time.time()
|
||||
while not self.lock.acquire(False):
|
||||
time.sleep(.001)
|
||||
if (start + wait - time.time()) <= 0.0:
|
||||
log.debug("Could not acquire lock")
|
||||
log.debug("==== Could not acquire lock in %s sec: %s -> %s ", wait, self.__current_state, to_state)
|
||||
return False
|
||||
|
||||
while not self.__current_state in from_states:
|
||||
@@ -108,7 +111,7 @@ class StateMachine(object):
|
||||
|
||||
# some 'false' value returned from func,
|
||||
# indicating that transition should not occur:
|
||||
if not return_val:
|
||||
if not return_val:
|
||||
return return_val
|
||||
|
||||
log.debug(' ==== TRANSITION %s -> %s', self.__current_state, to_state)
|
||||
@@ -193,9 +196,9 @@ class StateMachine(object):
|
||||
while not self.__current_state in states:
|
||||
# detect timeout:
|
||||
remainder = start + wait - time.time()
|
||||
if remainder > 0:
|
||||
if remainder > 0:
|
||||
self.lock.wait(remainder)
|
||||
else:
|
||||
else:
|
||||
self.lock.release()
|
||||
return False
|
||||
self.lock.release()
|
||||
@@ -241,7 +244,7 @@ class _StateCtx:
|
||||
while not self.state_machine[self.from_state] or not self.state_machine.lock.acquire(False):
|
||||
# detect timeout:
|
||||
remainder = start + self.wait - time.time()
|
||||
if remainder > 0:
|
||||
if remainder > 0:
|
||||
self.state_machine.lock.wait(remainder)
|
||||
else:
|
||||
log.debug('StateMachine timeout while waiting for state: %s', self.from_state)
|
||||
|
21
sleekxmpp/thirdparty/suelta/LICENSE
vendored
21
sleekxmpp/thirdparty/suelta/LICENSE
vendored
@@ -1,21 +0,0 @@
|
||||
This software is subject to "The MIT License"
|
||||
|
||||
Copyright 2007-2010 David Alan Cridland
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user