comparison sat/plugins/plugin_misc_forums.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 989b622faff6
children 9d0df638c8b4
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for pubsub forums 4 # SAT plugin for pubsub forums
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
27 from twisted.internet import defer 27 from twisted.internet import defer
28 import shortuuid 28 import shortuuid
29 import json 29 import json
30 log = getLogger(__name__) 30 log = getLogger(__name__)
31 31
32 NS_FORUMS = u'org.salut-a-toi.forums:0' 32 NS_FORUMS = 'org.salut-a-toi.forums:0'
33 NS_FORUMS_TOPICS = NS_FORUMS + u'#topics' 33 NS_FORUMS_TOPICS = NS_FORUMS + '#topics'
34 34
35 PLUGIN_INFO = { 35 PLUGIN_INFO = {
36 C.PI_NAME: _("forums management"), 36 C.PI_NAME: _("forums management"),
37 C.PI_IMPORT_NAME: "forums", 37 C.PI_IMPORT_NAME: "forums",
38 C.PI_TYPE: "EXP", 38 C.PI_TYPE: "EXP",
40 C.PI_DEPENDENCIES: ["XEP-0060", "XEP-0277"], 40 C.PI_DEPENDENCIES: ["XEP-0060", "XEP-0277"],
41 C.PI_MAIN: "forums", 41 C.PI_MAIN: "forums",
42 C.PI_HANDLER: "no", 42 C.PI_HANDLER: "no",
43 C.PI_DESCRIPTION: _("""forums management plugin""") 43 C.PI_DESCRIPTION: _("""forums management plugin""")
44 } 44 }
45 FORUM_ATTR = {u'title', u'name', u'main-language', u'uri'} 45 FORUM_ATTR = {'title', 'name', 'main-language', 'uri'}
46 FORUM_SUB_ELTS = (u'short-desc', u'desc') 46 FORUM_SUB_ELTS = ('short-desc', 'desc')
47 FORUM_TOPICS_NODE_TPL = u'{node}#topics_{uuid}' 47 FORUM_TOPICS_NODE_TPL = '{node}#topics_{uuid}'
48 FORUM_TOPIC_NODE_TPL = u'{node}_{uuid}' 48 FORUM_TOPIC_NODE_TPL = '{node}_{uuid}'
49 49
50 50
51 class forums(object): 51 class forums(object):
52 52
53 def __init__(self, host): 53 def __init__(self, host):
54 log.info(_(u"forums plugin initialization")) 54 log.info(_("forums plugin initialization"))
55 self.host = host 55 self.host = host
56 self._m = self.host.plugins['XEP-0277'] 56 self._m = self.host.plugins['XEP-0277']
57 self._p = self.host.plugins['XEP-0060'] 57 self._p = self.host.plugins['XEP-0060']
58 self._node_options = { 58 self._node_options = {
59 self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, 59 self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
65 } 65 }
66 host.registerNamespace('forums', NS_FORUMS) 66 host.registerNamespace('forums', NS_FORUMS)
67 host.bridge.addMethod("forumsGet", ".plugin", 67 host.bridge.addMethod("forumsGet", ".plugin",
68 in_sign='ssss', out_sign='s', 68 in_sign='ssss', out_sign='s',
69 method=self._get, 69 method=self._get,
70 async=True) 70 async_=True)
71 host.bridge.addMethod("forumsSet", ".plugin", 71 host.bridge.addMethod("forumsSet", ".plugin",
72 in_sign='sssss', out_sign='', 72 in_sign='sssss', out_sign='',
73 method=self._set, 73 method=self._set,
74 async=True) 74 async_=True)
75 host.bridge.addMethod("forumTopicsGet", ".plugin", 75 host.bridge.addMethod("forumTopicsGet", ".plugin",
76 in_sign='ssa{ss}s', out_sign='(aa{ss}a{ss})', 76 in_sign='ssa{ss}s', out_sign='(aa{ss}a{ss})',
77 method=self._getTopics, 77 method=self._getTopics,
78 async=True) 78 async_=True)
79 host.bridge.addMethod("forumTopicCreate", ".plugin", 79 host.bridge.addMethod("forumTopicCreate", ".plugin",
80 in_sign='ssa{ss}s', out_sign='', 80 in_sign='ssa{ss}s', out_sign='',
81 method=self._createTopic, 81 method=self._createTopic,
82 async=True) 82 async_=True)
83 83
84 @defer.inlineCallbacks 84 @defer.inlineCallbacks
85 def _createForums(self, client, forums, service, node, forums_elt=None, names=None): 85 def _createForums(self, client, forums, service, node, forums_elt=None, names=None):
86 """Recursively create <forums> element(s) 86 """Recursively create <forums> element(s)
87 87
92 @param parent_elt(domish.Element, None): element where the forum must be added 92 @param parent_elt(domish.Element, None): element where the forum must be added
93 if None, the root <forums> element will be created 93 if None, the root <forums> element will be created
94 @return (domish.Element): created forums 94 @return (domish.Element): created forums
95 """ 95 """
96 if not isinstance(forums, list): 96 if not isinstance(forums, list):
97 raise ValueError(_(u"forums arguments must be a list of forums")) 97 raise ValueError(_("forums arguments must be a list of forums"))
98 if forums_elt is None: 98 if forums_elt is None:
99 forums_elt = domish.Element((NS_FORUMS, u'forums')) 99 forums_elt = domish.Element((NS_FORUMS, 'forums'))
100 assert names is None 100 assert names is None
101 names = set() 101 names = set()
102 else: 102 else:
103 if names is None or forums_elt.name != u'forums': 103 if names is None or forums_elt.name != 'forums':
104 raise exceptions.InternalError(u'invalid forums or names') 104 raise exceptions.InternalError('invalid forums or names')
105 assert names is not None 105 assert names is not None
106 106
107 for forum in forums: 107 for forum in forums:
108 if not isinstance(forum, dict): 108 if not isinstance(forum, dict):
109 raise ValueError(_(u"A forum item must be a dictionary")) 109 raise ValueError(_("A forum item must be a dictionary"))
110 forum_elt = forums_elt.addElement('forum') 110 forum_elt = forums_elt.addElement('forum')
111 111
112 for key, value in forum.iteritems(): 112 for key, value in forum.items():
113 if key == u'name' and key in names: 113 if key == 'name' and key in names:
114 raise exceptions.ConflictError(_(u"following forum name is not unique: {name}").format(name=key)) 114 raise exceptions.ConflictError(_("following forum name is not unique: {name}").format(name=key))
115 if key == u'uri' and not value.strip(): 115 if key == 'uri' and not value.strip():
116 log.info(_(u"creating missing forum node")) 116 log.info(_("creating missing forum node"))
117 forum_node = FORUM_TOPICS_NODE_TPL.format(node=node, uuid=shortuuid.uuid()) 117 forum_node = FORUM_TOPICS_NODE_TPL.format(node=node, uuid=shortuuid.uuid())
118 yield self._p.createNode(client, service, forum_node, self._node_options) 118 yield self._p.createNode(client, service, forum_node, self._node_options)
119 value = uri.buildXMPPUri(u'pubsub', 119 value = uri.buildXMPPUri('pubsub',
120 path=service.full(), 120 path=service.full(),
121 node=forum_node) 121 node=forum_node)
122 if key in FORUM_ATTR: 122 if key in FORUM_ATTR:
123 forum_elt[key] = value.strip() 123 forum_elt[key] = value.strip()
124 elif key in FORUM_SUB_ELTS: 124 elif key in FORUM_SUB_ELTS:
125 forum_elt.addElement(key, content=value) 125 forum_elt.addElement(key, content=value)
126 elif key == u'sub-forums': 126 elif key == 'sub-forums':
127 sub_forums_elt = forum_elt.addElement(u'forums') 127 sub_forums_elt = forum_elt.addElement('forums')
128 yield self._createForums(client, value, service, node, sub_forums_elt, names=names) 128 yield self._createForums(client, value, service, node, sub_forums_elt, names=names)
129 else: 129 else:
130 log.warning(_(u"Unknown forum attribute: {key}").format(key=key)) 130 log.warning(_("Unknown forum attribute: {key}").format(key=key))
131 if not forum_elt.getAttribute(u'title'): 131 if not forum_elt.getAttribute('title'):
132 name = forum_elt.getAttribute(u'name') 132 name = forum_elt.getAttribute('name')
133 if name: 133 if name:
134 forum_elt[u'title'] = name 134 forum_elt['title'] = name
135 else: 135 else:
136 raise ValueError(_(u"forum need a title or a name")) 136 raise ValueError(_("forum need a title or a name"))
137 if not forum_elt.getAttribute(u'uri') and not forum_elt.children: 137 if not forum_elt.getAttribute('uri') and not forum_elt.children:
138 raise ValueError(_(u"forum need uri or sub-forums")) 138 raise ValueError(_("forum need uri or sub-forums"))
139 defer.returnValue(forums_elt) 139 defer.returnValue(forums_elt)
140 140
141 def _parseForums(self, parent_elt=None, forums=None): 141 def _parseForums(self, parent_elt=None, forums=None):
142 """Recursivly parse a <forums> elements and return corresponding forums data 142 """Recursivly parse a <forums> elements and return corresponding forums data
143 143
144 @param item(domish.Element): item with <forums> element 144 @param item(domish.Element): item with <forums> element
145 @param parent_elt(domish.Element, None): element to parse 145 @param parent_elt(domish.Element, None): element to parse
146 @return (list): parsed data 146 @return (list): parsed data
147 @raise ValueError: item is invalid 147 @raise ValueError: item is invalid
148 """ 148 """
149 if parent_elt.name == u'item': 149 if parent_elt.name == 'item':
150 forums = [] 150 forums = []
151 try: 151 try:
152 forums_elt = next(parent_elt.elements(NS_FORUMS, u'forums')) 152 forums_elt = next(parent_elt.elements(NS_FORUMS, 'forums'))
153 except StopIteration: 153 except StopIteration:
154 raise ValueError(_(u"missing <forums> element")) 154 raise ValueError(_("missing <forums> element"))
155 else: 155 else:
156 forums_elt = parent_elt 156 forums_elt = parent_elt
157 if forums is None: 157 if forums is None:
158 raise exceptions.InternalError(u'expected forums') 158 raise exceptions.InternalError('expected forums')
159 if forums_elt.name != 'forums': 159 if forums_elt.name != 'forums':
160 raise ValueError(_(u'Unexpected element: {xml}').format(xml=forums_elt.toXml())) 160 raise ValueError(_('Unexpected element: {xml}').format(xml=forums_elt.toXml()))
161 for forum_elt in forums_elt.elements(): 161 for forum_elt in forums_elt.elements():
162 if forum_elt.name == 'forum': 162 if forum_elt.name == 'forum':
163 data = {} 163 data = {}
164 for attrib in FORUM_ATTR.intersection(forum_elt.attributes): 164 for attrib in FORUM_ATTR.intersection(forum_elt.attributes):
165 data[attrib] = forum_elt[attrib] 165 data[attrib] = forum_elt[attrib]
166 unknown = set(forum_elt.attributes).difference(FORUM_ATTR) 166 unknown = set(forum_elt.attributes).difference(FORUM_ATTR)
167 if unknown: 167 if unknown:
168 log.warning(_(u"Following attributes are unknown: {unknown}").format(unknown=unknown)) 168 log.warning(_("Following attributes are unknown: {unknown}").format(unknown=unknown))
169 for elt in forum_elt.elements(): 169 for elt in forum_elt.elements():
170 if elt.name in FORUM_SUB_ELTS: 170 if elt.name in FORUM_SUB_ELTS:
171 data[elt.name] = unicode(elt) 171 data[elt.name] = str(elt)
172 elif elt.name == u'forums': 172 elif elt.name == 'forums':
173 sub_forums = data[u'sub-forums'] = [] 173 sub_forums = data['sub-forums'] = []
174 self._parseForums(elt, sub_forums) 174 self._parseForums(elt, sub_forums)
175 if not u'title' in data or not {u'uri', u'sub-forums'}.intersection(data): 175 if not 'title' in data or not {'uri', 'sub-forums'}.intersection(data):
176 log.warning(_(u"invalid forum, ignoring: {xml}").format(xml=forum_elt.toXml())) 176 log.warning(_("invalid forum, ignoring: {xml}").format(xml=forum_elt.toXml()))
177 else: 177 else:
178 forums.append(data) 178 forums.append(data)
179 else: 179 else:
180 log.warning(_(u"unkown forums sub element: {xml}").format(xml=forum_elt)) 180 log.warning(_("unkown forums sub element: {xml}").format(xml=forum_elt))
181 181
182 return forums 182 return forums
183 183
184 def _get(self, service=None, node=None, forums_key=None, profile_key=C.PROF_KEY_NONE): 184 def _get(self, service=None, node=None, forums_key=None, profile_key=C.PROF_KEY_NONE):
185 client = self.host.getClient(profile_key) 185 client = self.host.getClient(profile_key)
198 if service is None: 198 if service is None:
199 service = client.pubsub_service 199 service = client.pubsub_service
200 if node is None: 200 if node is None:
201 node = NS_FORUMS 201 node = NS_FORUMS
202 if forums_key is None: 202 if forums_key is None:
203 forums_key = u'default' 203 forums_key = 'default'
204 items_data = yield self._p.getItems(client, service, node, item_ids=[forums_key]) 204 items_data = yield self._p.getItems(client, service, node, item_ids=[forums_key])
205 item = items_data[0][0] 205 item = items_data[0][0]
206 # we have the item and need to convert it to json 206 # we have the item and need to convert it to json
207 forums = self._parseForums(item) 207 forums = self._parseForums(item)
208 defer.returnValue(forums) 208 defer.returnValue(forums)
239 if service is None: 239 if service is None:
240 service = client.pubsub_service 240 service = client.pubsub_service
241 if node is None: 241 if node is None:
242 node = NS_FORUMS 242 node = NS_FORUMS
243 if forums_key is None: 243 if forums_key is None:
244 forums_key = u'default' 244 forums_key = 'default'
245 forums_elt = yield self._createForums(client, forums, service, node) 245 forums_elt = yield self._createForums(client, forums, service, node)
246 yield self._p.sendItem(client, service, node, forums_elt, item_id=forums_key) 246 yield self._p.sendItem(client, service, node, forums_elt, item_id=forums_key)
247 247
248 def _getTopics(self, service, node, extra=None, profile_key=C.PROF_KEY_NONE): 248 def _getTopics(self, service, node, extra=None, profile_key=C.PROF_KEY_NONE):
249 client = self.host.getClient(profile_key) 249 client = self.host.getClient(profile_key)
250 extra = self._p.parseExtra(extra) 250 extra = self._p.parseExtra(extra)
251 d = self.getTopics(client, jid.JID(service), node, rsm_request=extra.rsm_request, extra=extra.extra) 251 d = self.getTopics(client, jid.JID(service), node, rsm_request=extra.rsm_request, extra=extra.extra)
252 d.addCallback(lambda(topics, metadata): (topics, {k: unicode(v) for k,v in metadata.iteritems()})) 252 d.addCallback(lambda topics_metadata: (topics_metadata[0], {k: str(v) for k,v in topics_metadata[1].items()}))
253 return d 253 return d
254 254
255 @defer.inlineCallbacks 255 @defer.inlineCallbacks
256 def getTopics(self, client, service, node, rsm_request=None, extra=None): 256 def getTopics(self, client, service, node, rsm_request=None, extra=None):
257 """Retrieve topics data 257 """Retrieve topics data
260 """ 260 """
261 topics_data = yield self._p.getItems(client, service, node, rsm_request=rsm_request, extra=extra) 261 topics_data = yield self._p.getItems(client, service, node, rsm_request=rsm_request, extra=extra)
262 topics = [] 262 topics = []
263 item_elts, metadata = topics_data 263 item_elts, metadata = topics_data
264 for item_elt in item_elts: 264 for item_elt in item_elts:
265 topic_elt = next(item_elt.elements(NS_FORUMS, u'topic')) 265 topic_elt = next(item_elt.elements(NS_FORUMS, 'topic'))
266 title_elt = next(topic_elt.elements(NS_FORUMS, u'title')) 266 title_elt = next(topic_elt.elements(NS_FORUMS, 'title'))
267 topic = {u'uri': topic_elt[u'uri'], 267 topic = {'uri': topic_elt['uri'],
268 u'author': topic_elt[u'author'], 268 'author': topic_elt['author'],
269 u'title': unicode(title_elt)} 269 'title': str(title_elt)}
270 topics.append(topic) 270 topics.append(topic)
271 defer.returnValue((topics, metadata)) 271 defer.returnValue((topics, metadata))
272 272
273 def _createTopic(self, service, node, mb_data, profile_key): 273 def _createTopic(self, service, node, mb_data, profile_key):
274 client = self.host.getClient(profile_key) 274 client = self.host.getClient(profile_key)
275 return self.createTopic(client, jid.JID(service), node, mb_data) 275 return self.createTopic(client, jid.JID(service), node, mb_data)
276 276
277 @defer.inlineCallbacks 277 @defer.inlineCallbacks
278 def createTopic(self, client, service, node, mb_data): 278 def createTopic(self, client, service, node, mb_data):
279 try: 279 try:
280 title = mb_data[u'title'] 280 title = mb_data['title']
281 if not u'content' in mb_data: 281 if not 'content' in mb_data:
282 raise KeyError(u'content') 282 raise KeyError('content')
283 except KeyError as e: 283 except KeyError as e:
284 raise exceptions.DataError(u"missing mandatory data: {key}".format(key=e.args[0])) 284 raise exceptions.DataError("missing mandatory data: {key}".format(key=e.args[0]))
285 285
286 topic_node = FORUM_TOPIC_NODE_TPL.format(node=node, uuid=shortuuid.uuid()) 286 topic_node = FORUM_TOPIC_NODE_TPL.format(node=node, uuid=shortuuid.uuid())
287 yield self._p.createNode(client, service, topic_node, self._node_options) 287 yield self._p.createNode(client, service, topic_node, self._node_options)
288 self._m.send(client, mb_data, service, topic_node) 288 self._m.send(client, mb_data, service, topic_node)
289 topic_uri = uri.buildXMPPUri(u'pubsub', 289 topic_uri = uri.buildXMPPUri('pubsub',
290 subtype=u'microblog', 290 subtype='microblog',
291 path=service.full(), 291 path=service.full(),
292 node=topic_node) 292 node=topic_node)
293 topic_elt = domish.Element((NS_FORUMS, 'topic')) 293 topic_elt = domish.Element((NS_FORUMS, 'topic'))
294 topic_elt[u'uri'] = topic_uri 294 topic_elt['uri'] = topic_uri
295 topic_elt[u'author'] = client.jid.userhost() 295 topic_elt['author'] = client.jid.userhost()
296 topic_elt.addElement(u'title', content = title) 296 topic_elt.addElement('title', content = title)
297 yield self._p.sendItem(client, service, node, topic_elt) 297 yield self._p.sendItem(client, service, node, topic_elt)