Remove all remaining async code for now

This commit is contained in:
Andrew Godwin 2023-07-17 00:37:47 -06:00
parent 0915b17c4b
commit 188e5a2446
19 changed files with 114 additions and 185 deletions

View file

@ -4,7 +4,6 @@ from typing import ClassVar
import httpx
import urlman
from asgiref.sync import sync_to_async
from cachetools import TTLCache, cached
from django.conf import settings
from django.core.exceptions import ValidationError
@ -35,13 +34,13 @@ class EmojiStates(StateGraph):
outdated.transitions_to(updated)
@classmethod
async def handle_outdated(cls, instance: "Emoji"):
def handle_outdated(cls, instance: "Emoji"):
"""
Fetches remote emoji and uploads to file for local caching
"""
if instance.remote_url and not instance.file:
try:
file, mimetype = await get_remote_file(
file, mimetype = get_remote_file(
instance.remote_url,
timeout=settings.SETUP.REMOTE_TIMEOUT,
max_size=settings.SETUP.EMOJI_MAX_IMAGE_FILESIZE_KB * 1024,
@ -55,7 +54,7 @@ class EmojiStates(StateGraph):
instance.file = file
instance.mimetype = mimetype
await sync_to_async(instance.save)()
instance.save()
return cls.updated

View file

@ -1,5 +1,4 @@
import httpx
from asgiref.sync import async_to_sync
from django.db import models
from activities.models.timeline_event import TimelineEvent
@ -77,7 +76,7 @@ class FanOutStates(StateGraph):
post = instance.subject_post
# Sign it and send it
try:
async_to_sync(post.author.signed_request)(
post.author.signed_request(
method="post",
uri=(
instance.identity.shared_inbox_uri
@ -93,7 +92,7 @@ class FanOutStates(StateGraph):
post = instance.subject_post
# Sign it and send it
try:
async_to_sync(post.author.signed_request)(
post.author.signed_request(
method="post",
uri=(
instance.identity.shared_inbox_uri
@ -119,7 +118,7 @@ class FanOutStates(StateGraph):
post = instance.subject_post
# Send it to the remote inbox
try:
async_to_sync(post.author.signed_request)(
post.author.signed_request(
method="post",
uri=(
instance.identity.shared_inbox_uri
@ -172,7 +171,7 @@ class FanOutStates(StateGraph):
body = interaction.to_add_ap()
else:
body = interaction.to_create_ap()
async_to_sync(interaction.identity.signed_request)(
interaction.identity.signed_request(
method="post",
uri=(
instance.identity.shared_inbox_uri
@ -202,7 +201,7 @@ class FanOutStates(StateGraph):
body = interaction.to_remove_ap()
else:
body = interaction.to_undo_ap()
async_to_sync(interaction.identity.signed_request)(
interaction.identity.signed_request(
method="post",
uri=(
instance.identity.shared_inbox_uri
@ -217,7 +216,7 @@ class FanOutStates(StateGraph):
case (FanOut.Types.identity_edited, False):
identity = instance.subject_identity
try:
async_to_sync(identity.signed_request)(
identity.signed_request(
method="post",
uri=(
instance.identity.shared_inbox_uri
@ -232,7 +231,7 @@ class FanOutStates(StateGraph):
case (FanOut.Types.identity_deleted, False):
identity = instance.subject_identity
try:
async_to_sync(identity.signed_request)(
identity.signed_request(
method="post",
uri=(
instance.identity.shared_inbox_uri

View file

@ -8,7 +8,6 @@ from urllib.parse import urlparse
import httpx
import urlman
from asgiref.sync import async_to_sync
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVector
from django.db import models, transaction
@ -831,7 +830,7 @@ class Post(StatorModel):
# If the author is not fetched yet, try again later
if author.domain is None:
if fetch_author:
async_to_sync(author.fetch_actor)()
author.fetch_actor()
# perhaps the entire "try again" logic below
# could be replaced with TryAgainLater for
# _all_ fetches, to let it handle pinned posts?
@ -981,7 +980,7 @@ class Post(StatorModel):
except cls.DoesNotExist:
if fetch:
try:
response = async_to_sync(SystemActor().signed_request)(
response = SystemActor().signed_request(
method="get", uri=object_uri
)
except (httpx.HTTPError, ssl.SSLCertVerificationError):
@ -1008,7 +1007,7 @@ class Post(StatorModel):
) from err
# We may need to fetch the author too
if post.author.state == IdentityStates.outdated:
async_to_sync(post.author.fetch_actor)()
post.author.fetch_actor()
return post
else:
raise cls.DoesNotExist(f"Cannot find Post with URI {object_uri}")

View file

@ -1,5 +1,4 @@
import httpx
from asgiref.sync import async_to_sync
from activities.models import Hashtag, Post
from core.ld import canonicalise
@ -49,7 +48,7 @@ class SearchService:
username, domain_instance or domain, fetch=True
)
if identity and identity.state == IdentityStates.outdated:
async_to_sync(identity.fetch_actor)()
identity.fetch_actor()
except ValueError:
pass
@ -74,7 +73,7 @@ class SearchService:
# Fetch the provided URL as the system actor to retrieve the AP JSON
try:
response = async_to_sync(SystemActor().signed_request)(
response = SystemActor().signed_request(
method="get",
uri=self.query,
)
@ -90,7 +89,7 @@ class SearchService:
# Try and retrieve the profile by actor URI
identity = Identity.by_actor_uri(document["id"], create=True)
if identity and identity.state == IdentityStates.outdated:
async_to_sync(identity.fetch_actor)()
identity.fetch_actor()
return identity
# Is it a post?

View file

@ -1,7 +1,6 @@
import json
import httpx
from asgiref.sync import async_to_sync
from django import forms
from django.utils.decorators import method_decorator
from django.views.generic import FormView, TemplateView
@ -13,7 +12,6 @@ from users.models import SystemActor
@method_decorator(admin_required, name="dispatch")
class JsonViewer(FormView):
template_name = "activities/debug_json.html"
class form_class(forms.Form):
@ -31,7 +29,7 @@ class JsonViewer(FormView):
context = self.get_context_data(form=form)
try:
response = async_to_sync(SystemActor().signed_request)(
response = SystemActor().signed_request(
method="get",
uri=uri,
)
@ -64,18 +62,15 @@ class JsonViewer(FormView):
class NotFound(TemplateView):
template_name = "404.html"
class ServerError(TemplateView):
template_name = "500.html"
@method_decorator(admin_required, name="dispatch")
class OauthAuthorize(TemplateView):
template_name = "api/oauth_authorize.html"
def get_context_data(self):

View file

@ -57,7 +57,7 @@ def blurhash_image(file) -> str:
return blurhash.encode(file, 4, 4)
async def get_remote_file(
def get_remote_file(
url: str,
*,
timeout: float = settings.SETUP.REMOTE_TIMEOUT,
@ -70,8 +70,8 @@ async def get_remote_file(
"User-Agent": settings.TAKAHE_USER_AGENT,
}
async with httpx.AsyncClient(headers=headers) as client:
async with client.stream(
with httpx.Client(headers=headers) as client:
with client.stream(
"GET", url, timeout=timeout, follow_redirects=True
) as stream:
allow_download = max_size is None
@ -82,7 +82,7 @@ async def get_remote_file(
except (KeyError, TypeError):
pass
if allow_download:
file = ContentFile(await stream.aread(), name=url)
file = ContentFile(stream.read(), name=url)
return file, stream.headers.get(
"content-type", "application/octet-stream"
)

View file

@ -177,7 +177,7 @@ class HttpSignature:
)
@classmethod
async def signed_request(
def signed_request(
cls,
uri: str,
body: dict | None,
@ -241,9 +241,9 @@ class HttpSignature:
# Send the request with all those headers except the pseudo one
del headers["(request-target)"]
async with httpx.AsyncClient(timeout=timeout) as client:
with httpx.Client(timeout=timeout) as client:
try:
response = await client.request(
response = client.request(
method,
uri,
headers=headers,

View file

@ -238,10 +238,6 @@ def stator(config_system) -> StatorRunner:
"""
Return an initialized StatorRunner for tests that need state transitioning
to happen.
Example:
# Do some tasks with state side effects
async_to_sync(stator_runner.fetch_and_process_tasks)()
"""
runner = StatorRunner(
StatorModel.subclasses,

View file

@ -1,5 +1,4 @@
import pytest
from asgiref.sync import async_to_sync
from django.test.client import RequestFactory
from pytest_httpx import HTTPXMock
@ -75,7 +74,7 @@ def test_sign_http(httpx_mock: HTTPXMock, keypair):
}
# Send the signed request to the mock library
httpx_mock.add_response()
async_to_sync(HttpSignature.signed_request)(
HttpSignature.signed_request(
uri="https://example.com/test-actor",
body=document,
private_key=keypair["private_key"],

View file

@ -1,5 +1,4 @@
import pytest
from asgiref.sync import async_to_sync
from pytest_httpx import HTTPXMock
from core.models import Config
@ -169,7 +168,7 @@ def test_fetch_actor(httpx_mock, config_system):
"url": "https://example.com/test-actor/view/",
},
)
async_to_sync(identity.fetch_actor)()
identity.fetch_actor()
# Verify the data arrived
identity = Identity.objects.get(pk=identity.pk)
@ -189,15 +188,14 @@ def test_fetch_actor(httpx_mock, config_system):
@pytest.mark.django_db
@pytest.mark.asyncio
async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system):
def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system):
"""
Ensures that we can deal with various kinds of webfinger URLs
"""
# With no host-meta, it should be the default
assert (
await Identity.fetch_webfinger_url("example.com")
Identity.fetch_webfinger_url("example.com")
== "https://example.com/.well-known/webfinger?resource={uri}"
)
@ -210,7 +208,7 @@ async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system):
</XRD>""",
)
assert (
await Identity.fetch_webfinger_url("example.com")
Identity.fetch_webfinger_url("example.com")
== "https://fedi.example.com/.well-known/webfinger?resource={uri}"
)
@ -223,7 +221,7 @@ async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system):
</XRD>""",
)
assert (
await Identity.fetch_webfinger_url("example.com")
Identity.fetch_webfinger_url("example.com")
== "https://example.com/amazing-webfinger?query={uri}"
)
@ -237,7 +235,7 @@ async def test_fetch_webfinger_url(httpx_mock: HTTPXMock, config_system):
</XRD>""",
)
assert (
await Identity.fetch_webfinger_url("example.com")
Identity.fetch_webfinger_url("example.com")
== "https://example.com/.well-known/webfinger?resource={uri}"
)

View file

@ -1,5 +1,4 @@
import pytest
from asgiref.sync import async_to_sync
from django.test.client import RequestFactory
from pytest_httpx import HTTPXMock
@ -16,7 +15,7 @@ def test_system_actor_signed(config_system, httpx_mock: HTTPXMock):
system_actor.generate_keys()
# Send a fake outbound request
httpx_mock.add_response()
async_to_sync(system_actor.signed_request)(
system_actor.signed_request(
method="get",
uri="http://example.com/test-actor",
)

View file

@ -1,4 +1,3 @@
from asgiref.sync import async_to_sync
from django.contrib import admin
from django.db import models
from django.utils import formats
@ -60,7 +59,7 @@ class DomainAdmin(admin.ModelAdmin):
@admin.action(description="Fetch nodeinfo")
def fetch_nodeinfo(self, request, queryset):
for instance in queryset:
info = async_to_sync(instance.fetch_nodeinfo)()
info = instance.fetch_nodeinfo()
if info:
instance.nodeinfo = info.dict()
instance.save()

View file

@ -30,7 +30,7 @@ class BlockStates(StateGraph):
return [cls.new, cls.sent, cls.awaiting_expiry]
@classmethod
async def handle_new(cls, instance: "Block"):
def handle_new(cls, instance: "Block"):
"""
Block that are new need us to deliver the Block object
to the target server.
@ -38,20 +38,18 @@ class BlockStates(StateGraph):
# Mutes don't send but might need expiry
if instance.mute:
return cls.awaiting_expiry
# Fetch more info
block = await instance.afetch_full()
# Remote blocks should not be here, local blocks just work
if not block.source.local or block.target.local:
if not instance.source.local or instance.target.local:
return cls.sent
# Don't try if the other identity didn't fetch yet
if not block.target.inbox_uri:
if not instance.target.inbox_uri:
return
# Sign it and send it
try:
await block.source.signed_request(
instance.source.signed_request(
method="post",
uri=block.target.inbox_uri,
body=canonicalise(block.to_ap()),
uri=instance.target.inbox_uri,
body=canonicalise(instance.to_ap()),
)
except httpx.RequestError:
return
@ -66,19 +64,18 @@ class BlockStates(StateGraph):
return cls.undone
@classmethod
async def handle_undone(cls, instance: "Block"):
def handle_undone(cls, instance: "Block"):
"""
Delivers the Undo object to the target server
"""
block = await instance.afetch_full()
# Remote blocks should not be here, mutes don't send, local blocks just work
if not block.source.local or block.target.local or instance.mute:
if not instance.source.local or instance.target.local or instance.mute:
return cls.undone_sent
try:
await block.source.signed_request(
instance.source.signed_request(
method="post",
uri=block.target.inbox_uri,
body=canonicalise(block.to_undo_ap()),
uri=instance.target.inbox_uri,
body=canonicalise(instance.to_undo_ap()),
)
except httpx.RequestError:
return
@ -227,16 +224,6 @@ class Block(StatorModel):
def active(self):
return self.state in BlockStates.group_active()
### Async helpers ###
async def afetch_full(self):
"""
Returns a version of the object with all relations pre-loaded
"""
return await Block.objects.select_related(
"source", "source__domain", "target"
).aget(pk=self.pk)
### ActivityPub (outbound) ###
def to_ap(self):

View file

@ -6,7 +6,6 @@ from typing import Optional
import httpx
import pydantic
import urlman
from asgiref.sync import sync_to_async
from django.conf import settings
from django.db import models
@ -33,15 +32,15 @@ class DomainStates(StateGraph):
outdated.times_out_to(connection_issue, 60 * 60 * 24)
@classmethod
async def handle_outdated(cls, instance: "Domain"):
info = await instance.fetch_nodeinfo()
def handle_outdated(cls, instance: "Domain"):
info = instance.fetch_nodeinfo()
if info:
instance.nodeinfo = info.dict()
await sync_to_async(instance.save)()
instance.save()
return cls.updated
@classmethod
async def handle_updated(cls, instance: "Domain"):
def handle_updated(cls, instance: "Domain"):
return cls.outdated
@ -157,18 +156,18 @@ class Domain(StatorModel):
)
super().save(*args, **kwargs)
async def fetch_nodeinfo(self) -> NodeInfo | None:
def fetch_nodeinfo(self) -> NodeInfo | None:
"""
Fetch the /NodeInfo/2.0 for the domain
"""
nodeinfo20_url = f"https://{self.domain}/nodeinfo/2.0"
async with httpx.AsyncClient(
with httpx.Client(
timeout=settings.SETUP.REMOTE_TIMEOUT,
headers={"User-Agent": settings.TAKAHE_USER_AGENT},
) as client:
try:
response = await client.get(
response = client.get(
f"https://{self.domain}/.well-known/nodeinfo",
follow_redirects=True,
headers={"Accept": "application/json"},
@ -190,7 +189,7 @@ class Domain(StatorModel):
pass
try:
response = await client.get(
response = client.get(
nodeinfo20_url,
follow_redirects=True,
headers={"Accept": "application/json"},

View file

@ -34,26 +34,25 @@ class FollowStates(StateGraph):
return [cls.unrequested, cls.local_requested, cls.accepted]
@classmethod
async def handle_unrequested(cls, instance: "Follow"):
def handle_unrequested(cls, instance: "Follow"):
"""
Follows that are unrequested need us to deliver the Follow object
to the target server.
"""
follow = await instance.afetch_full()
# Remote follows should not be here
if not follow.source.local:
if not instance.source.local:
return cls.remote_requested
if follow.target.local:
if instance.target.local:
return cls.accepted
# Don't try if the other identity didn't fetch yet
if not follow.target.inbox_uri:
if not instance.target.inbox_uri:
return
# Sign it and send it
try:
await follow.source.signed_request(
instance.source.signed_request(
method="post",
uri=follow.target.inbox_uri,
body=canonicalise(follow.to_ap()),
uri=instance.target.inbox_uri,
body=canonicalise(instance.to_ap()),
)
except httpx.RequestError:
return
@ -65,33 +64,31 @@ class FollowStates(StateGraph):
pass
@classmethod
async def handle_remote_requested(cls, instance: "Follow"):
def handle_remote_requested(cls, instance: "Follow"):
"""
Items in remote_requested need us to send an Accept object to the
source server.
"""
follow = await instance.afetch_full()
try:
await follow.target.signed_request(
instance.target.signed_request(
method="post",
uri=follow.source.inbox_uri,
body=canonicalise(follow.to_accept_ap()),
uri=instance.source.inbox_uri,
body=canonicalise(instance.to_accept_ap()),
)
except httpx.RequestError:
return
return cls.accepted
@classmethod
async def handle_undone(cls, instance: "Follow"):
def handle_undone(cls, instance: "Follow"):
"""
Delivers the Undo object to the target server
"""
follow = await instance.afetch_full()
try:
await follow.source.signed_request(
instance.source.signed_request(
method="post",
uri=follow.target.inbox_uri,
body=canonicalise(follow.to_undo_ap()),
uri=instance.target.inbox_uri,
body=canonicalise(instance.to_undo_ap()),
)
except httpx.RequestError:
return
@ -204,16 +201,6 @@ class Follow(StatorModel):
follow.save()
return follow
### Async helpers ###
async def afetch_full(self):
"""
Returns a version of the object with all relations pre-loaded
"""
return await Follow.objects.select_related(
"source", "source__domain", "target"
).aget(pk=self.pk)
### Properties ###
@property

View file

@ -5,7 +5,6 @@ from urllib.parse import urlparse
import httpx
import urlman
from asgiref.sync import async_to_sync, sync_to_async
from django.conf import settings
from django.db import IntegrityError, models
from django.utils import timezone
@ -66,13 +65,13 @@ class IdentityStates(StateGraph):
return [cls.deleted, cls.deleted_fanned_out]
@classmethod
async def targets_fan_out(cls, identity: "Identity", type_: str) -> None:
def targets_fan_out(cls, identity: "Identity", type_: str) -> None:
from activities.models import FanOut
from users.models import Follow
# Fan out to each target
shared_inboxes = set()
async for follower in Follow.objects.select_related("source", "target").filter(
for follower in Follow.objects.select_related("source", "target").filter(
target=identity
):
# Dedupe shared_inbox_uri
@ -80,7 +79,7 @@ class IdentityStates(StateGraph):
if shared_uri and shared_uri in shared_inboxes:
continue
await FanOut.objects.acreate(
FanOut.objects.create(
identity=follower.source,
type=type_,
subject_identity=identity,
@ -88,34 +87,32 @@ class IdentityStates(StateGraph):
shared_inboxes.add(shared_uri)
@classmethod
async def handle_edited(cls, instance: "Identity"):
def handle_edited(cls, instance: "Identity"):
from activities.models import FanOut
if not instance.local:
return cls.updated
identity = await instance.afetch_full()
await cls.targets_fan_out(identity, FanOut.Types.identity_edited)
cls.targets_fan_out(instance, FanOut.Types.identity_edited)
return cls.updated
@classmethod
async def handle_deleted(cls, instance: "Identity"):
def handle_deleted(cls, instance: "Identity"):
from activities.models import FanOut
if not instance.local:
return cls.updated
identity = await instance.afetch_full()
await cls.targets_fan_out(identity, FanOut.Types.identity_deleted)
cls.targets_fan_out(instance, FanOut.Types.identity_deleted)
return cls.deleted_fanned_out
@classmethod
async def handle_outdated(cls, identity: "Identity"):
def handle_outdated(cls, identity: "Identity"):
# Local identities never need fetching
if identity.local:
return cls.updated
# Run the actor fetch and progress to updated if it succeeds
if await identity.fetch_actor():
if identity.fetch_actor():
return cls.updated
@classmethod
@ -365,9 +362,7 @@ class Identity(StatorModel):
)
except cls.DoesNotExist:
if fetch and not local:
actor_uri, handle = async_to_sync(cls.fetch_webfinger)(
f"{username}@{domain}"
)
actor_uri, handle = cls.fetch_webfinger(f"{username}@{domain}")
if handle is None:
return None
# See if this actually does match an existing actor
@ -449,14 +444,6 @@ class Identity(StatorModel):
def limited(self) -> bool:
return self.restriction == self.Restriction.limited
### Async helpers ###
async def afetch_full(self):
"""
Returns a version of the object with all relations pre-loaded
"""
return await Identity.objects.select_related("domain").aget(pk=self.pk)
### ActivityPub (outbound) ###
def to_webfinger(self):
@ -637,17 +624,17 @@ class Identity(StatorModel):
### Actor/Webfinger fetching ###
@classmethod
async def fetch_webfinger_url(cls, domain: str):
def fetch_webfinger_url(cls, domain: str):
"""
Given a domain (hostname), returns the correct webfinger URL to use
based on probing host-meta.
"""
async with httpx.AsyncClient(
with httpx.Client(
timeout=settings.SETUP.REMOTE_TIMEOUT,
headers={"User-Agent": settings.TAKAHE_USER_AGENT},
) as client:
try:
response = await client.get(
response = client.get(
f"https://{domain}/.well-known/host-meta",
follow_redirects=True,
headers={"Accept": "application/xml"},
@ -669,24 +656,24 @@ class Identity(StatorModel):
return f"https://{domain}/.well-known/webfinger?resource={{uri}}"
@classmethod
async def fetch_webfinger(cls, handle: str) -> tuple[str | None, str | None]:
def fetch_webfinger(cls, handle: str) -> tuple[str | None, str | None]:
"""
Given a username@domain handle, returns a tuple of
(actor uri, canonical handle) or None, None if it does not resolve.
"""
domain = handle.split("@")[1].lower()
try:
webfinger_url = await cls.fetch_webfinger_url(domain)
webfinger_url = cls.fetch_webfinger_url(domain)
except ssl.SSLCertVerificationError:
return None, None
# Go make a Webfinger request
async with httpx.AsyncClient(
with httpx.Client(
timeout=settings.SETUP.REMOTE_TIMEOUT,
headers={"User-Agent": settings.TAKAHE_USER_AGENT},
) as client:
try:
response = await client.get(
response = client.get(
webfinger_url.format(uri=f"acct:{handle}"),
follow_redirects=True,
headers={"Accept": "application/json"},
@ -730,16 +717,16 @@ class Identity(StatorModel):
return None, None
@classmethod
async def fetch_pinned_post_uris(cls, uri: str) -> list[str]:
def fetch_pinned_post_uris(cls, uri: str) -> list[str]:
"""
Fetch an identity's featured collection.
"""
async with httpx.AsyncClient(
with httpx.Client(
timeout=settings.SETUP.REMOTE_TIMEOUT,
headers={"User-Agent": settings.TAKAHE_USER_AGENT},
) as client:
try:
response = await client.get(
response = client.get(
uri,
follow_redirects=True,
headers={"Accept": "application/activity+json"},
@ -785,7 +772,7 @@ class Identity(StatorModel):
response.content,
)
async def fetch_actor(self) -> bool:
def fetch_actor(self) -> bool:
"""
Fetches the user's actor information, as well as their domain from
webfinger if it's available.
@ -796,7 +783,7 @@ class Identity(StatorModel):
if self.local:
raise ValueError("Cannot fetch local identities")
try:
response = await SystemActor().signed_request(
response = SystemActor().signed_request(
method="get",
uri=self.actor_uri,
)
@ -810,7 +797,7 @@ class Identity(StatorModel):
if status_code >= 400:
if status_code == 410 and self.pk:
# Their account got deleted, so let's do the same.
await Identity.objects.filter(pk=self.pk).adelete()
Identity.objects.filter(pk=self.pk).delete()
if status_code < 500 and status_code not in [401, 403, 404, 406, 410]:
capture_message(
@ -866,44 +853,43 @@ class Identity(StatorModel):
)
# Now go do webfinger with that info to see if we can get a canonical domain
actor_url_parts = urlparse(self.actor_uri)
get_domain = sync_to_async(Domain.get_remote_domain)
if self.username:
webfinger_actor, webfinger_handle = await self.fetch_webfinger(
webfinger_actor, webfinger_handle = self.fetch_webfinger(
f"{self.username}@{actor_url_parts.hostname}"
)
if webfinger_handle:
webfinger_username, webfinger_domain = webfinger_handle.split("@")
self.username = webfinger_username
self.domain = await get_domain(webfinger_domain)
self.domain = Domain.get_remote_domain(webfinger_domain)
else:
self.domain = await get_domain(actor_url_parts.hostname)
self.domain = Domain.get_remote_domain(actor_url_parts.hostname)
else:
self.domain = await get_domain(actor_url_parts.hostname)
self.domain = Domain.get_remote_domain(actor_url_parts.hostname)
# Emojis (we need the domain so we do them here)
for tag in get_list(document, "tag"):
if tag["type"].lower() in ["toot:emoji", "emoji"]:
await sync_to_async(Emoji.by_ap_tag)(self.domain, tag, create=True)
Emoji.by_ap_tag(self.domain, tag, create=True)
# Mark as fetched
self.fetched = timezone.now()
try:
await sync_to_async(self.save)()
self.save()
except IntegrityError as e:
# See if we can fetch a PK and save there
if self.pk is None:
try:
other_row = await Identity.objects.aget(actor_uri=self.actor_uri)
other_row = Identity.objects.get(actor_uri=self.actor_uri)
except Identity.DoesNotExist:
raise ValueError(
f"Could not save Identity at end of actor fetch: {e}"
)
self.pk: int | None = other_row.pk
await sync_to_async(self.save)()
self.save()
# Fetch pinned posts after identity has been fetched and saved
if self.featured_collection_uri:
featured = await self.fetch_pinned_post_uris(self.featured_collection_uri)
featured = self.fetch_pinned_post_uris(self.featured_collection_uri)
service = IdentityService(self)
await sync_to_async(service.sync_pins)(featured)
service.sync_pins(featured)
return True
@ -1016,7 +1002,7 @@ class Identity(StatorModel):
### Cryptography ###
async def signed_request(
def signed_request(
self,
method: Literal["get", "post"],
uri: str,
@ -1025,7 +1011,7 @@ class Identity(StatorModel):
"""
Performs a signed request on behalf of the System Actor.
"""
return await HttpSignature.signed_request(
return HttpSignature.signed_request(
method=method,
uri=uri,
body=body,

View file

@ -2,7 +2,6 @@ from urllib.parse import urlparse
import httpx
import urlman
from asgiref.sync import sync_to_async
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.db import models
@ -22,26 +21,25 @@ class ReportStates(StateGraph):
new.transitions_to(sent)
@classmethod
async def handle_new(cls, instance: "Report"):
def handle_new(cls, instance: "Report"):
"""
Sends the report to the remote server if we need to
"""
from users.models import SystemActor, User
recipients = []
report = await instance.afetch_full()
async for mod in User.objects.filter(
for mod in User.objects.filter(
models.Q(moderator=True) | models.Q(admin=True)
).values_list("email", flat=True):
recipients.append(mod)
if report.forward and not report.subject_identity.domain.local:
if instance.forward and not instance.subject_identity.domain.local:
system_actor = SystemActor()
try:
await system_actor.signed_request(
system_actor.signed_request(
method="post",
uri=report.subject_identity.inbox_uri,
body=canonicalise(report.to_ap()),
uri=instance.subject_identity.inbox_uri,
body=canonicalise(instance.to_ap()),
)
except httpx.RequestError:
pass
@ -50,7 +48,7 @@ class ReportStates(StateGraph):
body=render_to_string(
"emails/report_new.txt",
{
"report": report,
"report": instance,
"config": Config.system,
"settings": settings,
},
@ -62,14 +60,14 @@ class ReportStates(StateGraph):
content=render_to_string(
"emails/report_new.html",
{
"report": report,
"report": instance,
"config": Config.system,
"settings": settings,
},
),
mimetype="text/html",
)
await sync_to_async(email.send)()
email.send()
return cls.sent
@ -145,15 +143,6 @@ class Report(StatorModel):
### ActivityPub ###
async def afetch_full(self) -> "Report":
return await Report.objects.select_related(
"source_identity",
"source_domain",
"subject_identity__domain",
"subject_identity",
"subject_post",
).aget(pk=self.pk)
@classmethod
def handle_ap(cls, data):
"""

View file

@ -79,7 +79,7 @@ class SystemActor:
],
}
async def signed_request(
def signed_request(
self,
method: Literal["get", "post"],
uri: str,
@ -88,7 +88,7 @@ class SystemActor:
"""
Performs a signed request on behalf of the System Actor.
"""
return await HttpSignature.signed_request(
return HttpSignature.signed_request(
method=method,
uri=uri,
body=body,

View file

@ -1,6 +1,5 @@
import json
from asgiref.sync import async_to_sync
from django.conf import settings
from django.http import Http404, HttpResponse, HttpResponseBadRequest, JsonResponse
from django.utils.decorators import method_decorator
@ -140,7 +139,7 @@ class Inbox(View):
if not identity.public_key:
# See if we can fetch it right now
async_to_sync(identity.fetch_actor)()
identity.fetch_actor()
if not identity.public_key:
exceptions.capture_message(