comparison src/plugins/plugin_xep_0277.py @ 615:6f4c31192c7c

plugins XEP-0060, XEP-0277, groupblog: comments implementation (first draft, not finished yet): - PubSub options constants are moved to XEP-0060 - comments url are generated/parsed according to XEP-0277 - microblog data can now have the following keys: - "comments", with the url as given in the <link> tag - "comments_service", with the jid of the PubSub service hosting the comments - "comments_node", with the parsed node - comments nodes use different access_model according to parent microblog item access - publisher is not verified yet, see FIXME warning - so far, comments node are automatically subscribed - some bug fixes
author Goffi <goffi@goffi.org>
date Mon, 20 May 2013 23:21:29 +0200
parents 84a6e83157c2
children 47d3a22b4629
comparison
equal deleted inserted replaced
614:bef0f893482f 615:6f4c31192c7c
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from logging import debug, info, error 20 from logging import debug, info, warning, error
21 from twisted.internet import protocol 21 from twisted.words.protocols.jabber import jid
22 from twisted.words.protocols.jabber import client, jid 22 from sat.core import exceptions
23 from twisted.words.protocols.jabber import error as jab_error
24 import twisted.internet.error
25 from twisted.words.xish import domish
26 from sat.tools.xml_tools import ElementParser 23 from sat.tools.xml_tools import ElementParser
27 24
28 from wokkel import disco, pubsub 25 from wokkel import pubsub
29 from feed.atom import Entry, Author 26 from feed import atom
30 import uuid 27 import uuid
31 from time import time 28 from time import time
29 import urlparse
32 30
33 NS_MICROBLOG = 'urn:xmpp:microblog:0' 31 NS_MICROBLOG = 'urn:xmpp:microblog:0'
34 OPT_ACCESS_MODEL = 'pubsub#access_model'
35 OPT_PERSIST_ITEMS = 'pubsub#persist_items'
36 OPT_MAX_ITEMS = 'pubsub#max_items'
37 OPT_DELIVER_PAYLOADS = 'pubsub#deliver_payloads'
38 OPT_SEND_ITEM_SUBSCRIBE = 'pubsub#send_item_subscribe'
39 32
40 PLUGIN_INFO = { 33 PLUGIN_INFO = {
41 "name": "Microblogging over XMPP Plugin", 34 "name": "Microblogging over XMPP Plugin",
42 "import_name": "XEP-0277", 35 "import_name": "XEP-0277",
43 "type": "XEP", 36 "type": "XEP",
71 host.bridge.addMethod("setMicroblogAccess", ".plugin", in_sign='ss', out_sign='', 64 host.bridge.addMethod("setMicroblogAccess", ".plugin", in_sign='ss', out_sign='',
72 method=self.setMicroblogAccess, 65 method=self.setMicroblogAccess,
73 async=True, 66 async=True,
74 doc={}) 67 doc={})
75 68
69 def parseCommentUrl(self, node_url):
70 parsed_url = urlparse.urlparse(node_url, 'xmpp')
71 service = jid.JID(parsed_url.path)
72 queries = parsed_url.query.split(';')
73 parsed_queries = dict()
74 for query in queries:
75 parsed_queries.update(urlparse.parse_qs(query))
76 node = parsed_queries.get('node',[''])[0]
77
78 if not node:
79 raise exceptions.DataError('Invalid comments link')
80
81 return (service, node)
82
76 def item2mbdata(self, item): 83 def item2mbdata(self, item):
77 """Convert an XML Item to microblog data used in bridge API 84 """Convert an XML Item to microblog data used in bridge API
78 @param item: domish.Element of microblog item 85 @param item: domish.Element of microblog item
79 @return: microblog data (dictionary)""" 86 @return: microblog data (dictionary)"""
80 try: 87 try:
81 entry_elt = filter(lambda x: x.name == "entry", item.children)[0] 88 entry_elt = filter(lambda x: x.name == "entry", item.children)[0]
82 except KeyError: 89 except KeyError:
83 warning(_('No entry element in microblog item')) 90 warning(_('No entry element in microblog item'))
84 return 91 return
85 _entry = Entry().import_xml(entry_elt.toXml().encode('utf-8')) 92 _entry = atom.Entry().import_xml(entry_elt.toXml().encode('utf-8'))
86 microblog_data = {} 93 microblog_data = {}
87 try: 94 try:
88 microblog_data['content'] = _entry.title.text 95 microblog_data['content'] = _entry.title.text
89 if len(_entry.authors): 96 if len(_entry.authors):
90 microblog_data['author'] = _entry.authors[0].name.text 97 microblog_data['author'] = _entry.authors[0].name.text
91 microblog_data['timestamp'] = str(int(_entry.updated.tf)) 98 microblog_data['timestamp'] = str(int(_entry.updated.tf))
92 microblog_data['id'] = item['id'] 99 microblog_data['id'] = item['id']
100 for link in _entry.links:
101 try:
102 if link.attrs["title"] == "comments":
103 microblog_data['comments'] = link.attrs["href"]
104 service, node = self.parseCommentUrl(microblog_data["comments"])
105 microblog_data['comments_service'] = service.full()
106 microblog_data['comments_node'] = node
107 break
108 except (KeyError, exceptions.DataError):
109 continue
110
93 except (AttributeError, KeyError): 111 except (AttributeError, KeyError):
94 error(_('Error while parsing atom entry for microblogging event')) 112 error(_('Error while parsing atom entry for microblogging event'))
95 return {} 113 return {}
96 114
97 ##XXX: workaround for Jappix behaviour 115 ##XXX: workaround for Jappix behaviour
115 """Convert a data dict to en entry usable to create an item 133 """Convert a data dict to en entry usable to create an item
116 @param data: data dict as given by bridge method 134 @param data: data dict as given by bridge method
117 @return: domish.Element""" 135 @return: domish.Element"""
118 _uuid = unicode(uuid.uuid1()) 136 _uuid = unicode(uuid.uuid1())
119 content = data['content'] 137 content = data['content']
120 _entry = Entry() 138 _entry = atom.Entry()
121 #FIXME: need to escape html 139 #FIXME: need to escape html
122 _entry.title = unicode(content).encode('utf-8') 140 _entry.title = unicode(content).encode('utf-8')
123 _entry.author = Author() 141 _entry.author = atom.Author()
124 _entry.author.name = data.get('author', self.host.getJidNStream(profile)[0].userhost()).encode('utf-8') 142 _entry.author.name = data.get('author', self.host.getJidNStream(profile)[0].userhost()).encode('utf-8')
125 _entry.updated = float(data.get('timestamp', time())) 143 _entry.updated = float(data.get('timestamp', time()))
126 _entry.id = str(_uuid) 144 _entry.id = str(_uuid)
145 if 'comments' in data:
146 link = atom.Link()
147 link.attrs['href'] = data['comments']
148 link.attrs['rel'] = 'replies'
149 link.attrs['title'] = 'comments'
150 _entry.links.append(link)
127 _entry_elt = ElementParser()(str(_entry).decode('utf-8')) 151 _entry_elt = ElementParser()(str(_entry).decode('utf-8'))
128 item = pubsub.Item(payload=_entry_elt) 152 item = pubsub.Item(payload=_entry_elt)
129 item['id'] = _uuid 153 item['id'] = _uuid
130 return item 154 return item
131 155
147 def getLastMicroblogs(self, pub_jid, max_items=10, profile_key='@DEFAULT@'): 171 def getLastMicroblogs(self, pub_jid, max_items=10, profile_key='@DEFAULT@'):
148 """Get the last published microblogs 172 """Get the last published microblogs
149 @param pub_jid: jid of the publisher 173 @param pub_jid: jid of the publisher
150 @param max_items: how many microblogs we want to get 174 @param max_items: how many microblogs we want to get
151 @param profile_key: profile key 175 @param profile_key: profile key
152 @param callback: used for the async answer
153 @param errback: used for the async answer
154 """ 176 """
155 assert(callback)
156 d = self.host.plugins["XEP-0060"].getItems(jid.JID(pub_jid), NS_MICROBLOG, max_items=max_items, profile_key=profile_key) 177 d = self.host.plugins["XEP-0060"].getItems(jid.JID(pub_jid), NS_MICROBLOG, max_items=max_items, profile_key=profile_key)
157 d.addCallback(lambda items: map(self.item2mbdata, items)) 178 d.addCallback(lambda items: map(self.item2mbdata, items))
158 179
159 def setMicroblogAccess(self, access="presence", profile_key='@DEFAULT@'): 180 def setMicroblogAccess(self, access="presence", profile_key='@DEFAULT@'):
160 """Create a microblog node on PEP with given access 181 """Create a microblog node on PEP with given access
164 185
165 _jid, xmlstream = self.host.getJidNStream(profile_key) 186 _jid, xmlstream = self.host.getJidNStream(profile_key)
166 if not _jid: 187 if not _jid:
167 error(_("Can't find profile's jid")) 188 error(_("Can't find profile's jid"))
168 return 189 return
169 _options = {OPT_ACCESS_MODEL: access, OPT_PERSIST_ITEMS: 1, OPT_MAX_ITEMS: -1, OPT_DELIVER_PAYLOADS: 1, OPT_SEND_ITEM_SUBSCRIBE: 1} 190 C = self.host.plugins["XEP-0060"]
191 _options = {C.OPT_ACCESS_MODEL: access, C.OPT_PERSIST_ITEMS: 1, C.OPT_MAX_ITEMS: -1, C.OPT_DELIVER_PAYLOADS: 1, C.OPT_SEND_ITEM_SUBSCRIBE: 1}
170 192
171 def cb(result): 193 def cb(result):
172 #Node is created with right permission 194 #Node is created with right permission
173 debug(_("Microblog node has now access %s") % access) 195 debug(_("Microblog node has now access %s") % access)
174 196