pubsubclient: 627c06f737e51a059036b7bf2c941c2218a9f8ee
1: # PubSubClient, a Python library for interacting with XMPP PubSub
2: # Copyright (C) 2008 Chris Warburton
3: #
4: # This program is free software: you can redistribute it and/or modify
5: # it under the terms of the GNU Affero General Public License as
6: # published by the Free Software Foundation, either version 3 of the
7: # License, or (at your option) any later version.
8: #
9: # This program is distributed in the hope that it will be useful,
10: # but WITHOUT ANY WARRANTY; without even the implied warranty of
11: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12: # GNU Affero General Public License for more details.
13: #
14: # You should have received a copy of the GNU Affero General Public License
15: # along with this program. If not, see <http://www.gnu.org/licenses/>.
16:
17: ### WARNING: This library is still early in development. Expected changes
18: ### include:
19: ### * Consistent naming of PubSubClient methods
20: ### * Any usage of a server URL should accept a string AND a Server
21: ### * Combine some of PubSubClient's methods into fewer, more generic ones
22: ### * Make some of PubSubClient's methods private after API stabilises
23: ### * Possible new objects: Entity? Affiliate? Option?
24:
25: import xmpp
26: import string
27: from random import Random
28: from StringIO import StringIO
29: from lxml.etree import ElementTree, Element, SubElement
30: import lxml.etree as etree
31:
32: class PubSubClient(object):
33:
34: def __init__(self, jid, password, resource='subscriptions'):
35: """Creates a new PubSubClient. jid can be a string or a JID,
36: password and resource are strings."""
37: # Store all of the information we need to connect to Jabber
38: # The user and server can be deduced from the JabberID
39: # FIXME: This is probably really insecure password storage?
40: self.password = password
41: self.resource = resource
42: if type(jid) == type("string"):
43: self.jid = JID(jid)
44: elif type(jid) == type(JID()):
45: self.jid = jid
46: self.user = self.jid.getNode()
47: self.server = self.jid.getDomain()
48: self.connection = xmpp.Client(self.server,debug=[])
49:
50: # self.pending uses the stanza id of any requests which are
51: # awaiting a response as keys, with assigned values of the
52: # predefined functions which should handle these responses.
53: # IMPORTANT: The functions are stored in a list since functions
54: # cannot be directly stored in dictionaries. Each list contains
55: # just one function, ie. {'id1':[function1], 'id2':[function2]}
56: # To call a function use self.pending[id][0](stanza)
57: self.pending = {}
58:
59: # self.callbacks works in a similar way to self.pending, except
60: # that it maps stanza ids to functions given by the programmer
61: # using the library. These functions are passed to the
62: # appropriate handler function from self.pending, and are
63: # executed by those functions when they have finished
64: # processing the replies
65: self.callbacks = {}
66:
67: # This stores the stanza ids used for this session, since ids
68: # must be unique for their session
69: self.used_ids = []
70:
71: # Assign a default printing handler for messages
72: self.assign_message_handler(self.default_handler)
73:
74: def default_handler(self, message):
75: print etree.tostring(message)
76:
77: def connect(self):
78: """Turn on the connection. Returns 1 for error, 0 for success."""
79: # First try connecting to the server
80: connection_result = self.connection.connect()
81: if not connection_result:
82: print "Could not connect to server " + str(self.server)
83: return 1
84: if connection_result != 'tls':
85: print "Warning: Could not use TLS"
86:
87: # Then try to log in
88: authorisation_result = self.connection.auth(self.user, \
89: self.password, \
90: self.resource)
91: if not authorisation_result:
92: print "Could not get authorized. Username/password problem?"
93: return 1
94: if authorisation_result != 'sasl':
95: print "Warning: Could not use SASL"
96:
97: # Here we specify which methods to run to process messages and
98: # queries
99: self.connection.RegisterHandler('message', self.message_handler)
100: self.connection.RegisterHandler('iq', self.iq_handler)
101:
102: # Tell the network we are online but don't ask for the roster
103: self.connection.sendInitPresence(1)
104:
105: print "Connected."
106: return 0
107:
108: def process(self):
109: """This looks for any new messages and passes those it finds to
110: the assigned handling method."""
111: self.connection.Process()
112:
113: def get_jid(self, jid=None, use=False):
114: """This is a convenience method which returns the given JID if
115: it is not None, or else returns the object's assigned JID."""
116: if jid == None or use == False:
117: return str(self.jid)
118: else:
119: return str(jid)
120:
121: def assign_message_handler(self, handler_function):
122: """This causes the function handler_function to run whenever a
123: message of type "message" is received."""
124: self.message_handler_function = handler_function
125:
126: def message_handler(self, connection, message):
127: """Passes incoming stanzas of type "message" to the function
128: assigned to handle messages."""
129: ## FIXME: Doesn't do anything at the moment
130: print message.__str__(1)
131: message = ElementTree(file=StringIO(message))
132: message_root = message.getroot()
133: self.message_handler_function(message_root)
134:
135: def iq_handler(self, connection, iq):
136: """Looks at every incoming Jabber iq stanza and handles them."""
137: # This creates an XML object out of the stanza, making it more
138: # manageable
139: stanza = ElementTree(file=StringIO(iq))
140: # Gets the top-level XML element of the stanza (the 'iq' one)
141: stanza_root = stanza.getroot()
142:
143: # See if there is a stanza id and if so whether it is in the
144: # dictionary of stanzas awaiting replies.
145: if 'id' in stanza_root.attrib.keys() and stanza_root.get('id') in self.pending.keys():
146: # This stanza must be a reply, therefore run the function
147: # which is assigned to handle it
148: self.pending[stanza_root.get('id')][0](stanza_root, self.callbacks[stanza_root.get('id')][0])
149: # These won't be run again, so collect the garbage
150: del(self.pending[stanza_root.get('id')])
151: del(self.callbacks[stanza_root.get('id')])
152:
153: def send(self, stanza, reply_handler=None, callback=None):
154: """Sends the given stanza through the connection, giving it a
155: random stanza id if it doesn't have one or if the current one
156: is not unique. Also assigns the optional functions
157: 'reply_handler' and 'callback' to handle replies to this stanza."""
158: # Get the id of this stanza if it has one,
159: # or else make a new random one
160: if 'id' in stanza.attrib.keys() and stanza.get('id') not in self.used_ids:
161: id = stanza.get('id')
162: else:
163: # Make a random ID which is not already used
164: while True:
165: id = ''.join(Random().sample(string.digits+string.ascii_letters, 8))
166: if id not in self.used_ids: break
167: stanza.set('id', id)
168: self.used_ids.append(id)
169: self.pending[id] = [reply_handler]
170: self.callbacks[id] = [callback]
171: self.connection.send(etree.tostring(stanza))
172:
173: def get_features(self, server, return_function=None, stanza_id=None): #FIXME IDENTITY NOT HANDLED
174: """Queries server (string or Server) for the XMPP features it
175: supports."""
176: # This is the kind of XML we want to send
177: #<iq type='get' from='us' to='them'>
178: # <query xmlns='http://jabber.org/protocol/disco#info' />
179: #</iq>
180:
181: # Make it as XML
182: contents = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
183: contents.append(Element('query', attrib={'xmlns':'http://jabber.org/protocol/disco#info'}))
184:
185: # This function is run when any replies are received with the
186: # same stanza id as a get_features stanza. The
187: # stanza argument is the reply stanza which has been received
188: def handler(stanza, to_run):
189: ## FIXME: This handles <feature> but not <identity>
190: if to_run is not None:
191: # See if the server is not in our server_properties tree
192: reply = Element('reply', attrib={'id':stanza.get('id')})
193: # If this is an error report then say so
194: if stanza.attrib.get('type') == 'error':
195: error = SubElement(reply, 'error')
196: # If this is a successful reply then handle it
197: elif stanza.attrib.get('type') == 'result':
198: identities = []
199: features = []
200: for query in stanza.xpath(".//{http://jabber.org/protocol/disco#info}query"):
201: for identity in query.xpath("{http://jabber.org/protocol/disco#info}identity"):
202: # Handle identity children
203: ## FIXME: Doesn't do anything yet
204: pass
205: for feature in query.xpath("{http://jabber.org/protocol/disco#info}feature"):
206: # Handle feature children, adding features to
207: # the server's entry in server_properties
208: features.append(feature.get('var'))
209: to_run(reply)
210:
211: # Send the message and set the handler function above to deal with the reply
212: self.send(contents, handler, return_function)
213:
214: def get_nodes(self, server, node, return_function=None, stanza_id=None):
215: """Queries server (string or Server) for the top-level nodes it
216: contains. If node is a string or Node then its child nodes are
217: requested instead.
218:
219: Upon reply, return_function is called with a list of Nodes which
220: were returned."""
221: # This is the kind of XML we want to send
222: # <iq type='get' from='us' to='them'>
223: # <query xmlns='http://jabber.org/protocol/disco#items'/>
224: #</iq>
225:
226: # Make it as XML elements
227: contents = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
228: query = SubElement(contents, 'query', attrib={'xmlns':'http://jabber.org/protocol/disco#items'})
229: if node is not None:
230: query.set('node', node.name)
231:
232: # This is run on any replies that are received (identified by
233: # their stanza id)
234: def handler(stanza, callback):
235: #<iq type='result'
236: # from='pubsub.shakespeare.lit'
237: # to='francisco@denmark.lit/barracks'
238: # id='nodes2'>
239: # <query xmlns='http://jabber.org/protocol/disco#items'
240: # node='blogs'>
241: # <item jid='pubsub.shakespeare.lit'
242: # node='princely_musings'/>
243: # <item jid='pubsub.shakespeare.lit'
244: # node='kingly_ravings'/>
245: # <item jid='pubsub.shakespeare.lit'
246: # node='starcrossed_stories'/>
247: # <item jid='pubsub.shakespeare.lit'
248: # node='moorish_meanderings'/>
249: # </query>
250: #</iq>
251: if callback is not None:
252: #reply = Element('reply')
253: reply = []
254: if stanza.attrib.get('type') == 'error':
255: # FIXME: Make this handle errors in a meaningful way
256: #error = SubElement(reply, 'error')
257: print "Error"
258: callback("error")
259: elif stanza.attrib.get('type') == 'result':
260: # This is run if the request has been successful
261: if stanza.find('.//{http://jabber.org/protocol/disco#items}query').get('node') is not None:
262: node_parent = Node(name=stanza.find('.//{http://jabber.org/protocol/disco#items}query').get('node'), server=Server(name=stanza.get('from')))
263: else:
264: node_parent = Server(name=stanza.get('from'))
265: # Go through all of the 'item' elements in the stanza
266: for item in stanza.findall('.//{http://jabber.org/protocol/disco#items}item'):
267: reply.append(Node(name=item.get('node'), jid=item.get('jid'), server=Server(name=stanza.get('from')), parent=node_parent))
268: callback(reply)
269:
270: self.send(contents, handler, return_function)
271:
272: def get_node_information(self, server, node, return_function=None, stanza_id=None): #FIXME NEEDS MOAR INFO
273: """Queries node (string or Node) on server (string or Server)
274: for its metadata."""
275: #<iq type='get' from='us' to='server'>
276: # <query xmlns='http://jabber.org/protocol/disco#info' node='node_name'/>
277: #</iq>
278:
279: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
280: stanza.append(Element('query', attrib={'xmlns':'http://jabber.org/protocol/disco#info', 'node':str(node)}))
281:
282: def handler(stanza, callback):
283: #print etree.tostring(stanza)
284: ## FIXME: Much more information available
285: if callback is not None:
286: #<iq type='result'
287: # from='pubsub.shakespeare.lit'
288: # to='francisco@denmark.lit/barracks'
289: # id='meta1'>
290: # <query xmlns='http://jabber.org/protocol/disco#info'
291: # node='blogs'>
292: # ...
293: # <identity category='pubsub' type='collection'/>
294: # ...
295: # </query>
296: #</iq>
297: if stanza.get('type') == 'result':
298: node = Node(server=stanza.get('from'), name=stanza.find('{http://jabber.org/protocol/disco#info}query').get('node'))
299: #for element in stanza.xpath("//query"):
300: for element in stanza.find("{http://jabber.org/protocol/disco#info}query"):
301: try:
302: if element.get('type') == 'collection':
303: node.set_type('collection')
304: elif element.get('type') == 'leaf':
305: node.set_type('leaf')
306: except:
307: pass
308: callback(node)
309: #etree.tostring()
310:
311: self.send(stanza, handler, return_function)
312:
313: def get_items(self, server, node, return_function=None, stanza_id=None):
314: """Requests the items of node (string or Node) on server (string
315: or Server)."""
316: #<iq type='get'
317: # from='jid'
318: # to='server'>
319: # <query xmlns='http://jabber.org/protocol/disco#items'
320: # node='node_name'/>
321: #</iq>
322:
323: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
324: query = SubElement(stanza, 'query', attrib={'xmlns':'http://jabber.org/protocol/disco#items', 'node':str(node)})
325:
326: def handler(stanza, callback):
327: #<iq type='result'
328: # from='pubsub.shakespeare.lit'
329: # to='francisco@denmark.lit/barracks'
330: # id='items1'>
331: # <query xmlns='http://jabber.org/protocol/disco#items'
332: # node='princely_musings'>
333: # <item jid='pubsub.shakespeare.lit' name='368866411b877c30064a5f62b917cffe'/>
334: # <item jid='pubsub.shakespeare.lit' name='3300659945416e274474e469a1f0154c'/>
335: # <item jid='pubsub.shakespeare.lit' name='4e30f35051b7b8b42abe083742187228'/>
336: # <item jid='pubsub.shakespeare.lit' name='ae890ac52d0df67ed7cfdf51b644e901'/>
337: # </query>
338: #</iq>
339: if callback is not None:
340: if stanza.get('type') == 'error':
341: items = False
342: elif stanza.get('type') == 'result':
343: # Make an empty list to store discovered items in
344: items = []
345: # Find every 'item' element
346: for item in stanza.findall('.//{http://jabber.org/protocol/disco#items}item'):
347: # Add new Items to the items list for each
348: items.append(Item(jid=item.get('jid'), name=item.get('name')))
349: # Give the items found to the callback function
350: callback(items)
351:
352: self.send(stanza, handler, return_function)
353:
354: def get_subscriptions(self, server, node, return_function=None, stanza_id=None):
355: """Redundant."""
356: #<iq type='get' from='us' to='them'>
357: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
358: # <subscriptions/>
359: # </pubsub>
360: #</iq>
361: self.retrieve_subscriptions(server, node, return_function, stanza_id)
362:
363: def get_affiliations(self, server, return_function=None, stanza_id=None): #FIXME NO HANDLER
364: """Requests all afilliations on server (string or Server)."""
365: #<iq type='get' from='us' to='them'>
366: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
367: # <affiliations/>
368: # </pubsub>
369: #</iq>
370: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
371: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
372: affiliations = SubElement(pubsub, 'affiliations')
373:
374: def handler(stanza, callback):
375: print etree.tostring(stanza)
376:
377: self.send(stanza, handler, return_function)
378:
379: def subscribe(self, server, node, jid=None, return_function=None, stanza_id=None):
380: """Subscribe the current JID to node on server. If supplied, jid
381: will be subscribed rather than the logged-in JID.
382:
383: return_function is given a single argument. This is False if there
384: was an error, or if it was successful it is given a list of
385: dictionaries of the form:
386:
387: [{'server':server_URL, 'jid':subscribed_jid, 'subid':subscription_ID}, {...}, ...]"""
388:
389: #<iq type='set' from='francisco@denmark.lit/barracks' to='pubsub.shakespeare.lit'>
390: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
391: # <subscribe node='princely_musings' jid='francisco@denmark.lit'/>
392: # </pubsub>
393: #</iq>
394: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
395: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
396: subscribe = SubElement(pubsub, 'subscribe', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
397:
398: def handler(stanza, callback):
399: #<iq xmlns="jabber:client" to="test2@localhost/subscriptions" from="pubsub.localhost" id="9r4LiyWpTOhI7z0j" type="result">
400: # <pubsub xmlns="http://jabber.org/protocol/pubsub">
401: # <subscription subid="4C1430B5BE841" node="/home" jid="test2@localhost/subscriptions" subscription="subscribed"/>
402: # </pubsub>
403: #</iq
404:
405: #<iq type='result' from='pubsub.shakespeare.lit' to='francisco@denmark.lit/barracks'>
406: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
407: # <subscription node='princely_musings' jid='francisco@denmark.lit' subid='ba49252aaa4f5d320c24d3766f0bdcade78c78d3' subscription='subscribed'/>
408: # </pubsub>
409: #</iq>
410:
411: if callback is not None:
412: if stanza.get('type') == 'error':
413: reply = False
414: elif stanza.get('type') == 'result':
415: reply = []
416: for subscription_element in stanza.xpath(".//subscription"):
417: reply.append({'node':subscription_element.get('node'), 'subid':subscription_element.get('subid'), 'server':stanza.get('from'), 'jid':subscription_element.get('jid')}))
418: callback(reply)
419:
420: self.send(stanza, handler, return_function)
421:
422: def unsubscribe(self, server, node, jid=None, return_function=None, stanza_id=None):
423: """Unsubscribe the given jid (or if not supplied, the currently
424: logged-in JID) from node on server.
425:
426: No reply handling yet.""" #FIXME: Add description of return_function arguments
427: #<iq type='set'
428: # from='us'
429: # to='them'>
430: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
431: # <unsubscribe
432: # node='node_name'
433: # jid='jid'/>
434: # </pubsub>
435: #</iq>
436: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
437: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
438: unsubscribe = SubElement(pubsub, 'unsubscribe', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
439:
440: def handler(stanza, callback):
441: #<iq type='result'
442: # from='pubsub.shakespeare.lit'
443: # to='francisco@denmark.lit/barracks'
444: # id='unsub1'/>
445: if callback is not None:
446: if stanza.get('type') == 'result':
447: reply = True
448: else:
449: reply = False
450: callback(reply)
451:
452: self.send(stanza, handler, return_function)
453:
454: def get_subscription_options(self, server, node, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
455: """Request the subscription options of jid (or if not supplied,
456: the currently logged-in JID) for node on server.
457:
458: No reply handling yet."""
459: #<iq type='get'
460: # from='us'
461: # to='them'>
462: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
463: # <options node='node_name' jid='jid'/>
464: # </pubsub>
465: #</iq>
466: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
467: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
468: options = SubElement(pubsub, 'options', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
469:
470: def handler(stanza, callback):
471: print etree.tostring(stanza)
472:
473: self.send(stanza, handler, return_function)
474:
475: def subscription_options_form_submission(self, server, node, options, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
476: """Not yet implemented sanely."""
477: # options is the "x" Element (which should contain all of the SubElements needed)
478: #<iq type='set'
479: # from='us'
480: # to='them'>
481: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
482: # <options node='node_name' jid='jid'>
483: # <x xmlns='jabber:x:data' type='submit'>
484: # <field var='FORM_TYPE' type='hidden'>
485: # <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
486: # </field>
487: # <field var='pubsub#deliver'><value>1</value></field>
488: # <field var='pubsub#digest'><value>0</value></field>
489: # <field var='pubsub#include_body'><value>false</value></field>
490: # <field var='pubsub#show-values'>
491: # <value>chat</value>
492: # <value>online</value>
493: # <value>away</value>
494: # </field>
495: # </x>
496: # </options>
497: # </pubsub>
498: #</iq>
499: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
500: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
501: options_stanza = SubElement(pubsub, 'options', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
502: options.append(options)
503:
504: def handler(stanza, callback):
505: print etree.tostring(stanza)
506:
507: self.send(stanza, handler, return_function)
508:
509: def subscribe_to_and_configure_a_node(self, server, node, options, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
510: """Not yet implemented sanely."""
511: #<iq type='set'
512: # from='us'
513: # to='them'>
514: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
515: # <subscribe node='node_name' jid='jid'/>
516: # <options>
517: # <x xmlns='jabber:x:data' type='submit'>
518: # <field var='FORM_TYPE' type='hidden'>
519: # <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
520: # </field>
521: # <field var='pubsub#deliver'><value>1</value></field>
522: # <field var='pubsub#digest'><value>0</value></field>
523: # <field var='pubsub#include_body'><value>false</value></field>
524: # <field var='pubsub#show-values'>
525: # <value>chat</value>
526: # <value>online</value>
527: # <value>away</value>
528: # </field>
529: # </x>
530: # </options>
531: # </pubsub>
532: #</iq>
533: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
534: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
535: subscribe = SubElement(pubsub, 'subscribe', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
536: options_stanza = SubElement(pubsub, 'options')
537: options_stanza.append(options)
538:
539: def handler(stanza, callback):
540: print etree.tostring(stanza)
541:
542: self.send(stanza, handler, return_function)
543:
544: def request_items_generic(self, server, node, specific=None, some=None, return_function=None, stanza_id=None): #FIXME NO HANDLER
545: """General method used to implement item requests.
546:
547: Retrieve items which have been published to node on server.
548:
549: If the optional argument specific is given as a list of item IDs
550: then those items are retrieved.
551:
552: Replies are not yet handled.
553:
554: If a number is supplied as the optional argument some then it is
555: used as an upper limit the the number of items retrieved."""
556: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
557: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
558: items = SubElement(pubsub, 'items', attrib={'node':str(node)})
559: if some is not None:
560: items.set('max_items', str(some))
561: if specific is not None:
562: for item in specific:
563: items.append(Element('item', attrib={'id':item}))
564:
565: def handler(stanza, callback):
566: print etree.tostring(stanza)
567:
568: self.send(stanza, handler, return_function)
569:
570: def request_all_items(self, server, node, return_function=None, stanza_id=None):
571: """Retrieve all of the items published to node on server.
572:
573: Replies are not yet handled."""
574: #<iq type='get' from='us' to='them'>
575: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
576: # <items node='my_blog'/>
577: # </pubsub>
578: #</iq>
579: self.request_items_generic(server, node, return_function=return_function, stanza_id)
580:
581: def request_specific_items(self, server, node, items, jid=None, return_function=None, stanza_id=None):
582: """Retrieves certain items which have been published to node on
583: server. items is a list of item IDs.
584:
585: Replies are not yet handled."""
586: #<iq type='get' from='us' to='them'>
587: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
588: # <items node='my_blog'>
589: # <item id='an_id'/>
590: # </items>
591: # </pubsub>
592: #</iq>
593: self.request_items_generic(server, node, specific=items, return_function=return_function, stanza_id)
594:
595: def request_some_items(self, server, node, item_count, return_function=None, stanza_id=None):
596: """Retrieves (at most) the last item_count items which have been
597: published to node at server.
598:
599: Replies are not yet handled."""
600: #<iq type='get' from='us' to='them'>
601: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
602: # <items node='my_blog' max_items='5'/>
603: # </pubsub>
604: #</iq>
605: self.request_items_generic(server, node, some=item_count, return_function=return_function, stanza_id)
606:
607: def publish(self, server, node, body, item_id=None, jid=None, return_function=None, stanza_id=None):
608: """Publish body to the node node on server. If item_id is
609: specified then request that it be used as the item's ID.
610:
611: Replies are not yet handled."""
612: #<iq type='set'
613: # from='us'
614: # to='them'>
615: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
616: # <publish node='node'>
617: # <item id='item_id'>
618: #.........body.............
619: # </entry>
620: # </item>
621: # </publish>
622: # </pubsub>
623: #</iq>
624:
625: self.publish_with_options(server, node, body, item_id=item_id, jid=jid, return_function=return_function, stanza_id)
626:
627: def publish_with_options(self, server, node, body, options=None, item_id=None, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
628: """Generic method to implement all publishing requests.
629:
630: Publishes body as an item at node on server. If item_id is
631: given then requests that it be used as the item's ID.
632:
633: options is not implemented sanely yet.
634:
635: Replies are not yet handled."""
636: #<iq type='set'
637: # from='us'
638: # to='them'>
639: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
640: # <publish node='node'>
641: # <item id='item_id'>
642: # ...body...
643: # </item>
644: # </publish>
645: # <publish-options>
646: # <x xmlns='jabber:x:data' type='submit'>
647: # <field var='FORM_TYPE' type='hidden'>
648: # <value>http://jabber.org/protocol/pubsub#publish-options</value>
649: # </field>
650: # <field var='pubsub#access_model'>
651: # <value>presence</value>
652: # </field>
653: # </x>
654: # </publish-options>
655: # </pubsub>
656: #</iq>
657: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
658: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
659: publish = SubElement(pubsub, 'publish', attrib={'node':str(node)})
660: item = SubElement(publish, 'item')
661: if item_id is not None:
662: item.set('id', item_id)
663: if type(body) == type(Element):
664: item.append(body)
665: elif type(body) == type("string"):
666: item.text = body
667: if options is not None:
668: publish_options = SubElement(pubsub, 'publish-options')
669: publish_options.append(options)
670:
671: def handler(stanza, callback):
672: #<iq type='result'
673: # from='pubsub.shakespeare.lit'
674: # to='hamlet@denmark.lit/blogbot'
675: # id='publish1'>
676: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
677: # <publish node='princely_musings'>
678: # <item id='ae890ac52d0df67ed7cfdf51b644e901'/>
679: # </publish>
680: # </pubsub>
681: print etree.tostring(stanza)
682: if callback is not None:
683: if stanza.get("type") == "result":
684: callback(0)
685: else:
686: callback(stanza)
687:
688: print "Sending"
689: self.send(stanza, handler, return_function)
690:
691: def delete_an_item_from_a_node(self, server, node, item_id, jid=None, return_function=None, stanza_id=None):
692: """Removes the item with ID item_id from node at server."""
693: #<iq type='set'
694: # from='us'
695: # to='them'>
696: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
697: # <retract node='node'>
698: # <item id='item_id'/>
699: # </retract>
700: # </pubsub>
701: #</iq>
702: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
703: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
704: retract = SubElement(pubsub, 'retract', attrib={'node':str(node)})
705: item = SubElement(retract, 'item', attrib={'id':item_id})
706:
707: def handler(stanza, callback):
708: #<iq type='result'
709: # from='pubsub.shakespeare.lit'
710: # to='hamlet@denmark.lit/elsinore'
711: # id='retract1'/>
712: if callback is not None:
713: if stanza.get('type') == 'result':
714: result = True
715: else:
716: result = False
717: callback(result)
718:
719: self.send(stanza, handler, return_function)
720:
721: def request_node(self, server, node, type, parent, options=None, return_function=None, stanza_id=None): #FIXME A LOT
722: """Asks the given server for a pubsub node. If node is None then
723: an instant node is made, if it is a string or Node then that is
724: used as the new node's ID. type can be 'collection' or 'leaf'
725: the type type ('collection' or 'leaf').
726:
727: Not implemented completely sanely yet."""
728: ##FIXME: Explain the other options
729: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
730: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
731: create = SubElement(pubsub, 'create')
732:
733: # Instant nodes do not need a name
734: if node is not None:
735: create.set('node', str(node))
736:
737: configure = SubElement(pubsub, 'configure')
738:
739: # Nodes must have an option set to show that they are collections
740: if type == 'collection':
741: # <x xmlns='jabber:x:data' type='submit'>
742: # <field var='FORM_TYPE' type='hidden'>
743: # <value>http://jabber.org/protocol/pubsub#node_config</value>
744: # </field>
745: # <field var='pubsub#node_type'><value>collection</value></field>
746: # </x>
747: x = SubElement(configure, "x", attrib={"xmlns":"jabber:x:data", "type":"submit"})
748: formtype_field = SubElement(x, "field", attrib={"var":"FORM_TYPE", "type":"hidden"})
749: formtype_value = SubElement(formtype_field, "value")
750: formtype_value.text = "http://jabber.org/protocol/pubsub#node_config"
751: nodetype_field = SubElement(x, "field", attrib={"var":"pubsub#node_type"})
752: nodetype_value = SubElement(nodetype_field, "value")
753: nodetype_value.text = "collection"
754:
755: if options is not None:
756: configure.append(options)
757:
758: def handler(stanza, callback):
759: #<iq type='result'
760: # from='pubsub.shakespeare.lit'
761: # to='hamlet@denmark.lit/elsinore'
762: # id='create1'/>
763: if callback is not None:
764: if stanza.attrib.get("type") == "error":
765: callback(False)
766: elif stanza.attrib.get("type") == "result":
767: callback(True)
768:
769: self.send(stanza, handler, return_function)
770:
771: def entity_request_instant_node(self, server, return_function=None, stanza_id=None):
772: """Asks the given server for an instant node (ie. one without
773: a predetermined name/id)."""
774: #<iq type='set' from='us' to='them'>
775: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
776: # <create/>
777: # <configure/>
778: # </pubsub>
779: #</iq>
780: self.request_node(server, None, "leaf", None, None, return_function, stanza_id)
781:
782: def get_new_leaf_node(self, server, node, parent, options, return_function=None, stanza_id=None):
783: """Requests a new leaf node (which can store items) with name
784: node, as a sub-node of the node parent on server.
785:
786: options is not yet implemented sanely.
787:
788: Replies are not yet handled."""
789: #<iq type='set' from='us' to='them'>
790: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
791: # <create node='my_blog'/>
792: # <configure/>
793: # </pubsub>
794: #</iq>
795: self.request_node(server, node, "leaf", parent, options, return_function, stanza_id)
796:
797: def get_new_collection_node(self, server, node, parent, options, return_function=None, stanza_id=None):
798: """Requests a new collection node (which can store nodes) with
799: name node, as a sub-node of the node parent on server.
800:
801: options is not yet implemented sanely.
802:
803: Replies are not yet handled."""
804: #<iq type='set'
805: # from='us'
806: # to='them'>
807: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
808: # <create node='node_name'/>
809: # <configure>
810: # <x xmlns='jabber:x:data' type='submit'>
811: # <field var='FORM_TYPE' type='hidden'>
812: # <value>http://jabber.org/protocol/pubsub#node_config</value>
813: # </field>
814: # <field var='pubsub#node_type'><value>collection</value></field>
815: # </x>
816: # </configure>
817: # </pubsub>
818: #</iq>
819: self.request_node(server, node, "collection", parent, options, return_function, stanza_id)
820:
821: def get_new_leaf_node_nondefault_access(self, server, node, access_model, return_function=None, stanza_id=None): #FIXME A LOT
822: """Not yet implemented sanely."""
823: #<iq type='set' from='us' to='them'>
824: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
825: # <create node='my_blog'/>
826: # <configure>
827: # <x xmlns='jabber:x:data' type='submit'>
828: # <field var='FORM_TYPE' type='hidden'>
829: # <value>http://jabber.org/protocol/pubsub#node_config</value>
830: # </field>
831: # <field var='pubsub#access_model'>
832: # <value>open</value>
833: # </field>
834: # </x>
835: # </configure>
836: # </pubsub>
837: #</iq>"""
838:
839: x = Element('x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
840: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
841: value1 = SubElement(field1, 'value')
842: value1.text = 'http://jabber.org/protocol/pubsub#node_config'
843: field2 = SubElement(x, 'field', attrib={'var':'pubsub#access_model'})
844: value2 = SubElement(field2, 'value')
845: ## FIXME: Add a check here
846: value2.text = access_model
847:
848: self.entity_request_new_node_nondefault_configuration(server, node, x, stanza_id)
849:
850: def entity_request_new_node_nondefault_configuration(self, server, node, options, return_function=None, stanza_id=None): #FIXME A LOT
851: """Not yet implemented sanely."""
852: #<iq type='set' from='us' to='them'>
853: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
854: # <create node='my_blog'/>
855: # <configure>
856: # <x xmlns='jabber:x:data' type='submit'>
857: # <field var='FORM_TYPE' type='hidden'>
858: # <value>http://jabber.org/protocol/pubsub#node_config</value>
859: # </field>
860: # <field var='pubsub#title'>
861: # <value>My Blog</value>
862: # </field>
863: # </x>
864: # </configure>
865: # </pubsub>
866: #</iq>
867:
868: self.request_node(server, node, options, return_function, stanza_id)
869:
870: def node_configuration_generic(self, server, node, options, return_function=None, stanza_id=None): #FIXME A LOT
871: """Not yet implemented sanely."""
872: stanza = Element('iq', attrib={'from':self.get_jid(), 'to':str(server)})
873: if options is not None:
874: stanza.set('type', 'set')
875: else:
876: stanza.set('type', 'get')
877: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
878: configure = SubElement(pubsub, 'configure', attrib={'node':str(node)})
879: if options is not None:
880: configure.append(options)
881:
882: def handler(stanza, callback):
883: print etree.tostring(stanza)
884:
885: self.send(stanza, handler, return_function)
886:
887: def request_node_configuration_form(self, server, node, return_function=None, stanza_id=None): #FIXME NO HANDLER
888: """Request a form with which to configure node on server.
889:
890: Replies are not yet handled."""
891: #<iq type='get' from='us' to='them'>
892: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
893: # <configure node='my_blog'/>
894: # </pubsub>
895: #</iq>
896:
897: self.node_configuration_generic(server, node, None, stanza_id)
898:
899: def submit_node_configuration_form(self, server, node, options, return_function=None, stanza_id=None): #FIXME A LOT
900: """Not yet implemented sanely."""
901: #<iq type='set' from='us' to='them'>
902: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
903: # <configure node='my_blog'>
904: # <x xmlns='jabber:x:data' type='submit'>
905: # <field var='FORM_TYPE' type='hidden'>
906: # <value>http://jabber.org/protocol/pubsub#node_config</value>
907: # </field>
908: # <field var='pubsub#title'>
909: # <value>Princely Musings (Atom)</value>
910: # </field>
911: # </x>
912: # </configure>
913: # </pubsub>
914: #</iq>
915:
916: self.node_configuration_generic(server, node, options, stanza_id)
917:
918: def cancel_node_configuration(self, server, node, return_function=None, stanza_id=None): #FIXME NO HANDLER
919: """Retracts a request to reconfigure node on server.
920:
921: Replies are not yet handled."""
922: #<iq type='set' from='us' to='them'>
923: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
924: # <configure node='my_blog'>
925: # <x xmlns='jabber:x:data' type='cancel'/>
926: # </configure>
927: # </pubsub>
928: #</iq>
929: x = Element(configure, 'x', attrib={'xmlns':'jabber:x:data', 'type':'cancel'})
930:
931: self.submit_node_configuration_form(server, node, x, stanza_id)
932:
933: def request_default_configuration_options(self, server, return_function=None, stanza_id=None): #FIXME NO HANDLER
934: """Asks server for the configuration which is used as default
935: for new nodes.
936:
937: Replies are not yet handled."""
938: #<iq type='get'
939: # from='us'
940: # to='them'>
941: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
942: # <default/>
943: # </pubsub>
944: #</iq>
945: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
946: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
947: default = SubElement(pubsub, 'default')
948:
949: def handler(stanza, callback):
950: print etree.tostring(stanza)
951:
952: self.send(stanza, handler, return_function)
953:
954: def request_default_collection_configuration(self, server, return_function=None, stanza_id=None): #FIXME NO HANDLER
955: """Ask server for the options which are applied by default to
956: new collection nodes.
957:
958: Replies are not yet handled."""
959: #<iq type='get'
960: # from='us'
961: # to='them'>
962: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
963: # <default>
964: # <x xmlns='jabber:x:data' type='submit'>
965: # <field var='FORM_TYPE' type='hidden'>
966: # <value>http://jabber.org/protocol/pubsub#node_config</value>
967: # </field>
968: # <field var='pubsub#node_type'><value>collection</value></field>
969: # </x>
970: # </default>
971: # </pubsub>
972: #</iq>
973: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
974: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
975: default = SubElement(pubsub, 'default')
976: x = SubElement(default, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
977: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
978: value1 = SubElement(field1, 'value')
979: value1.text = 'http://jabber.org/protocol/pubsub#node_config'
980: field2 = SubElement(x, 'field', attrib={'var':'pubsub#node_type'})
981: value2 = SubElement(field2, 'value')
982: value2.text = 'collection'
983:
984: def handler(stanza, callback):
985: print etree.tostring(stanza)
986:
987: self.send(stanza, handler, return_function)
988:
989: def delete_a_node(self, server, node, return_function=None, stanza_id=None):
990: """Delete the given node from the given server.
991:
992: If given, return_function is run upon reply with a value of
993: True if the deletion was successful, or False if there was an
994: error."""
995: ## FIXME: Give different results for different types of error.
996: #<iq type='set'
997: # from='us'
998: # to='them'>
999: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1000: # <delete node='node_name'/>
1001: # </pubsub>
1002: #</iq>
1003: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1004: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1005: delete = SubElement(pubsub, 'delete', attrib={'node':str(node)})
1006:
1007: def handler(stanza, callback):
1008: #<iq type='result'
1009: # from='pubsub.shakespeare.lit'
1010: # id='delete1'/>
1011: if callback is not None:
1012: if stanza.get("type") == "result":
1013: callback(True)
1014: elif stanza.get("type") == "error":
1015: callback(False)
1016:
1017:
1018: self.send(stanza, handler, return_function)
1019:
1020: def purge_all_items_from_a_node(self, server, node, return_function=None, stanza_id=None):
1021: """Remove every item which has been published to node on server."""
1022: #<iq type='set'
1023: # from='us'
1024: # to='them'>
1025: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1026: # <purge node='node_name'/>
1027: # </pubsub>
1028: #</iq>
1029: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1030: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1031: purge = SubElement(pubsub, 'purge', attrib={'node':str(node)})
1032:
1033: def handler(stanza, callback):
1034: #<iq type='result'
1035: # from='pubsub.shakespeare.lit'
1036: # id='purge1'/>
1037: if callback is not None:
1038: if stanza.get('type') == 'result':
1039: reply = True
1040: else:
1041: reply = False
1042: callback(reply)
1043:
1044: self.send(stanza, handler, return_function)
1045:
1046: def request_pending_subscription_requests(self, server, return_function=None, stanza_id=None): #FIXME NO HANDLER
1047: """Get every relevant request for subscription for nodes at
1048: server.
1049:
1050: Replies are not yet handled."""
1051: #<iq type='set'
1052: # from='us'
1053: # to='them'>
1054: # <command xmlns='http://jabber.org/protocol/commands'
1055: # node='http://jabber.org/protocol/pubsub#get-pending'
1056: # action='execute'/>
1057: #</iq>"""
1058: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1059: command = SubElement(stanza, 'command', attrib={'xmlns':'http://jabber.org/protocol/commands', 'node':'http://jabber.org/protocol/pubsub#get-pending', 'action':'execute'})
1060:
1061: def handler(stanza, callback):
1062: #<iq type='result'
1063: # from='pubsub.shakespeare.lit'
1064: # to='hamlet@denmark.lit/elsinore'
1065: # id='pending1'>
1066: # <command xmlns='http://jabber.org/protocol/commands'
1067: # sessionid='pubsub-get-pending:20031021T150901Z-600'
1068: # node='http://jabber.org/protocol/pubsub#get-pending'
1069: # status='executing'
1070: # action='execute'>
1071: # <x xmlns='jabber:x:data' type='form'>
1072: # <field type='list-single' var='pubsub#node'>
1073: # <option><value>princely_musings</value></option>
1074: # <option><value>news_from_elsinore</value></option>
1075: # </field>
1076: # </x>
1077: # </command>
1078: #</iq>
1079: print etree.tostring(stanza)
1080:
1081: self.send(stanza, handler, return_function)
1082:
1083: def request_pending_subscription_requests_for_a_node(self, server, node, time, return_function=None, stanza_id=None): #FIXME A LOT
1084: """Ask server for every subscription request which has not yet
1085: been handled for node.
1086:
1087: Not yet implemented sanely."""
1088: ## FIXME: Check spec, no "from"??!
1089: #<iq type='set' to='them'>
1090: # <command xmlns='http://jabber.org/protocol/commands'
1091: # sessionid='pubsub-get-pending:20031021T150901Z-600'
1092: # node='http://jabber.org/protocol/pubsub#get-pending'
1093: # action='execute'>
1094: # <x xmlns='jabber:x:data' type='submit'>
1095: # <field var='pubsub#node'>
1096: # <value>node_name</value>
1097: # </field>
1098: # </x>
1099: # </command>
1100: #</iq>
1101: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1102: command = SubElement(stanza, 'command', attrib={'xmlns':'http://jabber.org/protocol/commands', 'sessionid':'pubsub-get-pending:' + time, 'node':'http://jabber.org/protocol/pubsub#get-pending', 'action':'execute'})
1103: x = SubElement(command, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
1104: field1 = SubElement(x, 'field', attrib={'var':'pubsub#node'})
1105: value1 = SubElement(field1, 'value')
1106: value1.text = str(node)
1107:
1108: def handler(stanza, callback):
1109: print etree.tostring(stanza)
1110:
1111: self.send(stanza, handler, return_function)
1112:
1113: def request_all_subscriptions(self, server, node, return_function=None, stanza_id=None):
1114: """Redundant"""
1115: #<iq type='get'
1116: # from='us'
1117: # to='them'>
1118: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1119: # <subscriptions node='node_name'/>
1120: # </pubsub>
1121: #</iq>
1122: self.retrieve_subscriptions(server, node, return_function, stanza_id)
1123:
1124: def modify_subscriptions(self, server, node, subscriptions, return_function=None, stanza_id=None): #FIXME A LOT
1125: """Not yet implemented in a sane way."""
1126: #<iq type='set'
1127: # from='them'
1128: # to='us'>
1129: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1130: # <subscriptions node='node_name'>
1131: # <subscription jid='current_jid' subscription='subscribed'/>
1132: # </subscriptions>
1133: # </pubsub>
1134: #</iq>
1135: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
1136: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1137: subscriptions = SubElement(pubsub, 'subscriptions', attrib={'node':str(node)})
1138: for current_jid in subscriptions.keys():
1139: subscriptions.append(Element('subscription', attrib={'jid':current_jid, 'subscription':subscriptions[current_jid]}))
1140:
1141: def handler(stanza, callback):
1142: print etree.tostring(stanza)
1143:
1144: self.send(stanza, handler, return_function)
1145:
1146: def multiple_simultaneous_modifications(self, server, node, subscriptions, return_function=None, stanza_id=None): #FIXME A LOT
1147: """Not yet implemented in a sane way."""
1148: #<iq type='set'
1149: # from='us'
1150: # to='them'>
1151: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1152: # <subscriptions node='node_name'>
1153: # <subscription jid='current_subscription' subscription='subscribed'/>
1154: # </subscriptions>
1155: # </pubsub>
1156: #</iq>
1157: stanza = ElementTree('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1158: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1159: subscriptions = SubElement(pubsub, 'subscriptions', attrib={'node':str(node)})
1160: for current_subscription in subscriptions.keys():
1161: subscriptions.append(Element('subscription', attrib={'jid':current_subscription, 'subscription':subscriptions[current_subscription]}))
1162:
1163: def handler(stanza, callback):
1164: print etree.tostring(stanza)
1165:
1166: self.send(stanza, handler, return_function)
1167:
1168: def request_all_affiliated_entities(self, server, node, return_function=None, stanza_id=None):
1169: """Asks server for all entities (JIDs) affiliated in some way to
1170: node.
1171:
1172: An affiliation is owner, publisher or outcast. It does not
1173: include subscribers.
1174:
1175: If return_function is given, it is called upon reply with one
1176: argument of False if there was an error, or if it was successful
1177: a dictionary of the form:
1178:
1179: {'publisher':[JID1, JID2, ...], 'owner':[JID3, JID4, ...], ...}"""
1180: #<iq type='get'
1181: # from='us'
1182: # to='them'>
1183: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1184: # <affiliations node='node'/>
1185: # </pubsub>
1186: #</iq>
1187: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)})
1188: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1189: affiliations = SubElement(pubsub, 'affiliations', attrib={'node':str(node)})
1190:
1191: def handler(stanza, callback):
1192: #<iq xmlns="jabber:client"
1193: # to="test1@localhost/subscriptions"
1194: # from="pubsub.localhost"
1195: # type="result">
1196: # <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
1197: # <affiliations node="/home/localhost/test1">
1198: # <affiliation affiliation="owner" jid="test1@localhost"/>
1199: # </affiliations>
1200: # </pubsub>
1201: #</iq>
1202: if callback is not None:
1203: if stanza.get('type') == 'error':
1204: affiliation_dictionary = False
1205: elif stanza.get('type') == 'result':
1206: affiliations = stanza.find('.//{http://jabber.org/protocol/pubsub#owner}affiliations')
1207: affiliation_dictionary = {}
1208: if affiliations is not None:
1209: for affiliation in affiliations:
1210: if not affiliation.get("affiliation") in affiliation_dictionary.keys():
1211: affiliation_dictionary[affiliation.get("affiliation")] = []
1212: affiliation_dictionary[affiliation.get("affiliation")].append(JID(affiliation.get("jid")))
1213: callback(affiliation_dictionary)
1214:
1215: self.send(stanza, handler, return_function)
1216:
1217: def modify_affiliation(self, server, node, affiliations, return_function=None, stanza_id=None):
1218: """Tells server to change the affiliation of some entities to
1219: the node node. affiliations is a dictionary of the form:
1220:
1221: {JID1:affiliation_type, JID2:affiliation_type, ...}
1222: """
1223: #<iq type='set'
1224: # from='us'
1225: # to='them'>
1226: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1227: # <affiliations node='node'/>
1228: # <affiliation jid='current_affiliation' affiliation='affiliations'/>
1229: # </affiliations>
1230: # </pubsub>
1231: #</iq>
1232: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1233: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1234: affiliations_element = SubElement(pubsub, 'affiliations', attrib={'node':str(node)})
1235: for current_affiliation in affiliations.keys():
1236: affiliations_element.append(Element('affiliation', attrib={'jid':str(current_affiliation), 'affiliation':affiliations[current_affiliation]}))
1237:
1238: def handler(stanza, callback):
1239: if callback is not None:
1240: if stanza.get("type") == "result":
1241: reply = True
1242: else:
1243: reply = False
1244: callback(reply)
1245:
1246: self.send(stanza, handler, return_function)
1247:
1248: def subscribe_to_a_collection_node(self, server, node, jid, return_function=None, stanza_id=None): #FIXME REDUNDANT?
1249: """Not yet implemented in a sane way"""
1250: #<iq type='set'
1251: # from='us'
1252: # to='them'>
1253: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1254: # <subscribe jid='jid'
1255: # node='node_name'/>
1256: # </pubsub>
1257: #</iq>
1258: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1259: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
1260: subscribe = SubElement(pubsub, 'subscribe', attrib={'jid':self.get_jid(jid, True), 'node':str(node)})
1261:
1262: def handler(stanza, callback):
1263: print etree.tostring(stanza)
1264:
1265: self.send(stanza, handler, return_function)
1266:
1267: def subscribe_to_collection_node_with_configuration(self, server, node, options, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
1268: """Not yet implemented in a sane way."""
1269: #<iq type='set'
1270: # from='us'
1271: # to='them'>
1272: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1273: # <subscribe jid='jid'
1274: # node='node_name'/>
1275: # <options>
1276: # <x xmlns='jabber:x:data' type='submit'>
1277: # <field var='FORM_TYPE' type='hidden'>
1278: # <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
1279: # </field>
1280: # <field var='pubsub#subscription_type'>
1281: # <value>items</value>
1282: # </field>
1283: # <field var='pubsub#subscription_depth'>
1284: # <value>all</value>
1285: # </field>
1286: # </x>
1287: # </options>
1288: # </pubsub>
1289: #</iq>
1290: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1291: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
1292: subscribe = SubElement(pubsub, 'subscribe', attrib={'jid':self.get_jid(jid, True), 'node':str(node)})
1293: options_element = SubElement(pubsub, 'options')
1294: options_element.append(options)
1295:
1296: def handler(stanza, callback):
1297: print etree.tostring(stanza)
1298:
1299: self.send(stanza, handler, return_function)
1300:
1301: def subscribe_to_root_collection_node(self, server, jid=None, return_function=None, stanza_id=None): #FIXME NO HANDLER XEP-0248?
1302: """Subscribe jid (or, if not supplied, the currently logged-in
1303: JID) to the root collection node (the node which contains all
1304: of the nodes) on server.
1305:
1306: Replies are not yet handled."""
1307: #<iq type='set'
1308: # from='us'
1309: # to='them'>
1310: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1311: # <subscribe jid='jid'/>
1312: # </pubsub>
1313: #</iq>
1314: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1315: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
1316: subscribe = SubElement(pubsub, 'subscribe', attrib={'jid':self.get_jid(jid, True)})
1317:
1318: def handler(stanza, callback):
1319: print etree.tostring(stanza)
1320:
1321: self.send(stanza, handler, return_function)
1322:
1323: def create_a_new_node_associated_with_a_collection(self, server, node, collection, return_function=None, stanza_id=None): #FIXME NO HANDLER
1324: """Request a new node with name node on server server. The new
1325: node will be a sub-node of the node collection.
1326:
1327: Results are not yet handled."""
1328: #<iq type='set'
1329: # from='us'
1330: # to='them'>
1331: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1332: # <create node='node_name'/>
1333: # <configure>
1334: # <x xmlns='jabber:x:data' type='submit'>
1335: # <field var='pubsub#collection'><value>collection_name</value></field>
1336: # </x>
1337: # </configure>
1338: # </pubsub>
1339: #</iq>
1340: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1341: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
1342: create = SubElement(pubsub, 'create', attrib={'node':str(node)})
1343: configure = SubElement(pubsub, 'configure')
1344: x = SubElement(configure, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
1345: field1 = SubElement(x, 'field', attrib={'var':'pubsub#collection'})
1346: value1 = SubElement(field1, 'value')
1347: value1.text = str(collection)
1348:
1349: def handler(stanza, callback):
1350: print etree.tostring(stanza)
1351:
1352: self.send(stanza, handler, return_function)
1353:
1354: def modify_node_configuration(self, server, node, collection, return_function=None, stanza_id=None): #FIXME A LOT
1355: """Not yet implemented sanely."""
1356: # Sets node_name as member of collection_name
1357: #<iq type='set'
1358: # from='us'
1359: # to='them'>
1360: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1361: # <configure node='node'>
1362: # <x xmlns='jabber:x:data' type='submit'>
1363: # <field var='FORM_TYPE' type='hidden'>
1364: # <value>http://jabber.org/protocol/pubsub#node_config</value>
1365: # </field>
1366: # <field var='pubsub#collection'><value>collection</value></field>
1367: # </x>
1368: # </configure>
1369: # </pubsub>
1370: #</iq>
1371: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1372: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1373: configure = SubElement(pubsub, 'configure', attrib={'node':str(node)})
1374: x = SubElement(configure, 'x', attrib={'xmlns':'jaber:x:data', 'type':'submit'})
1375: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
1376: value1 = SubElement(field1, 'value')
1377: value1.text = 'http://jabber.org/protocol/pubsub#node_config'
1378: field2 = SubElement(x, 'field', attrib={'var':'pubsub#collection'})
1379: value2 = SubElement(field2, 'value')
1380: value2.text = str(collection)
1381:
1382: def handler(stanza, callback):
1383: print etree.tostring(stanza)
1384:
1385: self.send(stanza, handler, return_function)
1386:
1387: def modify_collection_configuration(self, server, node, collection, return_function=None, stanza_id=None): #FIXME A LOT
1388: """Not yet implemented sanely."""
1389: # Make node_name a child of collection_name
1390: ## FIXME: This MUST include the current children too, but doesn't at the mo'
1391: #<iq type='set'
1392: # from='us'
1393: # to='them'>
1394: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1395: # <configure node='collection'>
1396: # <x xmlns='jabber:x:data' type='submit'>
1397: # <field var='FORM_TYPE' type='hidden'>
1398: # <value>http://jabber.org/protocol/pubsub#node_config</value>
1399: # </field>
1400: # <field var='pubsub#children'>
1401: # <value>node</value>
1402: # </field>
1403: # </x>
1404: # </configure>
1405: # </pubsub>
1406: #</iq>
1407: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1408: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1409: configure = SubElement(pubsub, 'configure', attrib={'node':str(collection)})
1410: x = SubElement(configure, 'x', attrib={'xmlns':'jaber:x:data', 'type':'submit'})
1411: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
1412: value1 = SubElement(field1, 'value')
1413: value1.text = 'http://jabber.org/protocol/pubsub#node_config'
1414: field2 = SubElement(x, 'field', attrib={'var':'pubsub#children'})
1415: value2 = SubElement(field2, 'value')
1416: value2.text = str(node)
1417:
1418: def handler(stanza, callback):
1419: print etree.tostring(stanza)
1420:
1421: self.send(stanza, handler, return_function)
1422:
1423: def disassociate_collection_from_a_node(self, server, node, return_function=None, stanza_id=None): #FIXME A LOT
1424: """Not yet implemented sanely"""
1425: ## FIXME: This disassociates from EVERY collection. Should be able to specify collections
1426: #<iq type='set'
1427: # from='us'
1428: # to='them'>
1429: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1430: # <configure node='node'>
1431: # <x xmlns='jabber:x:data' type='submit'>
1432: # <field var='FORM_TYPE' type='hidden'>
1433: # <value>http://jabber.org/protocol/pubsub#node_config</value>
1434: # </field>
1435: # <field var='pubsub#collection'><value></value></field>
1436: # </x>
1437: # </configure>
1438: # </pubsub>
1439: #</iq>
1440: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1441: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1442: configure = SubElement(pubsub, 'configure', attrib={'node':str(node)})
1443: x = SubElement(configure, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
1444: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
1445: value1 = SubElement(field1, 'value')
1446: value1.text = 'http://jabber.org/protocol/pubsub#node_config'
1447: field2 = SubElement(x, 'field', attrib={'var':'pubsub#collection'})
1448: value2 = SubElement(field2, 'value')
1449: value2.text = ''
1450:
1451: def handler(stanza, callback):
1452: print etree.tostring(stanza)
1453:
1454: self.send(stanza, handler, return_function)
1455:
1456: def disassociate_node_from_a_collection(self, server, node, collection, return_function=None, stanza_id=None): #FIXME A LOT
1457: """Not implemented sanely yet"""
1458: ## FIXME: Needs fixing badly. This clears all children from the node
1459: #<iq type='set'
1460: # from='us'
1461: # to='them'>
1462: # <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
1463: # <configure node='collection'>
1464: # <x xmlns='jabber:x:data' type='submit'>
1465: # <field var='FORM_TYPE' type='hidden'>
1466: # <value>http://jabber.org/protocol/pubsub#node_config</value>
1467: # </field>
1468: # <field var='pubsub#children'>
1469: # <value>add children here, minus node</value>
1470: # </field>
1471: # </x>
1472: # </configure>
1473: # </pubsub>
1474: #</iq>
1475: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1476: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#owner'})
1477: configure = SubElement(pubsub, 'configure', attrib={'node':str(collection)})
1478: x = SubElement(configure, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
1479: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
1480: value1 = SubElement(field1, 'value')
1481: value1.text = 'http://jabber.org/protocol/pubsub#node_config'
1482: field2 = SubElement(x, 'field', attrib={'var':'pubsub#children'})
1483: value2 = SubElement(field2, 'value')
1484: value2.text = '' # sould be all current children, except for node
1485:
1486: def handler(stanza, callback):
1487: print etree.tostring(stanza)
1488:
1489: self.send(stanza, handler, return_function)
1490:
1491: def time_based_subscribe(self, server, node, expire_time, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
1492: """Not yet implemented sanely."""
1493: #<iq type='set'
1494: # from='us'
1495: # to='them'>
1496: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1497: # <options node='node_name' jid='jid'>
1498: # <x xmlns='jabber:x:data' type='submit'>
1499: # <field var='FORM_TYPE' type='hidden'>
1500: # <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
1501: # </field>
1502: # <field var='pubsub#expire'><value>expire_time</value></field>
1503: # </x>
1504: # </options>
1505: # </pubsub>
1506: #</iq>
1507: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1508: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub#subscription_options'})
1509: options = SubElement(pubsub, 'options', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
1510: x = SubElement(options, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
1511: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
1512: value1 = SubElement(field1, 'value')
1513: value1.text = 'http://jabber.org/protocol/pubsub#subscribe_options'
1514: field2 = SubElement(x, 'field', attrib={'var':'pubsub#expire'})
1515: value2 = SubElement(field2, 'value')
1516: value2.text = expire_time
1517:
1518: def handler(stanza, callback):
1519: print etree.tostring(stanza)
1520:
1521: self.send(stanza, handler, return_function)
1522:
1523: def renew_lease(self, server, node, new_expire, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
1524: """Not yet implemented sanely."""
1525: #<iq type='set'
1526: # from='us'
1527: # to='them'>
1528: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1529: # <options node='node_name' jid='jid'>
1530: # <x xmlns='jabber:x:data' type='submit'>
1531: # <field var='FORM_TYPE' type='hidden'>
1532: # <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
1533: # </field>
1534: # <field var='pubsub#expire'><value>new_expire</value></field>
1535: # </x>
1536: # </options>
1537: # </pubsub>
1538: #</iq>
1539: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1540: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
1541: options = SubElement(pubsub, 'options', attrib={'node':str(node), 'jid':self.get_jid(jid, True)})
1542: x = SubElement(options, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
1543: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
1544: value1 = SubElement(field1, 'value')
1545: value1.text = 'http://jabber.org/protocol/pubsub#subscribe_options'
1546: field2 = SubElement(x, 'field', attrib={'var':'pubsub#expire'})
1547: value2 = SubElement(field2, 'value')
1548: value2.text = new_expire
1549:
1550: def handler(stanza, callback):
1551: print etree.tostring(stanza)
1552:
1553: self.send(stanza, handler, return_function)
1554:
1555: def keyword_filtered_subscription(self, server, node, subid, filters, jid=None, return_function=None, stanza_id=None): #FIXME A LOT
1556: """Not yet implemented sanely."""
1557: #<iq type='set'
1558: # from='bard@shakespeare.lit/globe'
1559: # to='pubsub.shakespeare.lit'
1560: # id='filter3'>
1561: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1562: # <options node='node'
1563: # jid='ourjid'
1564: # subid='subid'>
1565: # <x xmlns='jabber:x:data' type='submit'>
1566: # <field var='FORM_TYPE' type='hidden'>
1567: # <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
1568: # </field>
1569: # <field var='http://shakespeare.lit/search#keyword'><value>filters</value></field>
1570: # </x>
1571: # </options>
1572: # </pubsub>
1573: #</iq>
1574: stanza = Element('iq', attrib={'type':'set', 'from':self.get_jid(), 'to':str(server)})
1575: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
1576: options = SubElement(pubsub, 'options', attrib={'node':str(node), 'jid':self.get_jid(jid, True), 'subid':subid})
1577: x = SubElement(options, 'x', attrib={'xmlns':'jabber:x:data', 'type':'submit'})
1578: field1 = SubElement(x, 'field', attrib={'var':'FORM_TYPE', 'type':'hidden'})
1579: value1 = SubElement(field1, 'value')
1580: value1.text = 'http://jabber.org/protocol/pubsub#subscribe_options'
1581: filter_field = SubElement(x, 'field', attrib={'var':'http://shakespeare.lit/search#keyword'})
1582: filter_value = SubElement(filter_field, "value")
1583: filter_value.text = filters ## FIXME: Desperately :P
1584:
1585: def handler(stanza, callback):
1586: print etree.tostring(stanza)
1587:
1588: self.send(stanza, handler, return_function)
1589:
1590: def retrieve_subscriptions(self, server, node=None, return_function=None, stanza_id=None):
1591: """Retrieve any subscriptions which the current account has on
1592: the given server. If a node is given, retrieve any subscriptions
1593: which the current account has on the given node on the given
1594: server.
1595:
1596: If given, return_function is run upon a reply being received with
1597: a dictionary in the format
1598: {server_name:{node_name:{'jid':subscribed_jid, 'state':subscription_state, 'subid':subscription_id}}}
1599: where 'jid', 'state' and 'subid' are literally those strings,
1600: whilst server_name is the address of the server, node_name is a
1601: string of the node ID, subscribed_jid is a string of the JID
1602: used to subscribe to this node (which may include a resource,
1603: depending on how the subscription was made), subscription_state
1604: is a string of 'subscribed', 'pending' or 'unconfigured' and
1605: subscription_id is an optional string of this subscription's
1606: unique ID (depending whether the server gives subscriptions an
1607: ID or not).
1608:
1609: If an error is received, the return_function is run with False."""
1610: # Server
1611: #<iq type='get'
1612: # from='francisco@denmark.lit/barracks'
1613: # to='pubsub.shakespeare.lit'
1614: # id='subscriptions1'>
1615: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1616: # <subscriptions/>
1617: # </pubsub>
1618: #</iq>
1619: #Node
1620: #<iq type='get'
1621: # from='francisco@denmark.lit/barracks'
1622: # to='pubsub.shakespeare.lit'
1623: # id='subscriptions2'>
1624: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1625: # <subscriptions node='princely_musings'/>
1626: # </pubsub>
1627: #</iq>
1628: stanza = Element('iq', attrib={'type':'get', 'from':self.get_jid(), 'to':str(server)}) # Is it correct to use self.server here?
1629: pubsub = SubElement(stanza, 'pubsub', attrib={'xmlns':'http://jabber.org/protocol/pubsub'})
1630: if node is not None:
1631: subscriptions = SubElement(pubsub, 'subscriptions', attrib={'node':str(node)})
1632: else:
1633: subscriptions = SubElement(pubsub, 'subscriptions')
1634: def handler(stanza, callback):
1635: #Subscriptions
1636: #<iq type='result'
1637: # from='pubsub.shakespeare.lit'
1638: # to='francisco@denmark.lit'
1639: # id='subscriptions1'>
1640: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1641: # <subscriptions>
1642: # <subscription node='node1' jid='francisco@denmark.lit' subscription='subscribed'/>
1643: # <subscription node='node2' jid='francisco@denmark.lit' subscription='subscribed'/>
1644: # <subscription node='node5' jid='francisco@denmark.lit' subscription='unconfigured'/>
1645: # <subscription node='node6' jid='francisco@denmark.lit' subscription='pending'/>
1646: # </subscriptions>
1647: # </pubsub>
1648: #</iq>
1649: #No Subscriptions
1650: #<iq type='result'
1651: # from='pubsub.shakespeare.lit'
1652: # to='francisco@denmark.lit/barracks'
1653: # id='subscriptions1'>
1654: # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
1655: # <subscriptions/>
1656: # </pubsub>
1657: #</iq>
1658:
1659: # Don't bother doing anything if we aren't going to act anyway
1660: if callback is not None:
1661: # Report errors as False
1662: if stanza.get('type') == 'error':
1663: callback(False)
1664: # If we get a successful reply, there's a bit more work to do
1665: elif stanza.get('type') == 'result':
1666: # Get server name
1667: reply_server = stanza.get('from')
1668: # Initialise an empty dictionary
1669: subscriptions_dict = {reply_server:{}}
1670: # Look for any <subscriptions> elements
1671: for subscriptions in stanza.xpath(".//{http://jabber.org/protocol/pubsub}subscriptions"):
1672: # Look inside the <subscriptions> element for actual subscriptions
1673: for subscription in subscriptions.xpath(".//{http://jabber.org/protocol/pubsub}subscription"):
1674: # The subid attribute is optional, so check for it
1675: if subscription.get('subid') is not None:
1676: # If found then construct the reply using it
1677: subscriptions_dict[reply_server][subscription.get('node')] = {\
1678: 'jid':subscription.get('jid'), \
1679: 'state':subscription.get('subscription'), \
1680: 'subid':subscription.get('subid')}
1681: else:
1682: # If not found then construct the reply without it
1683: subscriptions_dict[reply_server][subscription.get('node')] = {\
1684: 'jid':subscription.get('jid'), \
1685: 'state':subscription.get('subscription')}
1686: # Run the callback with the derived reply
1687: callback(subscriptions_dict)
1688:
1689: self.send(stanza, handler, return_function)
1690:
1691: class Node(object):
1692: """Pointer to a PubSub Node."""
1693:
1694: def __init__(self, server=None, name=None, jid=None, type=None, parent=None):
1695: self.set_server(server)
1696: self.set_name(name)
1697: self.set_jid(jid)
1698: self.set_type(type)
1699: self.set_parent(parent)
1700:
1701: def __str__(self):
1702: return self.name
1703:
1704: def set_server(self, server):
1705: """Sets the server which this Node object points to (does NOT
1706: edit any actual nodes, only this pointer!)"""
1707: if type(server) == type("string"):
1708: self.server = Server(server)
1709: elif type(server) == type(Server()):
1710: self.server = server
1711: else:
1712: print "Error: server must be a string or a Server."
1713:
1714: def set_name(self, name):
1715: """Sets the node name which this Node object points to (does NOT
1716: edit any actual nodes, only this pointer!)"""
1717: self.name = str(name)
1718:
1719: def set_jid(self, jid):
1720: self.jid = jid
1721:
1722: def set_type(self, type):
1723: """Sets the type of this Node object. Does not edit the actual
1724: node."""
1725: self.type = type
1726:
1727: def set_parent(self, parent):
1728: """Sets the parent collection node of this Node object. Does
1729: not edit the actual node."""
1730: self.parent = parent
1731:
1732: def get_sub_nodes(self, client, callback=None):
1733: """Queries this node for its children. Passes a list of Nodes
1734: it finds to the return_function when a reply is received."""
1735: client.get_nodes(self.server, self, return_function=callback)
1736:
1737: def get_items(self, client, callback=None):
1738: """TODO: Queries this node for the items it contains. Returns a list
1739: of the strings contained in the items."""
1740: client.get_items(self.server, self.name, return_function=callback)
1741:
1742: def get_information(self, client, callback=None):
1743: client.get_node_information(self.server, self, return_function=callback)
1744:
1745: def make_sub_node(self, client, name, type, callback=None):
1746: if self.type is "leaf":
1747: raise TypeError('Leaf nodes cannot contain child nodes')
1748: else:
1749: if self.type is None:
1750: print "Warning: Node type is not known, yet child node requested. This will fail for leaf nodes."
1751: if type == 'leaf':
1752: client.get_new_leaf_node()
1753: elif type == 'collection':
1754: client.get_new_collection_node()
1755:
1756: def request_all_affiliated_entities(self, client, return_function=None):
1757: client.request_all_affiliated_entities(self.server, self, return_function)
1758:
1759: def modify_affiliations(self, client, affiliation_dictionary, return_function=None):
1760: client.modify_affiliation(self.server, self, affiliation_dictionary, return_function)
1761:
1762: def publish(self, client, body, id=None, return_function=None):
1763: client.publish(self.server, self, body, id, None, return_function)
1764:
1765: def subscribe(self, client, jid, return_function=None):
1766: client.subscribe(self.server, self, jid, return_function)
1767:
1768: class Server(object):
1769:
1770: def __init__(self, name=None):
1771: if name is not None:
1772: self.set_name(name)
1773:
1774: def set_name(self, name):
1775: self.name = name
1776:
1777: def __str__(self):
1778: return self.name
1779:
1780: def add_node(self, client, name, callback=None):
1781: client.request_node(self, name, None, None, return_function=callback)
1782:
1783: class JID(xmpp.JID, object):
1784:
1785: def __init__(self, string='none@none'):
1786: super(JID, self).__init__(string)
1787: self.name = str(self)
1788:
1789: class Item(object):
1790: """Something which has been, or can be, published to a Node."""
1791:
1792: def __init__(name=None, jid=None, node=None):
1793: self.name = name
1794: self.jid = jid
1795: self.node = node
1796:
1797: class Form(object):
1798: """A form stores a list of fields, which can contain a list of
1799: values. Each field and value is accessed with
Generated by git2html.