Mercurial > libervia-backend
comparison sat/plugins/plugin_comp_ap_gateway/http_server.py @ 3821:0b1c30ff2cbb
component AP: XMPP identity => AP actor data converstion:
XMPP identity data (coming from various XEPs, notably XEP-0292 (vCard4) and XEP-0084 (User
Avatar)) are now used to complete actor data.
Avatar files can now be delivered with the `avatar` type in URL.
rel 368
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 29 Jun 2022 12:06:21 +0200 |
parents | 2032826cfbcf |
children | 81c79b7cafa7 |
comparison
equal
deleted
inserted
replaced
3820:88e332cec47b | 3821:0b1c30ff2cbb |
---|---|
15 | 15 |
16 # You should have received a copy of the GNU Affero General Public License | 16 # You should have received a copy of the GNU Affero General Public License |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 |
19 import time | 19 import time |
20 import html | |
20 from typing import Optional, Dict, List, Any | 21 from typing import Optional, Dict, List, Any |
21 import json | 22 import json |
22 from urllib import parse | 23 from urllib import parse |
23 from collections import deque | 24 from collections import deque |
24 import unicodedata | 25 import unicodedata |
26 from pathlib import Path | |
25 from pprint import pformat | 27 from pprint import pformat |
26 | 28 |
27 from twisted.web import http, resource as web_resource, server | 29 from twisted.web import http, resource as web_resource, server |
30 from twisted.web import static | |
28 from twisted.python import failure | 31 from twisted.python import failure |
29 from twisted.internet import reactor, defer | 32 from twisted.internet import reactor, defer |
30 from twisted.words.protocols.jabber import jid, error | 33 from twisted.words.protocols.jabber import jid, error |
31 from wokkel import pubsub, rsm | 34 from wokkel import pubsub, rsm |
32 | 35 |
298 following = self.apg.buildAPURL(TYPE_FOLLOWING, ap_account) | 301 following = self.apg.buildAPURL(TYPE_FOLLOWING, ap_account) |
299 | 302 |
300 # we have to use AP account as preferredUsername because it is used to retrieve | 303 # we have to use AP account as preferredUsername because it is used to retrieve |
301 # actor handle (see https://socialhub.activitypub.rocks/t/how-to-retrieve-user-server-tld-handle-from-actors-url/2196) | 304 # actor handle (see https://socialhub.activitypub.rocks/t/how-to-retrieve-user-server-tld-handle-from-actors-url/2196) |
302 preferred_username = ap_account.split("@", 1)[0] | 305 preferred_username = ap_account.split("@", 1)[0] |
303 return { | 306 |
307 identity_data = await self.apg._i.getIdentity(self.apg.client, account_jid) | |
308 | |
309 actor_data = { | |
304 "@context": [ | 310 "@context": [ |
305 "https://www.w3.org/ns/activitystreams", | 311 "https://www.w3.org/ns/activitystreams", |
306 "https://w3id.org/security/v1" | 312 "https://w3id.org/security/v1" |
307 ], | 313 ], |
308 | 314 |
320 }, | 326 }, |
321 "endpoints": { | 327 "endpoints": { |
322 "sharedInbox": shared_inbox | 328 "sharedInbox": shared_inbox |
323 }, | 329 }, |
324 } | 330 } |
331 | |
332 if identity_data.get("nicknames"): | |
333 actor_data["name"] = identity_data["nicknames"][0] | |
334 if identity_data.get("description"): | |
335 # description is plain text while summary expects HTML | |
336 actor_data["summary"] = html.escape(identity_data["description"]) | |
337 if identity_data.get("avatar"): | |
338 avatar_data = identity_data["avatar"] | |
339 try: | |
340 filename = avatar_data["filename"] | |
341 media_type = avatar_data["media_type"] | |
342 except KeyError: | |
343 log.error(f"incomplete avatar data: {identity_data!r}") | |
344 else: | |
345 avatar_url = self.apg.buildAPURL("avatar", filename) | |
346 actor_data["icon"] = { | |
347 "type": "Image", | |
348 "url": avatar_url, | |
349 "mediaType": media_type | |
350 } | |
351 | |
352 return actor_data | |
325 | 353 |
326 def getCanonicalURL(self, request: "HTTPRequest") -> str: | 354 def getCanonicalURL(self, request: "HTTPRequest") -> str: |
327 return parse.urljoin( | 355 return parse.urljoin( |
328 f"https://{self.apg.public_url}", | 356 f"https://{self.apg.public_url}", |
329 request.path.decode().rstrip("/") | 357 request.path.decode().rstrip("/") |
616 if request_type != "shared_inbox": | 644 if request_type != "shared_inbox": |
617 raise exceptions.DataError(f"Invalid request type: {request_type!r}") | 645 raise exceptions.DataError(f"Invalid request type: {request_type!r}") |
618 ret_data = await self.APInboxRequest( | 646 ret_data = await self.APInboxRequest( |
619 request, None, None, None, ap_url, signing_actor | 647 request, None, None, None, ap_url, signing_actor |
620 ) | 648 ) |
649 elif request_type == "avatar": | |
650 if len(extra_args) != 1: | |
651 raise exceptions.DataError("avatar argument expected in URL") | |
652 avatar_filename = extra_args[0] | |
653 avatar_path = self.apg.host.common_cache.getPath(avatar_filename) | |
654 return static.File(str(avatar_path)).render(request) | |
621 else: | 655 else: |
622 if len(extra_args) > 1: | 656 if len(extra_args) > 1: |
623 log.warning(f"unexpected extra arguments: {extra_args!r}") | 657 log.warning(f"unexpected extra arguments: {extra_args!r}") |
624 ap_account = extra_args[0] | 658 ap_account = extra_args[0] |
625 account_jid, node = await self.apg.getJIDAndNode(ap_account) | 659 account_jid, node = await self.apg.getJIDAndNode(ap_account) |