Mercurial > libervia-backend
annotate sat/plugins/plugin_misc_forums.py @ 3888:aa7197b67c26
component AP gateway: AP <=> XMPP reactions conversions:
- Pubsub Attachments plugin has been renamed to XEP-0470 following publication
- XEP-0470 has been updated to follow 0.2 changes
- AP reactions (as implemented in Pleroma) are converted to XEP-0470
- XEP-0470 events are converted to AP reactions (again, using "EmojiReact" from Pleroma)
- AP activities related to attachments (like/reactions) are cached in Libervia because
it's not possible to retrieve them from Pleroma instances once they have been emitted
(doing an HTTP get on their ID returns a 404). For now those cache are not flushed, this
should be improved in the future.
- `sharedInbox` is used when available. Pleroma returns a 500 HTTP error when ``to`` or
``cc`` are used in a direct inbox.
- reactions and like are not currently used for direct messages, because they can't be
emitted from Pleroma in this case, thus there is no point in implementing them for the
moment.
rel 371
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 31 Aug 2022 17:07:03 +0200 |
parents | edc79cefe968 |
children | 6c5f0fbc519b |
rev | line source |
---|---|
3028 | 1 #!/usr/bin/env python3 |
3137 | 2 |
2484 | 3 |
2959
989b622faff6
plugins schema, tickets, merge_requests: use serialised data for extra dict + some cosmetic changes
Goffi <goffi@goffi.org>
parents:
2771
diff
changeset
|
4 # SAT plugin for pubsub forums |
3479 | 5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) |
2484 | 6 |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
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/>. | |
19 | |
20 from sat.core.i18n import _ | |
21 from sat.core.constants import Const as C | |
22 from sat.core import exceptions | |
23 from sat.core.log import getLogger | |
3549
3fd60beb9b92
plugin forums: use serialised data for extra in forumTopicsGet
Goffi <goffi@goffi.org>
parents:
3515
diff
changeset
|
24 from sat.tools.common import uri, data_format |
2484 | 25 from twisted.words.protocols.jabber import jid |
26 from twisted.words.xish import domish | |
27 from twisted.internet import defer | |
28 import shortuuid | |
29 import json | |
30 log = getLogger(__name__) | |
31 | |
3028 | 32 NS_FORUMS = 'org.salut-a-toi.forums:0' |
33 NS_FORUMS_TOPICS = NS_FORUMS + '#topics' | |
2484 | 34 |
35 PLUGIN_INFO = { | |
36 C.PI_NAME: _("forums management"), | |
37 C.PI_IMPORT_NAME: "forums", | |
38 C.PI_TYPE: "EXP", | |
39 C.PI_PROTOCOLS: [], | |
40 C.PI_DEPENDENCIES: ["XEP-0060", "XEP-0277"], | |
41 C.PI_MAIN: "forums", | |
42 C.PI_HANDLER: "no", | |
43 C.PI_DESCRIPTION: _("""forums management plugin""") | |
44 } | |
3028 | 45 FORUM_ATTR = {'title', 'name', 'main-language', 'uri'} |
46 FORUM_SUB_ELTS = ('short-desc', 'desc') | |
47 FORUM_TOPICS_NODE_TPL = '{node}#topics_{uuid}' | |
48 FORUM_TOPIC_NODE_TPL = '{node}_{uuid}' | |
2484 | 49 |
50 | |
51 class forums(object): | |
52 | |
53 def __init__(self, host): | |
3028 | 54 log.info(_("forums plugin initialization")) |
2484 | 55 self.host = host |
56 self._m = self.host.plugins['XEP-0277'] | |
57 self._p = self.host.plugins['XEP-0060'] | |
58 self._node_options = { | |
59 self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, | |
60 self._p.OPT_PERSIST_ITEMS: 1, | |
61 self._p.OPT_MAX_ITEMS: -1, | |
62 self._p.OPT_DELIVER_PAYLOADS: 1, | |
63 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, | |
64 self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, | |
65 } | |
66 host.registerNamespace('forums', NS_FORUMS) | |
67 host.bridge.addMethod("forumsGet", ".plugin", | |
68 in_sign='ssss', out_sign='s', | |
69 method=self._get, | |
3028 | 70 async_=True) |
2484 | 71 host.bridge.addMethod("forumsSet", ".plugin", |
72 in_sign='sssss', out_sign='', | |
73 method=self._set, | |
3028 | 74 async_=True) |
2484 | 75 host.bridge.addMethod("forumTopicsGet", ".plugin", |
3549
3fd60beb9b92
plugin forums: use serialised data for extra in forumTopicsGet
Goffi <goffi@goffi.org>
parents:
3515
diff
changeset
|
76 in_sign='ssa{ss}s', out_sign='(aa{ss}s)', |
2484 | 77 method=self._getTopics, |
3028 | 78 async_=True) |
2484 | 79 host.bridge.addMethod("forumTopicCreate", ".plugin", |
80 in_sign='ssa{ss}s', out_sign='', | |
81 method=self._createTopic, | |
3028 | 82 async_=True) |
2484 | 83 |
84 @defer.inlineCallbacks | |
85 def _createForums(self, client, forums, service, node, forums_elt=None, names=None): | |
2959
989b622faff6
plugins schema, tickets, merge_requests: use serialised data for extra dict + some cosmetic changes
Goffi <goffi@goffi.org>
parents:
2771
diff
changeset
|
86 """Recursively create <forums> element(s) |
2484 | 87 |
88 @param forums(list): forums which may have subforums | |
89 @param service(jid.JID): service where the new nodes will be created | |
90 @param node(unicode): node of the forums | |
91 will be used as basis for the newly created nodes | |
92 @param parent_elt(domish.Element, None): element where the forum must be added | |
93 if None, the root <forums> element will be created | |
94 @return (domish.Element): created forums | |
95 """ | |
96 if not isinstance(forums, list): | |
3028 | 97 raise ValueError(_("forums arguments must be a list of forums")) |
2484 | 98 if forums_elt is None: |
3028 | 99 forums_elt = domish.Element((NS_FORUMS, 'forums')) |
2484 | 100 assert names is None |
101 names = set() | |
102 else: | |
3028 | 103 if names is None or forums_elt.name != 'forums': |
104 raise exceptions.InternalError('invalid forums or names') | |
2484 | 105 assert names is not None |
106 | |
107 for forum in forums: | |
108 if not isinstance(forum, dict): | |
3028 | 109 raise ValueError(_("A forum item must be a dictionary")) |
2484 | 110 forum_elt = forums_elt.addElement('forum') |
111 | |
3028 | 112 for key, value in forum.items(): |
113 if key == 'name' and key in names: | |
114 raise exceptions.ConflictError(_("following forum name is not unique: {name}").format(name=key)) | |
115 if key == 'uri' and not value.strip(): | |
116 log.info(_("creating missing forum node")) | |
2484 | 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) | |
3028 | 119 value = uri.buildXMPPUri('pubsub', |
2484 | 120 path=service.full(), |
121 node=forum_node) | |
122 if key in FORUM_ATTR: | |
123 forum_elt[key] = value.strip() | |
124 elif key in FORUM_SUB_ELTS: | |
125 forum_elt.addElement(key, content=value) | |
3028 | 126 elif key == 'sub-forums': |
127 sub_forums_elt = forum_elt.addElement('forums') | |
2484 | 128 yield self._createForums(client, value, service, node, sub_forums_elt, names=names) |
129 else: | |
3028 | 130 log.warning(_("Unknown forum attribute: {key}").format(key=key)) |
131 if not forum_elt.getAttribute('title'): | |
132 name = forum_elt.getAttribute('name') | |
2484 | 133 if name: |
3028 | 134 forum_elt['title'] = name |
2484 | 135 else: |
3028 | 136 raise ValueError(_("forum need a title or a name")) |
137 if not forum_elt.getAttribute('uri') and not forum_elt.children: | |
138 raise ValueError(_("forum need uri or sub-forums")) | |
2484 | 139 defer.returnValue(forums_elt) |
140 | |
141 def _parseForums(self, parent_elt=None, forums=None): | |
2959
989b622faff6
plugins schema, tickets, merge_requests: use serialised data for extra dict + some cosmetic changes
Goffi <goffi@goffi.org>
parents:
2771
diff
changeset
|
142 """Recursivly parse a <forums> elements and return corresponding forums data |
2484 | 143 |
144 @param item(domish.Element): item with <forums> element | |
145 @param parent_elt(domish.Element, None): element to parse | |
146 @return (list): parsed data | |
147 @raise ValueError: item is invalid | |
148 """ | |
3028 | 149 if parent_elt.name == 'item': |
2484 | 150 forums = [] |
151 try: | |
3028 | 152 forums_elt = next(parent_elt.elements(NS_FORUMS, 'forums')) |
2484 | 153 except StopIteration: |
3028 | 154 raise ValueError(_("missing <forums> element")) |
2484 | 155 else: |
156 forums_elt = parent_elt | |
157 if forums is None: | |
3028 | 158 raise exceptions.InternalError('expected forums') |
2484 | 159 if forums_elt.name != 'forums': |
3028 | 160 raise ValueError(_('Unexpected element: {xml}').format(xml=forums_elt.toXml())) |
2484 | 161 for forum_elt in forums_elt.elements(): |
162 if forum_elt.name == 'forum': | |
163 data = {} | |
164 for attrib in FORUM_ATTR.intersection(forum_elt.attributes): | |
165 data[attrib] = forum_elt[attrib] | |
166 unknown = set(forum_elt.attributes).difference(FORUM_ATTR) | |
167 if unknown: | |
3028 | 168 log.warning(_("Following attributes are unknown: {unknown}").format(unknown=unknown)) |
2484 | 169 for elt in forum_elt.elements(): |
170 if elt.name in FORUM_SUB_ELTS: | |
3028 | 171 data[elt.name] = str(elt) |
172 elif elt.name == 'forums': | |
173 sub_forums = data['sub-forums'] = [] | |
2484 | 174 self._parseForums(elt, sub_forums) |
3028 | 175 if not 'title' in data or not {'uri', 'sub-forums'}.intersection(data): |
176 log.warning(_("invalid forum, ignoring: {xml}").format(xml=forum_elt.toXml())) | |
2484 | 177 else: |
178 forums.append(data) | |
179 else: | |
3028 | 180 log.warning(_("unkown forums sub element: {xml}").format(xml=forum_elt)) |
2484 | 181 |
182 return forums | |
183 | |
184 def _get(self, service=None, node=None, forums_key=None, profile_key=C.PROF_KEY_NONE): | |
185 client = self.host.getClient(profile_key) | |
186 if service.strip(): | |
187 service = jid.JID(service) | |
188 else: | |
189 service = None | |
190 if not node.strip(): | |
191 node = None | |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
192 d = defer.ensureDeferred(self.get(client, service, node, forums_key or None)) |
2484 | 193 d.addCallback(lambda data: json.dumps(data)) |
194 return d | |
195 | |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
196 async def get(self, client, service=None, node=None, forums_key=None): |
2484 | 197 if service is None: |
198 service = client.pubsub_service | |
199 if node is None: | |
200 node = NS_FORUMS | |
201 if forums_key is None: | |
3028 | 202 forums_key = 'default' |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
203 items_data = await self._p.getItems(client, service, node, item_ids=[forums_key]) |
2484 | 204 item = items_data[0][0] |
205 # we have the item and need to convert it to json | |
206 forums = self._parseForums(item) | |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
207 return forums |
2484 | 208 |
209 def _set(self, forums, service=None, node=None, forums_key=None, profile_key=C.PROF_KEY_NONE): | |
210 client = self.host.getClient(profile_key) | |
211 forums = json.loads(forums) | |
212 if service.strip(): | |
213 service = jid.JID(service) | |
214 else: | |
215 service = None | |
216 if not node.strip(): | |
217 node = None | |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
218 return defer.ensureDeferred( |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
219 self.set(client, forums, service, node, forums_key or None) |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
220 ) |
2484 | 221 |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
222 async def set(self, client, forums, service=None, node=None, forums_key=None): |
2959
989b622faff6
plugins schema, tickets, merge_requests: use serialised data for extra dict + some cosmetic changes
Goffi <goffi@goffi.org>
parents:
2771
diff
changeset
|
223 """Create or replace forums structure |
2484 | 224 |
225 @param forums(list): list of dictionary as follow: | |
226 a dictionary represent a forum metadata, with the following keys: | |
227 - title: title of the forum | |
228 - name: short name (unique in those forums) for the forum | |
229 - main-language: main language to be use in the forums | |
230 - uri: XMPP uri to the microblog node hosting the forum | |
231 - short-desc: short description of the forum (in main-language) | |
232 - desc: long description of the forum (in main-language) | |
233 - sub-forums: a list of sub-forums with the same structure | |
234 title or name is needed, and uri or sub-forums | |
235 @param forums_key(unicode, None): key (i.e. item id) of the forums | |
236 may be used to store different forums structures for different languages | |
237 None to use "default" | |
238 """ | |
239 if service is None: | |
240 service = client.pubsub_service | |
241 if node is None: | |
242 node = NS_FORUMS | |
243 if forums_key is None: | |
3028 | 244 forums_key = 'default' |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
245 forums_elt = await self._createForums(client, forums, service, node) |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
246 return await self._p.sendItem( |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
247 client, service, node, forums_elt, item_id=forums_key |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
248 ) |
2484 | 249 |
250 def _getTopics(self, service, node, extra=None, profile_key=C.PROF_KEY_NONE): | |
251 client = self.host.getClient(profile_key) | |
252 extra = self._p.parseExtra(extra) | |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
253 d = defer.ensureDeferred( |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
254 self.getTopics( |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
255 client, jid.JID(service), node, rsm_request=extra.rsm_request, |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
256 extra=extra.extra |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
257 ) |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
258 ) |
3549
3fd60beb9b92
plugin forums: use serialised data for extra in forumTopicsGet
Goffi <goffi@goffi.org>
parents:
3515
diff
changeset
|
259 d.addCallback( |
3fd60beb9b92
plugin forums: use serialised data for extra in forumTopicsGet
Goffi <goffi@goffi.org>
parents:
3515
diff
changeset
|
260 lambda topics_data: (topics_data[0], data_format.serialise(topics_data[1])) |
3fd60beb9b92
plugin forums: use serialised data for extra in forumTopicsGet
Goffi <goffi@goffi.org>
parents:
3515
diff
changeset
|
261 ) |
2484 | 262 return d |
263 | |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
264 async def getTopics(self, client, service, node, rsm_request=None, extra=None): |
2959
989b622faff6
plugins schema, tickets, merge_requests: use serialised data for extra dict + some cosmetic changes
Goffi <goffi@goffi.org>
parents:
2771
diff
changeset
|
265 """Retrieve topics data |
2484 | 266 |
267 Topics are simple microblog URIs with some metadata duplicated from first post | |
268 """ | |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
269 topics_data = await self._p.getItems( |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
270 client, service, node, rsm_request=rsm_request, extra=extra |
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
271 ) |
2484 | 272 topics = [] |
273 item_elts, metadata = topics_data | |
274 for item_elt in item_elts: | |
3028 | 275 topic_elt = next(item_elt.elements(NS_FORUMS, 'topic')) |
276 title_elt = next(topic_elt.elements(NS_FORUMS, 'title')) | |
277 topic = {'uri': topic_elt['uri'], | |
278 'author': topic_elt['author'], | |
279 'title': str(title_elt)} | |
2484 | 280 topics.append(topic) |
3584
edc79cefe968
plugin XEP-0060: `getItem(s)`, `publish` and `(un)subscribe` are now coroutines
Goffi <goffi@goffi.org>
parents:
3549
diff
changeset
|
281 return (topics, metadata) |
2484 | 282 |
283 def _createTopic(self, service, node, mb_data, profile_key): | |
284 client = self.host.getClient(profile_key) | |
3515
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
285 return defer.ensureDeferred( |
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
286 self.createTopic(client, jid.JID(service), node, mb_data) |
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
287 ) |
2484 | 288 |
3515
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
289 async def createTopic(self, client, service, node, mb_data): |
2484 | 290 try: |
3028 | 291 title = mb_data['title'] |
3515
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
292 content = mb_data.pop('content') |
2484 | 293 except KeyError as e: |
3028 | 294 raise exceptions.DataError("missing mandatory data: {key}".format(key=e.args[0])) |
3515
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
295 else: |
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
296 mb_data["content_rich"] = content |
2484 | 297 topic_node = FORUM_TOPIC_NODE_TPL.format(node=node, uuid=shortuuid.uuid()) |
3515
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
298 await self._p.createNode(client, service, topic_node, self._node_options) |
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
299 await self._m.send(client, mb_data, service, topic_node) |
3028 | 300 topic_uri = uri.buildXMPPUri('pubsub', |
301 subtype='microblog', | |
2484 | 302 path=service.full(), |
303 node=topic_node) | |
304 topic_elt = domish.Element((NS_FORUMS, 'topic')) | |
3028 | 305 topic_elt['uri'] = topic_uri |
306 topic_elt['author'] = client.jid.userhost() | |
307 topic_elt.addElement('title', content = title) | |
3515
2dce411c2647
plugin misc forums: use rich content in createTopic
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
308 await self._p.sendItem(client, service, node, topic_elt) |