Fix Accept header and supply actor outbox

Should help with Mitra among others. Refs #207.
This commit is contained in:
Andrew Godwin 2022-12-19 02:47:35 +00:00
parent ecde831e77
commit 3de188e406
7 changed files with 56 additions and 27 deletions

View file

@ -107,16 +107,11 @@ class PostAdmin(admin.ModelAdmin):
list_display = ["id", "type", "author", "state", "created"]
list_filter = ("type", "local", "visibility", "state", "created")
raw_id_fields = ["to", "mentions", "author", "emojis"]
actions = ["force_fetch", "reparse_hashtags"]
actions = ["reparse_hashtags"]
search_fields = ["content"]
inlines = [PostAttachmentInline]
readonly_fields = ["created", "updated", "state_changed", "object_json"]
@admin.action(description="Force Fetch")
def force_fetch(self, request, queryset):
for instance in queryset:
instance.debug_fetch()
@admin.action(description="Reprocess content for hashtags")
def reparse_hashtags(self, request, queryset):
for instance in queryset:

View file

@ -5,7 +5,6 @@ from typing import Optional
import httpx
import urlman
from asgiref.sync import async_to_sync, sync_to_async
from django.conf import settings
from django.contrib.postgres.indexes import GinIndex
from django.db import models, transaction
from django.template import loader
@ -874,24 +873,6 @@ class Post(StatorModel):
raise ValueError("Actor on delete does not match object")
post.delete()
def debug_fetch(self):
"""
Fetches the Post from its original URL again and updates us with it
"""
response = httpx.get(
self.object_uri,
headers={
"Accept": "application/json",
"User-Agent": settings.TAKAHE_USER_AGENT,
},
follow_redirects=True,
)
if 200 <= response.status_code < 300:
return self.by_ap(
canonicalise(response.json(), include_security=True),
update=True,
)
### Mastodon API ###
def to_mastodon_json(self, interactions=None):

View file

@ -200,7 +200,9 @@ class HttpSignature:
body_bytes = b""
# GET requests get implicit accept headers added
if method == "get":
headers["Accept"] = "application/activity+json"
headers[
"Accept"
] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
# Sign the headers
signed_string = "\n".join(
f"{name.lower()}: {value}" for name, value in headers.items()

View file

@ -156,6 +156,7 @@ urlpatterns = [
# Identity views
path("@<handle>/", identity.ViewIdentity.as_view()),
path("@<handle>/inbox/", activitypub.Inbox.as_view()),
path("@<handle>/outbox/", activitypub.Outbox.as_view()),
path("@<handle>/action/", identity.ActionIdentity.as_view()),
path("@<handle>/rss/", identity.IdentityFeed()),
path("@<handle>/report/", report.SubmitReport.as_view()),
@ -233,6 +234,7 @@ urlpatterns = [
path("nodeinfo/2.0/", activitypub.NodeInfo2.as_view()),
path("actor/", activitypub.SystemActorView.as_view()),
path("actor/inbox/", activitypub.Inbox.as_view()),
path("actor/outbox/", activitypub.EmptyOutbox.as_view()),
path("inbox/", activitypub.Inbox.as_view(), name="shared_inbox"),
# API/Oauth
path("api/", api_router.urls),

View file

@ -335,6 +335,7 @@ class Identity(StatorModel):
"id": self.actor_uri,
"type": "Person",
"inbox": self.actor_uri + "inbox/",
"outbox": self.actor_uri + "outbox/",
"preferredUsername": self.username,
"publicKey": {
"id": self.public_key_id,

View file

@ -43,6 +43,7 @@ class SystemActor:
"id": self.actor_uri,
"type": "Application",
"inbox": self.actor_uri + "inbox/",
"outbox": self.actor_uri + "outbox/",
"endpoints": {
"sharedInbox": f"https://{settings.MAIN_DOMAIN}/inbox/",
},

View file

@ -2,7 +2,7 @@ import json
from asgiref.sync import async_to_sync
from django.conf import settings
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
from django.http import Http404, HttpResponse, HttpResponseBadRequest, JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
@ -208,6 +208,53 @@ class Inbox(View):
return HttpResponse(status=202)
class Outbox(View):
"""
The ActivityPub outbox for an identity
"""
def get(self, request, handle):
self.identity = by_handle_or_404(
self.request,
handle,
local=False,
fetch=True,
)
# If this not a local actor, 404
if not self.identity.local:
raise Http404("Not a local identity")
# Return an ordered collection with the most recent 10 public posts
posts = list(self.identity.posts.not_hidden().public()[:10])
return JsonResponse(
canonicalise(
{
"type": "OrderedCollection",
"totalItems": len(posts),
"orderedItems": [post.to_ap() for post in posts],
}
),
content_type="application/activity+json",
)
class EmptyOutbox(View):
"""
A fixed-empty outbox for the system actor
"""
def get(self, request, *args, **kwargs):
return JsonResponse(
canonicalise(
{
"type": "OrderedCollection",
"totalItems": 0,
"orderedItems": [],
}
),
content_type="application/activity+json",
)
@method_decorator(cache_page(), name="dispatch")
class SystemActorView(View):
"""