summaryrefslogtreecommitdiffstats
path: root/users/models
diff options
context:
space:
mode:
Diffstat (limited to 'users/models')
-rw-r--r--users/models/__init__.py1
-rw-r--r--users/models/report.py129
2 files changed, 130 insertions, 0 deletions
diff --git a/users/models/__init__.py b/users/models/__init__.py
index 1c5f519..4e271ba 100644
--- a/users/models/__init__.py
+++ b/users/models/__init__.py
@@ -5,6 +5,7 @@ from .identity import Identity, IdentityStates # noqa
from .inbox_message import InboxMessage, InboxMessageStates # noqa
from .invite import Invite # noqa
from .password_reset import PasswordReset # noqa
+from .report import Report # noqa
from .system_actor import SystemActor # noqa
from .user import User # noqa
from .user_event import UserEvent # noqa
diff --git a/users/models/report.py b/users/models/report.py
new file mode 100644
index 0000000..7bafd0c
--- /dev/null
+++ b/users/models/report.py
@@ -0,0 +1,129 @@
+import httpx
+import urlman
+from django.db import models
+
+from core.ld import canonicalise
+from stator.models import State, StateField, StateGraph, StatorModel
+from users.models import SystemActor
+
+
+class ReportStates(StateGraph):
+ new = State(try_interval=600)
+ sent = State()
+
+ new.transitions_to(sent)
+
+ @classmethod
+ async def handle_new(cls, instance: "Report"):
+ """
+ Sends the report to the remote server if we need to
+ """
+ report = await instance.afetch_full()
+ if report.forward and not report.subject_identity.domain.local:
+ system_actor = SystemActor()
+ try:
+ await system_actor.signed_request(
+ method="post",
+ uri=report.subject_identity.inbox_uri,
+ body=canonicalise(report.to_ap()),
+ )
+ except httpx.RequestError:
+ return
+ return cls.sent
+
+
+class Report(StatorModel):
+ """
+ A complaint about a user or post.
+ """
+
+ class Types(models.TextChoices):
+ spam = "spam"
+ hateful = "hateful"
+ illegal = "illegal"
+ remote = "remote"
+ other = "other"
+
+ state = StateField(ReportStates)
+
+ subject_identity = models.ForeignKey(
+ "users.Identity",
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True,
+ related_name="reports",
+ )
+ subject_post = models.ForeignKey(
+ "activities.Post",
+ on_delete=models.SET_NULL,
+ blank=True,
+ null=True,
+ related_name="reports",
+ )
+
+ source_identity = models.ForeignKey(
+ "users.Identity",
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True,
+ related_name="filed_reports",
+ )
+ source_domain = models.ForeignKey(
+ "users.Domain",
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True,
+ related_name="filed_reports",
+ )
+
+ moderator = models.ForeignKey(
+ "users.Identity",
+ on_delete=models.SET_NULL,
+ blank=True,
+ null=True,
+ related_name="moderated_reports",
+ )
+
+ type = models.CharField(max_length=100, choices=Types.choices)
+ complaint = models.TextField()
+ forward = models.BooleanField(default=False)
+ valid = models.BooleanField(null=True)
+
+ seen = models.DateTimeField(blank=True, null=True)
+ resolved = models.DateTimeField(blank=True, null=True)
+ notes = models.TextField(blank=True, null=True)
+
+ created = models.DateTimeField(auto_now_add=True)
+ updated = models.DateTimeField(auto_now=True)
+
+ class urls(urlman.Urls):
+ admin = "/admin/reports/"
+ admin_view = "{admin}{self.pk}/"
+
+ ### 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)
+
+ def to_ap(self):
+ system_actor = SystemActor()
+ if self.subject_post:
+ objects = [
+ self.subject_post.object_uri,
+ self.subject_identity.actor_uri,
+ ]
+ else:
+ objects = self.subject_identity.actor_uri
+ return {
+ "id": f"https://{self.source_domain.uri_domain}/reports/{self.id}/",
+ "type": "Flag",
+ "actor": system_actor.actor_uri,
+ "object": objects,
+ "content": self.complaint,
+ }