from datetime import datetime
from functools import cached_property
from typing import Any
from django.conf import settings
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext as _
from catalog.models import Item
from takahe.utils import Takahe
from users.models import APIdentity
from .common import Content
from .rating import Rating
from .renderers import render_post_with_macro, render_spoiler_text, render_text
from .shelf import ShelfManager, ShelfType
class Comment(Content):
text = models.TextField(blank=False, null=False)
@property
def ap_object(self):
d = {
"id": self.absolute_url,
"type": "Comment",
"content": self.text,
"published": self.created_time.isoformat(),
"updated": self.edited_time.isoformat(),
"attributedTo": self.owner.actor_uri,
"withRegardTo": self.item.absolute_url,
"href": self.absolute_url,
}
if self.metadata.get("position"):
d["relatedWithItemPosition"] = self.metadata["position"]
d["relatedWithItemPositionType"] = "time"
return d
@classmethod
def update_by_ap_object(cls, owner, item, obj, post, crosspost=None):
if post.local: # ignore local user updating their post via Mastodon API
return
p = cls.objects.filter(owner=owner, item=item).first()
if p and p.edited_time >= datetime.fromisoformat(obj["updated"]):
return p # incoming ap object is older than what we have, no update needed
content = obj.get("content", "").strip() if obj else ""
if not content:
cls.objects.filter(owner=owner, item=item).delete()
return
d = {
"text": content,
"local": False,
"remote_id": obj["id"],
"visibility": Takahe.visibility_t2n(post.visibility),
"created_time": datetime.fromisoformat(obj["published"]),
"edited_time": datetime.fromisoformat(obj["updated"]),
}
if obj.get("relatedWithItemPosition"):
d["metadata"] = {"position": obj["relatedWithItemPosition"]}
p, _ = cls.objects.update_or_create(owner=owner, item=item, defaults=d)
p.link_post_id(post.id)
return p
@property
def html(self):
return render_text(self.text)
@cached_property
def rating_grade(self):
return Rating.get_item_rating(self.item, self.owner)
@cached_property
def mark(self):
from .mark import Mark
m = Mark(self.owner, self.item)
m.comment = self
return m
@property
def item_url(self):
if self.metadata.get("position"):
return self.item.get_url_with_position(self.metadata["position"])
else:
return self.item.url
@staticmethod
def comment_item(
item: Item, owner: APIdentity, text: str | None, visibility=0, created_time=None
):
comment = Comment.objects.filter(owner=owner, item=item).first()
if not text:
if comment is not None:
comment.delete()
comment = None
elif comment is None:
comment = Comment.objects.create(
owner=owner,
item=item,
text=text,
visibility=visibility,
created_time=created_time or timezone.now(),
)
elif comment.text != text or comment.visibility != visibility:
comment.text = text
comment.visibility = visibility
if created_time:
comment.created_time = created_time
comment.save()
return comment
def get_crosspost_postfix(self):
tags = render_post_with_macro(
self.owner.user.preference.mastodon_append_tag, self.item
)
return "\n" + tags if tags else ""
def get_crosspost_template(self):
return _(
ShelfManager.get_action_template(ShelfType.PROGRESS, self.item.category)
)
def to_crosspost_params(self):
spoiler_text, txt = render_spoiler_text(self.text, self.item)
txt = "\n" + txt if txt else ""
action = self.get_crosspost_template().format(item="##obj##")
content = f"{action}\n##obj_link_if_plain##{txt}{self.get_crosspost_postfix()}"
params = {
"content": content,
"spoiler_text": spoiler_text,
"sensitive": bool(spoiler_text),
"obj": self.item,
}
return params
def to_post_params(self):
item_link = f"{settings.SITE_INFO['site_url']}/~neodb~{self.item_url}"
prepend_content = (
self.get_crosspost_template().format(
item=f'{self.item.display_title}'
)
+ "
"
)
spoiler_text, txt = render_spoiler_text(self.text, self.item)
content = f"{txt}\n{self.get_crosspost_postfix()}"
return {
"prepend_content": prepend_content,
"content": content,
"summary": spoiler_text,
"sensitive": bool(spoiler_text),
}
@cached_property
def sibling_shelfmember(self):
from .shelf import ShelfMember
return ShelfMember.objects.filter(owner=self.owner, item=self.item).first()
def to_indexable_doc(self) -> dict[str, Any]:
if self.sibling_shelfmember:
return {}
return {
"item_id": [self.item.id],
"item_class": [self.item.__class__.__name__],
"item_title": self.item.to_indexable_titles(),
"rating": self.rating_grade or 0,
"content": [self.text],
}