-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpubsubclient.py
1799 lines (1572 loc) · 70.6 KB
/
pubsubclient.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# PubSubClient, a Python library for interacting with XMPP PubSub
# Copyright (C) 2008 Chris Warburton
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
### WARNING: This library is still early in development. Expected changes
### include:
### * Consistent naming of PubSubClient methods
### * Any usage of a server URL should accept a string AND a Server
### * Combine some of PubSubClient's methods into fewer, more generic ones
### * Make some of PubSubClient's methods private after API stabilises
### * Possible new objects: Entity? Affiliate? Option?
import xmpp
import string
from random import Random
from StringIO import StringIO
from lxml.etree import ElementTree, Element, SubElement
import lxml.etree as etree
class PubSubClient(object):
def __init__(self, jid, password, resource='subscriptions'):
"""Creates a new PubSubClient. jid can be a string or a JID,
password and resource are strings."""
# Store all of the information we need to connect to Jabber
# The user and server can be deduced from the JabberID
# FIXME: This is probably really insecure password storage?
self.password = password
self.resource = resource
if type(jid) == type("string"):
self.jid = JID(jid)
elif type(jid) == type(JID()):
self.jid = jid
self.user = self.jid.getNode()
self.server = self.jid.getDomain()
self.connection = xmpp.Client(self.server,debug=[])
# self.pending uses the stanza id of any requests which are
# awaiting a response as keys, with assigned values of the
# predefined functions which should handle these responses.
# IMPORTANT: The functions are stored in a list since functions
# cannot be directly stored in dictionaries. Each list contains
# just one function, ie. {'id1':[function1], 'id2':[function2]}
# To call a function use self.pending[id][0](stanza)
self.pending = {}
# self.callbacks works in a similar way to self.pending, except
# that it maps stanza ids to functions given by the programmer
# using the library. These functions are passed to the
# appropriate handler function from self.pending, and are
# executed by those functions when they have finished
# processing the replies
self.callbacks = {}
# This stores the stanza ids used for this session, since ids
# must be unique for their session
self.used_ids = []
# Assign a default printing handler for messages
self.assign_message_handler(self.default_handler)
def default_handler(self, message):
print etree.tostring(message)
def connect(self):
"""Turn on the connection. Returns 1 for error, 0 for success."""
# First try connecting to the server
connection_result = self.connection.connect()
if not connection_result:
print "Could not connect to server " + str(self.server)
return 1
if connection_result != 'tls':
print "Warning: Could not use TLS"
# Then try to log in
authorisation_result = self.connection.auth(self.user, \
self.password, \
self.resource)
if not authorisation_result:
print "Could not get authorized. Username/password problem?"
return 1
if authorisation_result != 'sasl':
print "Warning: Could not use SASL"
# Here we specify which methods to run to process messages and
# queries
self.connection.RegisterHandler('message', self.message_handler)
self.connection.RegisterHandler('iq', self.iq_handler)
# Tell the network we are online but don't ask for the roster
self.connection.sendInitPresence(1)
print "Connected."
return 0
def process(self):
"""This looks for any new messages and passes those it finds to
the assigned handling method."""
self.connection.Process()
def get_jid(self, jid=None, use=False):
"""This is a convenience method which returns the given JID if
it is not None, or else returns the object's assigned JID."""
if jid == None or use == False:
return str(self.jid)
else:
return str(jid)
def assign_message_handler(self, handler_function):
"""This causes the function handler_function to run whenever a
message of type "message" is received."""
self.message_handler_function = handler_function
def message_handler(self, connection, message):
"""Passes incoming stanzas of type "message" to the function
assigned to handle messages."""
## FIXME: Doesn't do anything at the moment
print message.__str__(1)
message = ElementTree(file=StringIO(message))
message_root = message.getroot()
self.message_handler_function(message_root)
def iq_handler(self, connection, iq):
"""Looks at every incoming Jabber iq stanza and handles them."""
# This creates an XML object out of the stanza, making it more
# manageable
stanza = ElementTree(file=StringIO(iq))
# Gets the top-level XML element of the stanza (the 'iq' one)
stanza_root = stanza.getroot()
# See if there is a stanza id and if so whether it is in the
# dictionary of stanzas awaiting replies.
if 'id' in stanza_root.attrib.keys() and stanza_root.get('id') in self.pending.keys():
# This stanza must be a reply, therefore run the function
# which is assigned to handle it
self.pending[stanza_root.get('id')][0](stanza_root, self.callbacks[stanza_root.get('id')][0])
# These won't be run again, so collect the garbage
del(self.pending[stanza_root.get('id')])
del(self.callbacks[stanza_root.get('id')])
def send(self, stanza, reply_handler=None, callback=None):
"""Sends the given stanza through the connection, giving it a
random stanza id if it doesn't have one or if the current one
is not unique. Also assigns the optional functions
'reply_handler' and 'callback' to handle replies to this stanza."""
# Get the id of this stanza if it has one,
# or else make a new random one
if 'id' in stanza.attrib.keys() and stanza.get('id') not in self.used_ids:
id = stanza.get('id')
else:
# Make a random ID which is not already used
while True:
id = ''.join(Random().sample(string.digits+string.ascii_letters, 8))
if id not in self.used_ids: break
stanza.set('id', id)
self.used_ids.append(id)
self.pending[id] = [reply_handler]
self.callbacks[id] = [callback]
self.connection.send(etree.tostring(stanza))
def get_features(self, server, return_function=None, stanza_id=None): #FIXME IDENTITY NOT HANDLED
"""Queries server (string or Server) for the XMPP features it
supports."""
# This is the kind of XML we want to send
#<iq type='get' from='us' to='them'>
# <query xmlns='http://jabber.org/protocol/disco#info' />
#</iq>
# Make it as XML
contents = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
contents.append(Element('query', attrib={'xmlns':'http://jabber.org/protocol/disco#info'}))
# This function is run when any replies are received with the
# same stanza id as a get_features stanza. The
# stanza argument is the reply stanza which has been received
def handler(stanza, to_run):
## FIXME: This handles <feature> but not <identity>
if to_run is not None:
# See if the server is not in our server_properties tree
reply = Element('reply', attrib={'id':stanza.get('id')})
# If this is an error report then say so
if stanza.attrib.get('type') == 'error':
error = SubElement(reply, 'error')
# If this is a successful reply then handle it
elif stanza.attrib.get('type') == 'result':
identities = []
features = []
for query in stanza.xpath(".//{http://jabber.org/protocol/disco#info}query"):
for identity in query.xpath("{http://jabber.org/protocol/disco#info}identity"):
# Handle identity children
## FIXME: Doesn't do anything yet
pass
for feature in query.xpath("{http://jabber.org/protocol/disco#info}feature"):
# Handle feature children, adding features to
# the server's entry in server_properties
features.append(feature.get('var'))
to_run(reply)
# Send the message and set the handler function above to deal with the reply
self.send(contents, handler, return_function)
def get_nodes(self, server, node, return_function=None, stanza_id=None):
"""Queries server (string or Server) for the top-level nodes it
contains. If node is a string or Node then its child nodes are
requested instead.
Upon reply, return_function is called with a list of Nodes which
were returned."""
# This is the kind of XML we want to send
# <iq type='get' from='us' to='them'>
# <query xmlns='http://jabber.org/protocol/disco#items'/>
#</iq>
# Make it as XML elements
contents = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
query = SubElement(contents, 'query', attrib={'xmlns':'http://jabber.org/protocol/disco#items'})
if node is not None:
query.set('node', node.name)
# This is run on any replies that are received (identified by
# their stanza id)
def handler(stanza, callback):
#<iq type='result'
# from='pubsub.shakespeare.lit'
# to='francisco@denmark.lit/barracks'
# id='nodes2'>
# <query xmlns='http://jabber.org/protocol/disco#items'
# node='blogs'>
# <item jid='pubsub.shakespeare.lit'
# node='princely_musings'/>
# <item jid='pubsub.shakespeare.lit'
# node='kingly_ravings'/>
# <item jid='pubsub.shakespeare.lit'
# node='starcrossed_stories'/>
# <item jid='pubsub.shakespeare.lit'
# node='moorish_meanderings'/>
# </query>
#</iq>
if callback is not None:
#reply = Element('reply')
reply = []
if stanza.attrib.get('type') == 'error':
# FIXME: Make this handle errors in a meaningful way
#error = SubElement(reply, 'error')
print "Error"
callback("error")
elif stanza.attrib.get('type') == 'result':
# This is run if the request has been successful
if stanza.find('.//{http://jabber.org/protocol/disco#items}query').get('node') is not None:
node_parent = Node(name=stanza.find('.//{http://jabber.org/protocol/disco#items}query').get('node'), server=Server(name=stanza.get('from')))
else:
node_parent = Server(name=stanza.get('from'))
# Go through all of the 'item' elements in the stanza
for item in stanza.findall('.//{http://jabber.org/protocol/disco#items}item'):
reply.append(Node(name=item.get('node'), jid=item.get('jid'), server=Server(name=stanza.get('from')), parent=node_parent))
callback(reply)
self.send(contents, handler, return_function)
def get_node_information(self, server, node, return_function=None, stanza_id=None): #FIXME NEEDS MOAR INFO
"""Queries node (string or Node) on server (string or Server)
for its metadata."""
#<iq type='get' from='us' to='server'>
# <query xmlns='http://jabber.org/protocol/disco#info' node='node_name'/>
#</iq>
stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
stanza.append(Element('query', attrib={'xmlns':'http://jabber.org/protocol/disco#info', 'node':str(node)}))
def handler(stanza, callback):
#print etree.tostring(stanza)
## FIXME: Much more information available
if callback is not None:
#<iq type='result'
# from='pubsub.shakespeare.lit'
# to='francisco@denmark.lit/barracks'
# id='meta1'>
# <query xmlns='http://jabber.org/protocol/disco#info'
# node='blogs'>
# ...
# <identity category='pubsub' type='collection'/>
# ...
# </query>
#</iq>
if stanza.get('type') == 'result':
node = Node(server=stanza.get('from'), name=stanza.find('{http://jabber.org/protocol/disco#info}query').get('node'))
#for element in stanza.xpath("//query"):
for element in stanza.find("{http://jabber.org/protocol/disco#info}query"):
try:
if element.get('type') == 'collection':
node.set_type('collection')
elif element.get('type') == 'leaf':
node.set_type('leaf')
except:
pass
callback(node)
#etree.tostring()
self.send(stanza, handler, return_function)
def get_items(self, server, node, return_function=None, stanza_id=None):
"""Requests the items of node (string or Node) on server (string
or Server)."""
#<iq type='get'
# from='jid'
# to='server'>
# <query xmlns='http://jabber.org/protocol/disco#items'
# node='node_name'/>
#</iq>
stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
query = SubElement(stanza, 'query', attrib={'xmlns':'http://jabber.org/protocol/disco#items', 'node':str(node)})
def handler(stanza, callback):
#<iq type='result'
# from='pubsub.shakespeare.lit'
# to='francisco@denmark.lit/barracks'
# id='items1'>
# <query xmlns='http://jabber.org/protocol/disco#items'
# node='princely_musings'>
# <item jid='pubsub.shakespeare.lit' name='368866411b877c30064a5f62b917cffe'/>
# <item jid='pubsub.shakespeare.lit' name='3300659945416e274474e469a1f0154c'/>
# <item jid='pubsub.shakespeare.lit' name='4e30f35051b7b8b42abe083742187228'/>
# <item jid='pubsub.shakespeare.lit' name='ae890ac52d0df67ed7cfdf51b644e901'/>
# </query>
#</iq>
if callback is not None:
if stanza.get('type') == 'error':
items = False
elif stanza.get('type') == 'result':
# Make an empty list to store discovered items in
items = []
# Find every 'item' element
for item in stanza.findall('.//{http://jabber.org/protocol/disco#items}item'):
# Add new Items to the items list for each
items.append(Item(jid=item.get('jid'), name=item.get('name')))
# Give the items found to the callback function
callback(items)
self.send(stanza, handler, return_function)
def get_subscriptions(self, server, node, return_function=None, stanza_id=None):
"""Redundant."""
#<iq type='get' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <subscriptions/>
# </pubsub>
#</iq>
self.retrieve_subscriptions(server, node, return_function, stanza_id)
def get_affiliations(self, server, return_function=None, stanza_id=None): #FIXME NO HANDLER
"""Requests all afilliations on server (string or Server)."""
#<iq type='get' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <affiliations/>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
affiliations = SubElement(pubsub, 'affiliations')
def handler(stanza, callback):
print etree.tostring(stanza)
self.send(stanza, handler, return_function)
def subscribe(self, server, node, jid=None, return_function=None, stanza_id=None):
"""Subscribe the current JID to node on server. If supplied, jid
will be subscribed rather than the logged-in JID.
return_function is given a single argument. This is False if there
was an error, or if it was successful it is given a list of
dictionaries of the form:
[{'server':server_URL, 'jid':subscribed_jid, 'subid':subscription_ID}, {...}, ...]"""
#<iq type='set' from='francisco@denmark.lit/barracks' to='pubsub.shakespeare.lit'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <subscribe node='princely_musings' jid='francisco@denmark.lit'/>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
subscribe = SubElement(pubsub, 'subscribe', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
def handler(stanza, callback):
#<iq xmlns="jabber:client" to="test2@localhost/subscriptions" from="pubsub.localhost" id="9r4LiyWpTOhI7z0j" type="result">
# <pubsub xmlns="http://jabber.org/protocol/pubsub">
# <subscription subid="4C1430B5BE841" node="/home" jid="test2@localhost/subscriptions" subscription="subscribed"/>
# </pubsub>
#</iq
#<iq type='result' from='pubsub.shakespeare.lit' to='francisco@denmark.lit/barracks'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <subscription node='princely_musings' jid='francisco@denmark.lit' subid='ba49252aaa4f5d320c24d3766f0bdcade78c78d3' subscription='subscribed'/>
# </pubsub>
#</iq>
if callback is not None:
if stanza.get('type') == 'error':
reply = False
elif stanza.get('type') == 'result':
reply = []
for subscription_element in stanza.xpath(".//subscription"):
reply.append({'node':subscription_element.get('node'), 'subid':subscription_element.get('subid'), 'server':stanza.get('from'), 'jid':subscription_element.get('jid')}))
callback(reply)
self.send(stanza, handler, return_function)
def unsubscribe(self, server, node, jid=None, return_function=None, stanza_id=None):
"""Unsubscribe the given jid (or if not supplied, the currently
logged-in JID) from node on server.
No reply handling yet.""" #FIXME: Add description of return_function arguments
#<iq type='set'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <unsubscribe
# node='node_name'
# jid='jid'/>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
unsubscribe = SubElement(pubsub, 'unsubscribe', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
def handler(stanza, callback):
#<iq type='result'
# from='pubsub.shakespeare.lit'
# to='francisco@denmark.lit/barracks'
# id='unsub1'/>
if callback is not None:
if stanza.get('type') == 'result':
reply = True
else:
reply = False
callback(reply)
self.send(stanza, handler, return_function)
def get_subscription_options(self, server, node, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
"""Request the subscription options of jid (or if not supplied,
the currently logged-in JID) for node on server.
No reply handling yet."""
#<iq type='get'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <options node='node_name' jid='jid'/>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
options = SubElement(pubsub, 'options', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
def handler(stanza, callback):
print etree.tostring(stanza)
self.send(stanza, handler, return_function)
def subscription_options_form_submission(self, server, node, options, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
"""Not yet implemented sanely."""
# options is the "x" Element (which should contain all of the SubElements needed)
#<iq type='set'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <options node='node_name' jid='jid'>
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
# </field>
# <field var='pubsub#deliver'><value>1</value></field>
# <field var='pubsub#digest'><value>0</value></field>
# <field var='pubsub#include_body'><value>false</value></field>
# <field var='pubsub#show-values'>
# <value>chat</value>
# <value>online</value>
# <value>away</value>
# </field>
# </x>
# </options>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
options_stanza = SubElement(pubsub, 'options', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
options.append(options)
def handler(stanza, callback):
print etree.tostring(stanza)
self.send(stanza, handler, return_function)
def subscribe_to_and_configure_a_node(self, server, node, options, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
"""Not yet implemented sanely."""
#<iq type='set'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <subscribe node='node_name' jid='jid'/>
# <options>
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
# </field>
# <field var='pubsub#deliver'><value>1</value></field>
# <field var='pubsub#digest'><value>0</value></field>
# <field var='pubsub#include_body'><value>false</value></field>
# <field var='pubsub#show-values'>
# <value>chat</value>
# <value>online</value>
# <value>away</value>
# </field>
# </x>
# </options>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
subscribe = SubElement(pubsub, 'subscribe', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
options_stanza = SubElement(pubsub, 'options')
options_stanza.append(options)
def handler(stanza, callback):
print etree.tostring(stanza)
self.send(stanza, handler, return_function)
def request_items_generic(self, server, node, specific=None, some=None, return_function=None, stanza_id=None): #FIXME NO HANDLER
"""General method used to implement item requests.
Retrieve items which have been published to node on server.
If the optional argument specific is given as a list of item IDs
then those items are retrieved.
Replies are not yet handled.
If a number is supplied as the optional argument some then it is
used as an upper limit the the number of items retrieved."""
stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
items = SubElement(pubsub, 'items', attrib={'node':str(node)})
if some is not None:
items.set('max_items', str(some))
if specific is not None:
for item in specific:
items.append(Element('item', attrib={'id':item}))
def handler(stanza, callback):
print etree.tostring(stanza)
self.send(stanza, handler, return_function)
def request_all_items(self, server, node, return_function=None, stanza_id=None):
"""Retrieve all of the items published to node on server.
Replies are not yet handled."""
#<iq type='get' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <items node='my_blog'/>
# </pubsub>
#</iq>
self.request_items_generic(server, node, return_function=return_function, stanza_id)
def request_specific_items(self, server, node, items, jid=None, return_function=None, stanza_id=None):
"""Retrieves certain items which have been published to node on
server. items is a list of item IDs.
Replies are not yet handled."""
#<iq type='get' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <items node='my_blog'>
# <item id='an_id'/>
# </items>
# </pubsub>
#</iq>
self.request_items_generic(server, node, specific=items, return_function=return_function, stanza_id)
def request_some_items(self, server, node, item_count, return_function=None, stanza_id=None):
"""Retrieves (at most) the last item_count items which have been
published to node at server.
Replies are not yet handled."""
#<iq type='get' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <items node='my_blog' max_items='5'/>
# </pubsub>
#</iq>
self.request_items_generic(server, node, some=item_count, return_function=return_function, stanza_id)
def publish(self, server, node, body, item_id=None, jid=None, return_function=None, stanza_id=None):
"""Publish body to the node node on server. If item_id is
specified then request that it be used as the item's ID.
Replies are not yet handled."""
#<iq type='set'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <publish node='node'>
# <item id='item_id'>
#.........body.............
# </entry>
# </item>
# </publish>
# </pubsub>
#</iq>
self.publish_with_options(server, node, body, item_id=item_id, jid=jid, return_function=return_function, stanza_id)
def publish_with_options(self, server, node, body, options=None, item_id=None, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
"""Generic method to implement all publishing requests.
Publishes body as an item at node on server. If item_id is
given then requests that it be used as the item's ID.
options is not implemented sanely yet.
Replies are not yet handled."""
#<iq type='set'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <publish node='node'>
# <item id='item_id'>
# ...body...
# </item>
# </publish>
# <publish-options>
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#publish-options</value>
# </field>
# <field var='pubsub#access_model'>
# <value>presence</value>
# </field>
# </x>
# </publish-options>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
publish = SubElement(pubsub, 'publish', attrib={'node':str(node)})
item = SubElement(publish, 'item')
if item_id is not None:
item.set('id', item_id)
if type(body) == type(Element):
item.append(body)
elif type(body) == type("string"):
item.text = body
if options is not None:
publish_options = SubElement(pubsub, 'publish-options')
publish_options.append(options)
def handler(stanza, callback):
#<iq type='result'
# from='pubsub.shakespeare.lit'
# to='hamlet@denmark.lit/blogbot'
# id='publish1'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <publish node='princely_musings'>
# <item id='ae890ac52d0df67ed7cfdf51b644e901'/>
# </publish>
# </pubsub>
print etree.tostring(stanza)
if callback is not None:
if stanza.get("type") == "result":
callback(0)
else:
callback(stanza)
print "Sending"
self.send(stanza, handler, return_function)
def delete_an_item_from_a_node(self, server, node, item_id, jid=None, return_function=None, stanza_id=None):
"""Removes the item with ID item_id from node at server."""
#<iq type='set'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <retract node='node'>
# <item id='item_id'/>
# </retract>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
retract = SubElement(pubsub, 'retract', attrib={'node':str(node)})
item = SubElement(retract, 'item', attrib={'id':item_id})
def handler(stanza, callback):
#<iq type='result'
# from='pubsub.shakespeare.lit'
# to='hamlet@denmark.lit/elsinore'
# id='retract1'/>
if callback is not None:
if stanza.get('type') == 'result':
result = True
else:
result = False
callback(result)
self.send(stanza, handler, return_function)
def request_node(self, server, node, type, parent, options=None, return_function=None, stanza_id=None): #FIXME A LOT
"""Asks the given server for a pubsub node. If node is None then
an instant node is made, if it is a string or Node then that is
used as the new node's ID. type can be 'collection' or 'leaf'
the type type ('collection' or 'leaf').
Not implemented completely sanely yet."""
##FIXME: Explain the other options
stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
create = SubElement(pubsub, 'create')
# Instant nodes do not need a name
if node is not None:
create.set('node', str(node))
configure = SubElement(pubsub, 'configure')
# Nodes must have an option set to show that they are collections
if type == 'collection':
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#node_config</value>
# </field>
# <field var='pubsub#node_type'><value>collection</value></field>
# </x>
x = SubElement(configure, "x", attrib={"xmlns":"jabber:x:data", "type":"submit"})
formtype_field = SubElement(x, "field", attrib={"var":"FORM_TYPE", "type":"hidden"})
formtype_value = SubElement(formtype_field, "value")
formtype_value.text = "http://jabber.org/protocol/pubsub#node_config"
nodetype_field = SubElement(x, "field", attrib={"var":"pubsub#node_type"})
nodetype_value = SubElement(nodetype_field, "value")
nodetype_value.text = "collection"
if options is not None:
configure.append(options)
def handler(stanza, callback):
#<iq type='result'
# from='pubsub.shakespeare.lit'
# to='hamlet@denmark.lit/elsinore'
# id='create1'/>
if callback is not None:
if stanza.attrib.get("type") == "error":
callback(False)
elif stanza.attrib.get("type") == "result":
callback(True)
self.send(stanza, handler, return_function)
def entity_request_instant_node(self, server, return_function=None, stanza_id=None):
"""Asks the given server for an instant node (ie. one without
a predetermined name/id)."""
#<iq type='set' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <create/>
# <configure/>
# </pubsub>
#</iq>
self.request_node(server, None, "leaf", None, None, return_function, stanza_id)
def get_new_leaf_node(self, server, node, parent, options, return_function=None, stanza_id=None):
"""Requests a new leaf node (which can store items) with name
node, as a sub-node of the node parent on server.
options is not yet implemented sanely.
Replies are not yet handled."""
#<iq type='set' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <create node='my_blog'/>
# <configure/>
# </pubsub>
#</iq>
self.request_node(server, node, "leaf", parent, options, return_function, stanza_id)
def get_new_collection_node(self, server, node, parent, options, return_function=None, stanza_id=None):
"""Requests a new collection node (which can store nodes) with
name node, as a sub-node of the node parent on server.
options is not yet implemented sanely.
Replies are not yet handled."""
#<iq type='set'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <create node='node_name'/>
# <configure>
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#node_config</value>
# </field>
# <field var='pubsub#node_type'><value>collection</value></field>
# </x>
# </configure>
# </pubsub>
#</iq>
self.request_node(server, node, "collection", parent, options, return_function, stanza_id)
def get_new_leaf_node_nondefault_access(self, server, node, access_model, return_function=None, stanza_id=None): #FIXME A LOT
"""Not yet implemented sanely."""
#<iq type='set' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <create node='my_blog'/>
# <configure>
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#node_config</value>
# </field>
# <field var='pubsub#access_model'>
# <value>open</value>
# </field>
# </x>
# </configure>
# </pubsub>
#</iq>"""
x = Element('x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
value1 = SubElement(field1, 'value')
value1.text = 'http://jabber.org/protocol/pubsub#node_config'
field2 = SubElement(x, 'field', attrib={'var':'pubsub#access_model'})
value2 = SubElement(field2, 'value')
## FIXME: Add a check here
value2.text = access_model
self.entity_request_new_node_nondefault_configuration(server, node, x, stanza_id)
def entity_request_new_node_nondefault_configuration(self, server, node, options, return_function=None, stanza_id=None): #FIXME A LOT
"""Not yet implemented sanely."""
#<iq type='set' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub'>
# <create node='my_blog'/>
# <configure>
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#node_config</value>
# </field>
# <field var='pubsub#title'>
# <value>My Blog</value>
# </field>
# </x>
# </configure>
# </pubsub>
#</iq>
self.request_node(server, node, options, return_function, stanza_id)
def node_configuration_generic(self, server, node, options, return_function=None, stanza_id=None): #FIXME A LOT
"""Not yet implemented sanely."""
stanza = Element('iq', attrib={'from':self.get_jid(), 'to':str(server)})
if options is not None:
stanza.set('type', 'set')
else:
stanza.set('type', 'get')
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
configure = SubElement(pubsub, 'configure', attrib={'node':str(node)})
if options is not None:
configure.append(options)
def handler(stanza, callback):
print etree.tostring(stanza)
self.send(stanza, handler, return_function)
def request_node_configuration_form(self, server, node, return_function=None, stanza_id=None): #FIXME NO HANDLER
"""Request a form with which to configure node on server.
Replies are not yet handled."""
#<iq type='get' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
# <configure node='my_blog'/>
# </pubsub>
#</iq>
self.node_configuration_generic(server, node, None, stanza_id)
def submit_node_configuration_form(self, server, node, options, return_function=None, stanza_id=None): #FIXME A LOT
"""Not yet implemented sanely."""
#<iq type='set' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
# <configure node='my_blog'>
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#node_config</value>
# </field>
# <field var='pubsub#title'>
# <value>Princely Musings (Atom)</value>
# </field>
# </x>
# </configure>
# </pubsub>
#</iq>
self.node_configuration_generic(server, node, options, stanza_id)
def cancel_node_configuration(self, server, node, return_function=None, stanza_id=None): #FIXME NO HANDLER
"""Retracts a request to reconfigure node on server.
Replies are not yet handled."""
#<iq type='set' from='us' to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
# <configure node='my_blog'>
# <x xmlns='jabber:x:data' type='cancel'/>
# </configure>
# </pubsub>
#</iq>
x = Element(configure, 'x', attrib={'xmlns':'jabber:x:data', 'type':'cancel'})
self.submit_node_configuration_form(server, node, x, stanza_id)
def request_default_configuration_options(self, server, return_function=None, stanza_id=None): #FIXME NO HANDLER
"""Asks server for the configuration which is used as default
for new nodes.
Replies are not yet handled."""
#<iq type='get'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
# <default/>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
default = SubElement(pubsub, 'default')
def handler(stanza, callback):
print etree.tostring(stanza)
self.send(stanza, handler, return_function)
def request_default_collection_configuration(self, server, return_function=None, stanza_id=None): #FIXME NO HANDLER
"""Ask server for the options which are applied by default to
new collection nodes.
Replies are not yet handled."""
#<iq type='get'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
# <default>
# <x xmlns='jabber:x:data' type='submit'>
# <field var='FORM_TYPE' type='hidden'>
# <value>http://jabber.org/protocol/pubsub#node_config</value>
# </field>
# <field var='pubsub#node_type'><value>collection</value></field>
# </x>
# </default>
# </pubsub>
#</iq>
stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
default = SubElement(pubsub, 'default')
x = SubElement(default, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
value1 = SubElement(field1, 'value')
value1.text = 'http://jabber.org/protocol/pubsub#node_config'
field2 = SubElement(x, 'field', attrib={'var':'pubsub#node_type'})
value2 = SubElement(field2, 'value')
value2.text = 'collection'
def handler(stanza, callback):
print etree.tostring(stanza)
self.send(stanza, handler, return_function)
def delete_a_node(self, server, node, return_function=None, stanza_id=None):
"""Delete the given node from the given server.
If given, return_function is run upon reply with a value of
True if the deletion was successful, or False if there was an
error."""
## FIXME: Give different results for different types of error.
#<iq type='set'
# from='us'
# to='them'>
# <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
# <delete node='node_name'/>