diff --git a/compose.yml b/compose.yml index e0604c7b..6cca442d 100644 --- a/compose.yml +++ b/compose.yml @@ -41,6 +41,7 @@ x-shared: NEODB_SEARCH_URL: ${NEODB_SEARCH_URL:-typesense://user:eggplant@typesense:8108/catalog} NEODB_EMAIL_URL: NEODB_EMAIL_FROM: no-reply@${NEODB_SITE_DOMAIN} + NEODB_ENABLE_LOCAL_ONLY: NEODB_FANOUT_LIMIT_DAYS: TAKAHE_FANOUT_LIMIT_DAYS: NEODB_DOWNLOADER_PROXY_LIST: diff --git a/journal/api.py b/journal/api.py index b77ee93e..dea01ddf 100644 --- a/journal/api.py +++ b/journal/api.py @@ -204,7 +204,7 @@ def review_item(request, item_uuid: str, review: ReviewInSchema): created_time=review.created_time, ) if post and review.post_to_fediverse: - if settings.FORCE_CLASSIC_REPOST: + if request.user.preference.mastodon_repost_mode == 1: share_review(review) else: boost_toot_later(request.user, post.url) diff --git a/journal/models/mark.py b/journal/models/mark.py index ab001a36..0dc37f17 100644 --- a/journal/models/mark.py +++ b/journal/models/mark.py @@ -233,15 +233,16 @@ class Mark: self.rating_grade = rating_grade # publish a new or updated ActivityPub post post_as_new = shelf_type != last_shelf_type or visibility != last_visibility + classic_repost = self.owner.user.preference.mastodon_repost_mode == 1 append = ( f" \n@{self.owner.user.mastodon_acct}" - if visibility > 0 and share_to_mastodon + if visibility > 0 and share_to_mastodon and not classic_repost else "" ) post = Takahe.post_mark(self, post_as_new, append) # async boost to mastodon if post and share_to_mastodon: - if settings.FORCE_CLASSIC_REPOST: + if classic_repost: share_mark(self) else: boost_toot_later(self.owner.user, post.url) diff --git a/journal/templates/comment.html b/journal/templates/comment.html index 5adbd1d8..9840bfc0 100644 --- a/journal/templates/comment.html +++ b/journal/templates/comment.html @@ -31,13 +31,15 @@
+ {% if request.user.mastodon_acct %} + + 转发到主ID时间轴 + + {% endif %}
diff --git a/journal/templates/mark.html b/journal/templates/mark.html index ca635b78..4c3c163e 100644 --- a/journal/templates/mark.html +++ b/journal/templates/mark.html @@ -107,15 +107,17 @@
- + {% if request.user.mastodon_acct %} + + {% endif %}
diff --git a/journal/templates/replies.html b/journal/templates/replies.html index d413ea22..d5099651 100644 --- a/journal/templates/replies.html +++ b/journal/templates/replies.html @@ -50,8 +50,8 @@ diff --git a/journal/views/collection.py b/journal/views/collection.py index 649e6396..df63d2cf 100644 --- a/journal/views/collection.py +++ b/journal/views/collection.py @@ -129,17 +129,26 @@ def collection_share(request: AuthedHttpRequest, collection_uuid): if request.method == "GET": return render(request, "collection_share.html", {"collection": collection}) elif request.method == "POST": - if settings.FORCE_CLASSIC_REPOST: - visibility = int(request.POST.get("visibility", default=0)) - comment = request.POST.get("comment") - if share_collection(collection, comment, request.user, visibility): - return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) - else: - return render_relogin(request) + comment = request.POST.get("comment") + # boost if possible, otherwise quote + if ( + not comment + and request.user.preference.mastodon_repost_mode == 0 + and collection.latest_post + ): + boost_toot_later(request.user, collection.latest_post.url) else: - if collection.latest_post: - boost_toot_later(request.user, collection.latest_post) - return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + visibility = int(request.POST.get("visibility", default=0)) + link = ( + collection.latest_post.url + if collection.latest_post + else collection.absolute_url + ) + if not share_collection( + collection, comment, request.user, visibility, link + ): + return render_relogin(request) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) else: raise BadRequest() diff --git a/journal/views/mark.py b/journal/views/mark.py index 9f38428a..9988bff8 100644 --- a/journal/views/mark.py +++ b/journal/views/mark.py @@ -13,7 +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 boost_toot_later +from mastodon.api import boost_toot_later, share_comment from takahe.utils import Takahe from ..models import ( @@ -236,7 +236,10 @@ def comment(request: AuthedHttpRequest, item_uuid): post = Takahe.post_comment(comment, False) share_to_mastodon = bool(request.POST.get("share_to_mastodon", default=False)) if post and share_to_mastodon: - boost_toot_later(request.user, post.url) + if request.user.preference.mastodon_repost_mode == 1: + share_comment(comment) + else: + boost_toot_later(request.user, post.url) return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) raise BadRequest() diff --git a/journal/views/review.py b/journal/views/review.py index b2316b88..83dbaa24 100644 --- a/journal/views/review.py +++ b/journal/views/review.py @@ -85,7 +85,7 @@ def review_edit(request: AuthedHttpRequest, item_uuid, review_uuid=None): if not review: raise BadRequest() if form.cleaned_data["share_to_mastodon"] and post: - if settings.FORCE_CLASSIC_REPOST: + if request.user.preference.mastodon_repost_mode == 1: share_review(review) else: boost_toot_later(request.user, post.url) diff --git a/mastodon/api.py b/mastodon/api.py index c54dba7f..f3ed4249 100644 --- a/mastodon/api.py +++ b/mastodon/api.py @@ -91,7 +91,9 @@ def boost_toot(site, token, toot_url): j = response.json() if "statuses" in j and len(j["statuses"]) > 0: s = j["statuses"][0] - if s["uri"] != toot_url and s["url"] != toot_url: + url_id = toot_url.split("/posts/")[-1] + url_id2 = s["uri"].split("/posts/")[-1] + if s["uri"] != toot_url and s["url"] != toot_url and url_id != url_id2: logger.warning( f"Error status url mismatch {s['uri']} or {s['uri']} != {toot_url}" ) @@ -437,29 +439,57 @@ def get_spoiler_text(text, item): return None, text -def get_visibility(visibility, user): +def get_toot_visibility(visibility, user): if visibility == 2: return TootVisibilityEnum.DIRECT elif visibility == 1: return TootVisibilityEnum.PRIVATE - elif user.preference.mastodon_publish_public: + elif user.preference.post_public_mode == 0: return TootVisibilityEnum.PUBLIC else: return TootVisibilityEnum.UNLISTED +def share_comment(comment): + from catalog.common import ItemCategory + + user = comment.owner.user + visibility = get_toot_visibility(comment.visibility, user) + tags = ( + "\n" + + user.preference.mastodon_append_tag.replace( + "[category]", str(ItemCategory(comment.item.category).label) + ) + if user.preference.mastodon_append_tag + else "" + ) + content = f"评论《{comment.item.display_title}》\n{comment.text}\n{comment.item.absolute_url}{tags}" + update_id = None + if comment.metadata.get( + "shared_link" + ): # "https://mastodon.social/@username/1234567890" + r = re.match( + r".+/(\w+)$", comment.metadata.get("shared_link") + ) # might be re.match(r'.+/([^/]+)$', u) if Pleroma supports edit + update_id = r[1] if r else None + response = post_toot( + user.mastodon_site, content, visibility, user.mastodon_token, False, update_id + ) + if response is not None and response.status_code in [200, 201]: + j = response.json() + if "url" in j: + comment.metadata["shared_link"] = j["url"] + comment.save() + return True + else: + return False + + def share_mark(mark): from catalog.common import ItemCategory user = mark.owner.user - if mark.visibility == 2: - visibility = TootVisibilityEnum.DIRECT - elif mark.visibility == 1: - visibility = TootVisibilityEnum.PRIVATE - elif user.preference.mastodon_publish_public: - visibility = TootVisibilityEnum.PUBLIC - else: - visibility = TootVisibilityEnum.UNLISTED + visibility = get_toot_visibility(mark.visibility, user) tags = ( "\n" + user.preference.mastodon_append_tag.replace( @@ -473,7 +503,7 @@ def share_mark(mark): MastodonApplication.objects.get(domain_name=user.mastodon_site).star_mode, ) content = f"{mark.action_label}《{mark.item.display_title}》{stars}\n{mark.item.absolute_url}\n{mark.comment_text or ''}{tags}" - update_id = get_status_id_by_url(mark.shared_link) + update_id = None # get_status_id_by_url(mark.shared_link) spoiler_text, content = get_spoiler_text(content, mark.item) response = post_toot( user.mastodon_site, @@ -485,11 +515,11 @@ def share_mark(mark): spoiler_text, ) if response is not None and response.status_code in [200, 201]: - j = response.json() - if "url" in j: - mark.shared_link = j["url"] - if mark.shared_link: - mark.save(update_fields=["shared_link"]) + # j = response.json() + # if "url" in j: + # mark.shared_link = j["url"] + # if mark.shared_link: + # mark.save(update_fields=["shared_link"]) return True, 200 else: logger.warning(response) @@ -499,15 +529,8 @@ def share_mark(mark): def share_review(review): from catalog.common import ItemCategory - user = review.owner - if review.visibility == 2: - visibility = TootVisibilityEnum.DIRECT - elif review.visibility == 1: - visibility = TootVisibilityEnum.PRIVATE - elif user.preference.mastodon_publish_public: - visibility = TootVisibilityEnum.PUBLIC - else: - visibility = TootVisibilityEnum.UNLISTED + user = review.owner.user + visibility = get_toot_visibility(review.visibility, user) tags = ( "\n" + user.preference.mastodon_append_tag.replace( @@ -538,15 +561,8 @@ def share_review(review): return False -def share_collection(collection, comment, user, visibility_no): - if visibility_no == 2: - visibility = TootVisibilityEnum.DIRECT - elif visibility_no == 1: - visibility = TootVisibilityEnum.PRIVATE - elif user.preference.mastodon_publish_public: - visibility = TootVisibilityEnum.PUBLIC - else: - visibility = TootVisibilityEnum.UNLISTED +def share_collection(collection, comment, user, visibility_no, link): + visibility = get_toot_visibility(visibility_no, user) tags = ( "\n" + user.preference.mastodon_append_tag.replace("[category]", "收藏单") if user.preference.mastodon_append_tag @@ -561,7 +577,7 @@ def share_collection(collection, comment, user, visibility_no): else " " + collection.owner.username + " " ) ) - content = f"分享{user_str}的收藏单《{collection.title}》\n{collection.absolute_url}\n{comment}{tags}" + content = f"分享{user_str}的收藏单《{collection.title}》\n{link}\n{comment}{tags}" response = post_toot(user.mastodon_site, content, visibility, user.mastodon_token) if response is not None and response.status_code in [200, 201]: return True diff --git a/takahe/utils.py b/takahe/utils.py index 972fbf3e..426268d4 100644 --- a/takahe/utils.py +++ b/takahe/utils.py @@ -416,22 +416,26 @@ class Takahe: PostInteraction.objects.filter(post__in=post_pks).update(state="undone") @staticmethod - def visibility_n2t(visibility: int, default_public) -> Visibilities: + def visibility_n2t(visibility: int, post_public_mode: int) -> Visibilities: if visibility == 1: return Takahe.Visibilities.followers elif visibility == 2: return Takahe.Visibilities.mentioned - elif default_public: - return Takahe.Visibilities.public - else: + elif post_public_mode == 4: + return Takahe.Visibilities.local_only + elif post_public_mode == 1: return Takahe.Visibilities.unlisted + else: + return Takahe.Visibilities.public @staticmethod def post_collection(collection: "Collection"): existing_post = collection.latest_post user = collection.owner.user + if not user: + raise ValueError(f"Cannot find user for collection {collection}") visibility = Takahe.visibility_n2t( - collection.visibility, user.preference.mastodon_publish_public + collection.visibility, user.preference.post_public_mode ) if existing_post and visibility != existing_post.visibility: Takahe.delete_posts([existing_post.pk]) @@ -490,9 +494,7 @@ class Takahe: "relatedWith": [comment.ap_object], } } - v = Takahe.visibility_n2t( - comment.visibility, user.preference.mastodon_publish_public - ) + v = Takahe.visibility_n2t(comment.visibility, user.preference.post_public_mode) existing_post = None if share_as_new_post else comment.latest_post post = Takahe.post( comment.owner.pk, @@ -532,9 +534,7 @@ class Takahe: "relatedWith": [review.ap_object], } } - v = Takahe.visibility_n2t( - review.visibility, user.preference.mastodon_publish_public - ) + v = Takahe.visibility_n2t(review.visibility, user.preference.post_public_mode) existing_post = None if share_as_new_post else review.latest_post post = Takahe.post( # TODO post as Article? review.owner.pk, @@ -580,9 +580,7 @@ class Takahe: data["object"]["relatedWith"].append(mark.comment.ap_object) if mark.rating: data["object"]["relatedWith"].append(mark.rating.ap_object) - v = Takahe.visibility_n2t( - mark.visibility, user.preference.mastodon_publish_public - ) + v = Takahe.visibility_n2t(mark.visibility, user.preference.post_public_mode) existing_post = ( None if share_as_new_post diff --git a/users/data.py b/users/data.py index 4c6b4d31..836f4128 100644 --- a/users/data.py +++ b/users/data.py @@ -25,31 +25,37 @@ def preferences(request): preference = request.user.preference if request.method == "POST": preference.default_visibility = int(request.POST.get("default_visibility")) - preference.default_no_share = bool(request.POST.get("default_no_share")) + preference.mastodon_default_repost = ( + int(request.POST.get("mastodon_default_repost", 0)) == 1 + ) preference.no_anonymous_view = bool(request.POST.get("no_anonymous_view")) preference.classic_homepage = int(request.POST.get("classic_homepage")) preference.hidden_categories = request.POST.getlist("hidden_categories") - preference.mastodon_publish_public = bool( - request.POST.get("mastodon_publish_public") - ) + preference.post_public_mode = int(request.POST.get("post_public_mode")) preference.show_last_edit = bool(request.POST.get("show_last_edit")) + preference.mastodon_repost_mode = int(request.POST.get("mastodon_repost_mode")) preference.mastodon_append_tag = request.POST.get( "mastodon_append_tag", "" ).strip() preference.save( update_fields=[ "default_visibility", - "default_no_share", + "post_public_mode", "no_anonymous_view", "classic_homepage", - "mastodon_publish_public", "mastodon_append_tag", + "mastodon_repost_mode", + "mastodon_default_repost", "show_last_edit", "hidden_categories", ] ) clear_preference_cache(request) - return render(request, "users/preferences.html") + return render( + request, + "users/preferences.html", + {"enable_local_only": settings.ENABLE_LOCAL_ONLY}, + ) @login_required diff --git a/users/migrations/0015_remove_preference_mastodon_publish_public_and_more.py b/users/migrations/0015_remove_preference_mastodon_publish_public_and_more.py new file mode 100644 index 00000000..b1a5c8b2 --- /dev/null +++ b/users/migrations/0015_remove_preference_mastodon_publish_public_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.8 on 2023-12-10 18:25 + +from django.db import migrations, models + + +def migrate_public_mode(apps, schema_editor): + User = apps.get_model("users", "User") + User.objects.filter(mastodon_publish_public=True).update(post_public_mode=0) + User.objects.filter(mastodon_publish_public=False).update(post_public_mode=1) + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0014_preference_mastodon_skip_relationship_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="preference", + name="mastodon_repost_mode", + field=models.PositiveSmallIntegerField(default=0), + ), + migrations.AddField( + model_name="preference", + name="post_public_mode", + field=models.PositiveSmallIntegerField(default=0), + ), + # migrations.RunPython(migrate_public_mode), + migrations.RunSQL( + "UPDATE users_preference SET post_public_mode = 1 where mastodon_publish_public = false;" + ), + migrations.AlterField( + model_name="preference", + name="show_last_edit", + field=models.PositiveSmallIntegerField(default=1), + ), + migrations.RemoveField( + model_name="preference", + name="mastodon_publish_public", + ), + ] diff --git a/users/migrations/0016_rename_preference_default_no_share.py b/users/migrations/0016_rename_preference_default_no_share.py new file mode 100644 index 00000000..112504ad --- /dev/null +++ b/users/migrations/0016_rename_preference_default_no_share.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.8 on 2023-12-10 19:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0015_remove_preference_mastodon_publish_public_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="preference", + name="mastodon_default_repost", + field=models.BooleanField(default=True), + ), + migrations.RunSQL( + "UPDATE users_preference SET mastodon_default_repost = false where default_no_share = true;" + ), + migrations.RemoveField( + model_name="preference", + name="default_no_share", + ), + ] diff --git a/users/models/preference.py b/users/models/preference.py index 3b9e2c86..50b8c4ec 100644 --- a/users/models/preference.py +++ b/users/models/preference.py @@ -40,16 +40,23 @@ class Preference(models.Model): import_status = models.JSONField( blank=True, null=True, encoder=DjangoJSONEncoder, default=dict ) - default_no_share = models.BooleanField(default=False) - default_visibility = models.PositiveSmallIntegerField(default=0) + # 0: public, 1: follower only, 2: private + default_visibility = models.PositiveSmallIntegerField(null=False, default=0) + # 0: public, 1: unlisted, 4: local + post_public_mode = models.PositiveSmallIntegerField(null=False, default=0) + # 0: discover, 1: timeline, 2: my profile classic_homepage = models.PositiveSmallIntegerField(null=False, default=0) - mastodon_publish_public = models.BooleanField(null=False, default=False) - mastodon_append_tag = models.CharField(max_length=2048, default="") - show_last_edit = models.PositiveSmallIntegerField(default=0) + show_last_edit = models.PositiveSmallIntegerField(null=False, default=1) no_anonymous_view = models.PositiveSmallIntegerField(default=0) hidden_categories = models.JSONField(default=list) + mastodon_append_tag = models.CharField(max_length=2048, default="") + mastodon_default_repost = models.BooleanField(null=False, default=True) + mastodon_repost_mode = models.PositiveSmallIntegerField(null=False, default=0) mastodon_skip_userinfo = models.BooleanField(null=False, default=False) mastodon_skip_relationship = models.BooleanField(null=False, default=False) + # Removed: + # mastodon_publish_public = models.BooleanField(null=False, default=False) + # default_no_share = models.BooleanField(null=False, default=False) def __str__(self): return str(self.user) diff --git a/users/templates/users/preferences.html b/users/templates/users/preferences.html index ca8c30a0..e5529231 100644 --- a/users/templates/users/preferences.html +++ b/users/templates/users/preferences.html @@ -26,7 +26,7 @@
{% csrf_token %}
- {% trans '登录后显示:' %} + {% trans '登录后显示:' %} 个人主页
- {% trans '新标记默认可见性:' %} + {% trans '发表时的默认可见性:' %} - +
-
+ {% if request.user.mastodon_acct %} +
+ 转发到 @{{ request.user.mastodon_acct }} 时间轴时: + + + + +
+
+ +
+ {% endif %}
- -
-
- -
-
- -
-
- +
+
+ +
+
+ +