diff --git a/catalog/podcast/models.py b/catalog/podcast/models.py
index e2bd6047..52eb36b0 100644
--- a/catalog/podcast/models.py
+++ b/catalog/podcast/models.py
@@ -111,11 +111,11 @@ class PodcastEpisode(Item):
def cover_image_url(self):
return self.cover_url or self.program.cover_image_url
- def get_absolute_url_with_position(self, position=None):
+ def get_url_with_position(self, position=None):
return (
- self.absolute_url
+ self.url
if position is None or position == ""
- else f"{self.absolute_url}?position={position}"
+ else f"{self.url}?position={position}"
)
class Meta:
diff --git a/journal/models/comment.py b/journal/models/comment.py
index 78189ff5..29453dc8 100644
--- a/journal/models/comment.py
+++ b/journal/models/comment.py
@@ -17,7 +17,7 @@ class Comment(Content):
@property
def ap_object(self):
- return {
+ d = {
"id": self.absolute_url,
"type": "Comment",
"content": self.text,
@@ -27,6 +27,10 @@ class Comment(Content):
"relatedWith": 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_id, visibility):
@@ -42,6 +46,8 @@ class Comment(Content):
"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
@@ -65,7 +71,7 @@ class Comment(Content):
@property
def item_url(self):
if self.metadata.get("position"):
- return self.item.get_absolute_url_with_position(self.metadata["position"])
+ return self.item.get_url_with_position(self.metadata["position"])
else:
return self.item.url
diff --git a/journal/models/shelf.py b/journal/models/shelf.py
index 0560325f..fd6abb9a 100644
--- a/journal/models/shelf.py
+++ b/journal/models/shelf.py
@@ -80,6 +80,7 @@ class ShelfMember(ListMember):
def update_by_ap_object(
cls, owner: APIdentity, item: Identity, obj: dict, post_id: int, visibility: int
):
+ # TODO check timestamp? (update may come in with inconsistent sequence)
if not obj:
cls.objects.filter(owner=owner, item=item).delete()
return
diff --git a/journal/views/__init__.py b/journal/views/__init__.py
index aa58787f..5e78ce29 100644
--- a/journal/views/__init__.py
+++ b/journal/views/__init__.py
@@ -15,16 +15,7 @@ from .collection import (
user_liked_collection_list,
)
from .common import piece_delete
-from .mark import (
- comment,
- like,
- mark,
- mark_log,
- share_comment,
- unlike,
- user_mark_list,
- wish,
-)
+from .mark import comment, like, mark, mark_log, unlike, user_mark_list, wish
from .post import piece_replies, post_like, post_replies, post_reply, post_unlike
from .profile import profile, user_calendar_data
from .review import ReviewFeed, review_edit, review_retrieve, user_review_list
diff --git a/journal/views/mark.py b/journal/views/mark.py
index 3a93bd8b..f5821ece 100644
--- a/journal/views/mark.py
+++ b/journal/views/mark.py
@@ -13,12 +13,7 @@ from django.utils.translation import gettext_lazy as _
from catalog.models import *
from common.utils import AuthedHttpRequest, PageLinksGenerator, get_uuid_or_404
-from mastodon.api import (
- get_spoiler_text,
- get_status_id_by_url,
- get_visibility,
- post_toot,
-)
+from mastodon.api import boost_toot
from takahe.utils import Takahe
from ..models import Comment, Mark, Piece, ShelfType, ShelfTypeNames, TagManager
@@ -168,36 +163,6 @@ def mark(request: AuthedHttpRequest, item_uuid):
raise BadRequest()
-def share_comment(user, item, text, visibility, shared_link=None, position=None):
- post_error = False
- status_id = get_status_id_by_url(shared_link)
- link = (
- item.get_absolute_url_with_position(position) if position else item.absolute_url
- )
- action_label = "评论" if text else "分享"
- status = f"{action_label}{ItemCategory(item.category).label}《{item.display_title}》\n{link}\n\n{text}"
- spoiler, status = get_spoiler_text(status, item)
- try:
- response = post_toot(
- user.mastodon_site,
- status,
- get_visibility(visibility, user),
- user.mastodon_token,
- False,
- status_id,
- spoiler,
- )
- if response and response.status_code in [200, 201]:
- j = response.json()
- if "url" in j:
- shared_link = j["url"]
- except Exception as e:
- if settings.DEBUG:
- raise
- post_error = True
- return post_error, shared_link
-
-
@login_required
def mark_log(request: AuthedHttpRequest, item_uuid, log_id):
"""
@@ -220,15 +185,7 @@ def comment(request: AuthedHttpRequest, item_uuid):
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
if not item.class_name in ["podcastepisode", "tvepisode"]:
raise BadRequest("不支持评论此类型的条目")
- # episode = None
- # if item.class_name == "tvseason":
- # try:
- # episode = int(request.POST.get("episode", 0))
- # except:
- # episode = 0
- # if episode <= 0:
- # raise BadRequest("请输入正确的集数")
- comment = Comment.objects.filter(owner=request.user, item=item).first()
+ comment = Comment.objects.filter(owner=request.user.identity, item=item).first()
if request.method == "GET":
return render(
request,
@@ -256,49 +213,22 @@ def comment(request: AuthedHttpRequest, item_uuid):
if settings.DEBUG:
raise
position = None
- share_to_mastodon = bool(request.POST.get("share_to_mastodon", default=False))
- shared_link = comment.metadata.get("shared_link") if comment else None
- post_error = False
- if share_to_mastodon and request.user.mastodon_username:
- post_error, shared_link = share_comment(
- request.user, item, text, visibility, shared_link, position
- )
- Comment.objects.update_or_create(
- owner=request.user,
- item=item,
- # metadata__episode=episode,
- defaults={
- "text": text,
- "visibility": visibility,
- "metadata": {
- "shared_link": shared_link,
- "position": position,
- },
- },
+ d = {"text": text, "visibility": visibility}
+ if position:
+ d["metadata"] = {"position": position}
+ comment, _ = Comment.objects.update_or_create(
+ owner=request.user.identity, item=item, defaults=d
)
-
- # if comment:
- # comment.visibility = visibility
- # comment.text = text
- # comment.metadata["position"] = position
- # comment.metadata["episode"] = episode
- # if shared_link:
- # comment.metadata["shared_link"] = shared_link
- # comment.save()
- # else:
- # comment = Comment.objects.create(
- # owner=request.user,
- # item=item,
- # text=text,
- # visibility=visibility,
- # metadata={
- # "shared_link": shared_link,
- # "position": position,
- # "episode": episode,
- # },
- # )
- if post_error:
- return render_relogin(request)
+ post = Takahe.post_comment(comment, False)
+ share_to_mastodon = bool(request.POST.get("share_to_mastodon", default=False))
+ if post and share_to_mastodon and request.user.mastodon_username:
+ boost_toot(
+ request.user.mastodon_site,
+ request.user.mastodon_token,
+ post.url,
+ )
+ # if post_error:
+ # return render_relogin(request)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
raise BadRequest()
diff --git a/takahe/ap_handlers.py b/takahe/ap_handlers.py
index b5aabce7..029a6118 100644
--- a/takahe/ap_handlers.py
+++ b/takahe/ap_handlers.py
@@ -18,6 +18,7 @@ _supported_ap_catalog_item_types = [
"Album",
"Game",
"Podcast",
+ "PodcastEpisode",
"Performance",
"PerformanceProduction",
]
@@ -30,14 +31,12 @@ _supported_ap_journal_types = {
}
-def _parse_item_links(objects):
+def _parse_items(objects):
logger.debug(f"Parsing item links from {objects}")
if not objects:
return []
objs = objects if isinstance(objects, list) else [objects]
- items = [
- obj["href"] for obj in objs if obj["type"] in _supported_ap_catalog_item_types
- ]
+ items = [obj for obj in objs if obj["type"] in _supported_ap_catalog_item_types]
return items
@@ -55,8 +54,14 @@ def _parse_piece_objects(objects):
return pieces
-def _get_or_create_item_by_ap_url(url):
- logger.debug(f"Fetching item by ap from {url}")
+def _get_or_create_item(item_obj):
+ logger.debug(f"Fetching item by ap from {item_obj}")
+ typ = item_obj["type"]
+ url = item_obj["href"]
+ if typ in ["TVEpisode", "PodcastEpisode"]:
+ # TODO support episode item
+ # match and fetch parent item first
+ return None
site = SiteManager.get_site_by_url(url)
if not site:
return None
@@ -75,13 +80,13 @@ def _get_visibility(post_visibility):
return 0
-def _update_or_create_post(pk, obj):
+def post_fetched(pk, obj):
post = Post.objects.get(pk=pk)
owner = Takahe.get_or_create_remote_apidentity(post.author)
if not post.type_data:
logger.warning(f"Post {post} has no type_data")
return
- items = _parse_item_links(post.type_data["object"]["tag"])
+ items = _parse_items(post.type_data["object"]["tag"])
pieces = _parse_piece_objects(post.type_data["object"]["relatedWith"])
logger.info(f"Post {post} has items {items} and pieces {pieces}")
if len(items) == 0:
@@ -90,20 +95,15 @@ def _update_or_create_post(pk, obj):
elif len(items) > 1:
logger.warning(f"Post {post} has more than one remote item")
return
- remote_url = items[0]
- item = _get_or_create_item_by_ap_url(remote_url)
+ item = _get_or_create_item(items[0])
if not item:
- logger.warning(f"Post {post} has no local item")
+ logger.warning(f"Post {post} has no local item matched or created")
return
for p in pieces:
cls = _supported_ap_journal_types[p["type"]]
cls.update_by_ap_object(owner, item, p, pk, _get_visibility(post.visibility))
-def post_fetched(pk, obj):
- _update_or_create_post(pk, obj)
-
-
def post_deleted(pk, obj):
Piece.objects.filter(posts__id=pk, local=False).delete()
diff --git a/takahe/utils.py b/takahe/utils.py
index 897f357a..4fbc543e 100644
--- a/takahe/utils.py
+++ b/takahe/utils.py
@@ -397,6 +397,50 @@ class Takahe:
def delete_posts(post_pks):
Post.objects.filter(pk__in=post_pks).update(state="deleted")
+ @staticmethod
+ def post_comment(comment, share_as_new_post: bool) -> Post | None:
+ from catalog.common import ItemCategory
+
+ user = comment.owner.user
+ category = str(ItemCategory(comment.item.category).label)
+ tags = (
+ "\n" + user.preference.mastodon_append_tag.replace("[category]", category)
+ if user.preference.mastodon_append_tag
+ else ""
+ )
+ item_link = f"{settings.SITE_INFO['site_url']}/~neodb~{comment.item_url}"
+ action_label = "评论" if comment.text else "分享"
+ pre_conetent = f'{action_label}{category}《{comment.item.display_title}》
'
+ content = f"{comment.text}\n{tags}"
+ data = {
+ "object": {
+ "tag": [comment.item.ap_object_ref],
+ "relatedWith": [comment.ap_object],
+ }
+ }
+ if comment.visibility == 1:
+ v = Takahe.Visibilities.followers
+ elif comment.visibility == 2:
+ v = Takahe.Visibilities.mentioned
+ elif user.preference.mastodon_publish_public:
+ v = Takahe.Visibilities.public
+ else:
+ v = Takahe.Visibilities.unlisted
+ existing_post = None if share_as_new_post else comment.latest_post
+ post = Takahe.post( # TODO post as Article?
+ comment.owner.pk,
+ pre_conetent,
+ content,
+ v,
+ data,
+ existing_post.pk if existing_post else None,
+ comment.created_time,
+ )
+ if not post:
+ return
+ comment.link_post(post)
+ return post
+
@staticmethod
def post_review(review, share_as_new_post: bool) -> Post | None:
from catalog.common import ItemCategory