support classic repost as a user option

This commit is contained in:
Her Email 2023-12-10 19:14:32 -05:00 committed by Henri Dickson
parent 0e08f70cd5
commit 18eb234774
16 changed files with 283 additions and 129 deletions

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -31,13 +31,15 @@
<div class="mark-modal__share-checkbox float-right"
style="width:max-content">
<label for="id_share_to_mastodon">
<input type="checkbox"
name="share_to_mastodon"
id="id_share_to_mastodon"
value="1"
{% if not request.user.preference.default_no_share %}checked{% endif %}>
分享到联邦宇宙
</label>
{% if request.user.mastodon_acct %}
<input type="checkbox"
name="share_to_mastodon"
id="id_share_to_mastodon"
value="1"
{% if request.user.preference.mastodon_default_repost %}checked{% endif %}>
转发到<em data-tooltip="@{{ request.user.mastodon_acct }}">主ID</em>时间轴
</label>
{% endif %}
</div>
<div class="mark-modal__option" style="width:max-content;">
<div class="mark-modal__visibility-radio">

View file

@ -107,15 +107,17 @@
</div>
<div>
<fieldset>
<label for="id_share_to_mastodon">
<input role="switch"
type="checkbox"
name="share_to_mastodon"
id="id_share_to_mastodon"
value="1"
{% if not request.user.preference.default_no_share %}checked{% endif %}>
分享到联邦宇宙
</label>
{% if request.user.mastodon_acct %}
<label for="id_share_to_mastodon">
<input role="switch"
type="checkbox"
name="share_to_mastodon"
id="id_share_to_mastodon"
value="1"
{% if request.user.preference.mastodon_default_repost %}checked{% endif %}>
转发到<em data-tooltip="@{{ request.user.mastodon_acct }}">主ID</em>时间轴
</label>
{% endif %}
</fieldset>
</div>
</div>

View file

@ -50,8 +50,8 @@
<label>
<input type="radio"
name="visibility"
value="{% if request.user.preference.mastodon_publish_public %}0{% else %}1{% endif %}"
{% if post.visibility <= 1 %}checked{% endif %} />
value="{{ request.user.preference.post_public_mode }}"
{% if post.visibility <= 1 or post.visibility == 4 %}checked{% endif %} />
<i class="fa-solid fa-globe"></i>
</label>
</li>

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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",
),
]

View file

@ -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",
),
]

View file

@ -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)

View file

@ -26,7 +26,7 @@
<form action="{% url 'users:preferences' %}" method="post">
{% csrf_token %}
<fieldset>
<legend>{% trans '登录后显示:' %}</legend>
{% trans '登录后显示:' %}
<input type="radio"
name="classic_homepage"
value="0"
@ -47,7 +47,7 @@
<label for="classic_homepage1">个人主页</label>
</fieldset>
<fieldset>
<legend>{% trans '新标记默认可见性:' %}</legend>
{% trans '发表时的默认可见性:' %}
<input type="radio"
name="default_visibility"
value="0"
@ -68,42 +68,67 @@
required=""
id="id_visibility_2"
{% if request.user.preference.default_visibility == 2 %}checked{% endif %}>
<label for="id_visibility_2">自己</label>
<label for="id_visibility_2">本人和被提及的用户</label>
</fieldset>
<fieldset>
<label>
<input type="checkbox"
name="no_anonymous_view"
{% if request.user.preference.no_anonymous_view %}checked{% endif %}>
{% trans '仅允许已登录用户查看你的个人主页' %}
可见性选择「公开」时以如下方式发布到联邦网络:
<input type="radio"
id="post_public_mode_0"
name="post_public_mode"
value="0"
{% if request.user.preference.post_public_mode == 0 %}checked{% endif %} />
<label for="post_public_mode_0">Public</label>
<input type="radio"
id="post_public_mode_1"
name="post_public_mode"
value="1"
{% if request.user.preference.post_public_mode == 1 %}checked{% endif %} />
<label for="post_public_mode_1">
Unlisted
<em data-tooltip="unlisted帖子不会出现在本站和外站公共时间轴"><i class="fa fa-question-circle"></i></em>
</label>
{% if enable_local_only %}
<input type="radio"
id="post_public_mode_4"
name="post_public_mode"
value="4"
{% if request.user.preference.post_public_mode == 4 %}checked{% endif %} />
<label for="post_public_mode_4">仅本站</label>
{% endif %}
</fieldset>
{% if request.user.mastodon_acct %}
<fieldset>
转发到 @{{ request.user.mastodon_acct }} 时间轴时:
<input type="radio"
name="mastodon_repost_mode"
value="0"
required=""
id="mastodon_repost_mode_0"
{% if request.user.preference.mastodon_repost_mode == 0 %}checked{% endif %}>
<label for="mastodon_repost_mode_0">使用转播(推荐)</label>
<input type="radio"
name="mastodon_repost_mode"
value="1"
required=""
id="mastodon_repost_mode_1"
{% if request.user.preference.mastodon_repost_mode == 1 %}checked{% endif %}>
<label for="mastodon_repost_mode_1">
另发新帖文
<em data-tooltip="其他人对新帖的回应、点赞不会被记录,修改标记可能产生重复帖文"><i class="fa fa-question-circle"></i></em>
</label>
</fieldset>
<fieldset>
<label>
发表时默认选中转发到 @{{ request.user.mastodon_acct }} 时间轴
<input type="checkbox"
name="mastodon_default_repost"
value="1"
{% if request.user.preference.mastodon_default_repost %}checked{% endif %}>
</label>
</fieldset>
{% endif %}
<fieldset>
<label>
<input type="checkbox"
name="show_last_edit"
{% if request.user.preference.show_last_edit %}checked{% endif %}>
{% trans '显示你是某条目的最近编辑者' %}
</label>
</fieldset>
<fieldset>
<label>
<input type="checkbox"
name="mastodon_publish_public"
{% if request.user.preference.mastodon_publish_public %}checked{% endif %}>
标记时以公开方式分享的帖文发布到<em data-tooltip="选中时为public未选中时为unlisted">公共时间轴</em>
</label>
</fieldset>
<fieldset>
<label>
<input type="checkbox"
name="default_no_share"
{% if request.user.preference.default_no_share %}checked{% endif %}>
标记时默认不分享到联邦宇宙
</label>
</fieldset>
<fieldset>
<label for="mastodon_append_tag">{% trans '在联邦宇宙分享帖文时在结尾附加标签:' %}</label>
<label for="mastodon_append_tag">{% trans '发表标记时在结尾附加标签:' %}</label>
<input name="mastodon_append_tag"
id="mastodon_append_tag"
placeholder="例如 #我的书影音"
@ -121,6 +146,23 @@
{% endfor %}
</select>
</fieldset>
<fieldset>
<label>
<input type="checkbox"
name="no_anonymous_view"
{% if request.user.preference.no_anonymous_view %}checked{% endif %}>
{% trans '未登录访客不能查看你的个人信息' %}
<em data-tooltip="此选项仅针对网页访客,如果不希望被联邦网络用户看到请在发表时选择仅关注者或本人"><i class="fa fa-question-circle"></i></em>
</label>
</fieldset>
<fieldset>
<label>
<input type="checkbox"
name="show_last_edit"
{% if request.user.preference.show_last_edit %}checked{% endif %}>
{% trans '显示你是某条目的最近编辑者' %}
</label>
</fieldset>
<input type="submit" value="{% trans '保存' %}">
</form>
</details>