mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
parent
cede0163fd
commit
01d4b2678c
13 changed files with 177 additions and 52 deletions
|
@ -1364,17 +1364,10 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep
|
|||
db.session.commit()
|
||||
|
||||
# send notification to the post/comment being replied to
|
||||
if notification_target.notify_author and post_reply.user_id != notification_target.user_id and notification_target.author.ap_id is None:
|
||||
anchor = f"comment_{post_reply.id}"
|
||||
notification = Notification(title=shorten_string(_('Reply from %(name)s on %(post_title)s',
|
||||
name=post_reply.author.display_name(),
|
||||
post_title=post.title), 50),
|
||||
user_id=notification_target.user_id,
|
||||
author_id=post_reply.user_id,
|
||||
url=url_for('activitypub.post_ap', post_id=post.id, _anchor=anchor))
|
||||
db.session.add(notification)
|
||||
notification_target.author.unread_notifications += 1
|
||||
db.session.commit()
|
||||
if parent_comment_id:
|
||||
notify_about_post_reply(parent_comment, post_reply)
|
||||
else:
|
||||
notify_about_post_reply(None, post_reply)
|
||||
|
||||
if user.reputation > 100:
|
||||
post_reply.up_votes += 1
|
||||
|
@ -1555,6 +1548,35 @@ def notify_about_post(post: Post):
|
|||
notifications_sent_to.add(notify_id)
|
||||
|
||||
|
||||
def notify_about_post_reply(parent_reply: Union[PostReply, None], new_reply: PostReply):
|
||||
|
||||
if parent_reply is None: # This happens when a new_reply is a top-level comment, not a comment on a comment
|
||||
send_notifs_to = notification_subscribers(new_reply.post.id, NOTIF_POST)
|
||||
for notify_id in send_notifs_to:
|
||||
if new_reply.user_id != notify_id:
|
||||
new_notification = Notification(title=shorten_string(_('Reply to %(post_title)s',
|
||||
post_title=new_reply.post.title), 50),
|
||||
url=f"/post/{new_reply.post.id}#comment_{new_reply.id}",
|
||||
user_id=notify_id, author_id=new_reply.user_id)
|
||||
db.session.add(new_notification)
|
||||
user = User.query.get(notify_id)
|
||||
user.unread_notifications += 1
|
||||
db.session.commit()
|
||||
else:
|
||||
# Send notifications based on subscriptions
|
||||
send_notifs_to = set(notification_subscribers(parent_reply.id, NOTIF_REPLY))
|
||||
for notify_id in send_notifs_to:
|
||||
if new_reply.user_id != notify_id:
|
||||
new_notification = Notification(title=shorten_string(_('Reply to comment on %(post_title)s',
|
||||
post_title=parent_reply.post.title), 50),
|
||||
url=f"/post/{parent_reply.post.id}#comment_{new_reply.id}",
|
||||
user_id=notify_id, author_id=new_reply.user_id)
|
||||
db.session.add(new_notification)
|
||||
user = User.query.get(notify_id)
|
||||
user.unread_notifications += 1
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def update_post_reply_from_activity(reply: PostReply, request_json: dict):
|
||||
if 'source' in request_json['object'] and \
|
||||
isinstance(request_json['object']['source'], dict) and \
|
||||
|
|
30
app/cli.py
30
app/cli.py
|
@ -16,11 +16,12 @@ import os
|
|||
|
||||
from app.activitypub.signature import RsaKeys
|
||||
from app.auth.util import random_token
|
||||
from app.constants import NOTIF_COMMUNITY
|
||||
from app.constants import NOTIF_COMMUNITY, NOTIF_POST, NOTIF_REPLY
|
||||
from app.email import send_verification_email, send_email
|
||||
from app.models import Settings, BannedInstances, Interest, Role, User, RolePermission, Domain, ActivityPubLog, \
|
||||
utcnow, Site, Instance, File, Notification, Post, CommunityMember, NotificationSubscription
|
||||
from app.utils import file_get_contents, retrieve_block_list, blocked_domains, retrieve_peertube_block_list
|
||||
utcnow, Site, Instance, File, Notification, Post, CommunityMember, NotificationSubscription, PostReply
|
||||
from app.utils import file_get_contents, retrieve_block_list, blocked_domains, retrieve_peertube_block_list, \
|
||||
shorten_string
|
||||
|
||||
|
||||
def register(app):
|
||||
|
@ -318,6 +319,29 @@ def register(app):
|
|||
db.session.commit()
|
||||
print('Done')
|
||||
|
||||
@app.cli.command("migrate_post_notifs")
|
||||
def migrate_post_notifs():
|
||||
with app.app_context():
|
||||
posts = Post.query.filter(Post.notify_author == True).all()
|
||||
for post in posts:
|
||||
new_notification = NotificationSubscription(name=shorten_string(_('Replies to my post %(post_title)s',
|
||||
post_title=post.title)),
|
||||
user_id=post.user_id, entity_id=post.id,
|
||||
type=NOTIF_POST)
|
||||
db.session.add(new_notification)
|
||||
db.session.commit()
|
||||
|
||||
post_replies = PostReply.query.filter(PostReply.notify_author == True).all()
|
||||
for reply in post_replies:
|
||||
new_notification = NotificationSubscription(name=shorten_string(_('Replies to my comment on %(post_title)s',
|
||||
post_title=reply.post.title)),
|
||||
user_id=post.user_id, entity_id=reply.id,
|
||||
type=NOTIF_REPLY)
|
||||
db.session.add(new_notification)
|
||||
db.session.commit()
|
||||
|
||||
print('Done')
|
||||
|
||||
|
||||
def parse_communities(interests_source, segment):
|
||||
lines = interests_source.split("\n")
|
||||
|
|
|
@ -497,8 +497,8 @@ def add_discussion_post(actor):
|
|||
db.session.commit()
|
||||
|
||||
upvote_own_post(post)
|
||||
if not post.community.user_is_banned(current_user):
|
||||
notify_about_post(post)
|
||||
|
||||
notify_about_post(post)
|
||||
|
||||
if not community.local_only:
|
||||
federate_post(community, post)
|
||||
|
@ -1178,7 +1178,8 @@ def community_notification(community_id: int):
|
|||
db.session.commit()
|
||||
else: # no subscription yet, so make one
|
||||
if community.id not in communities_banned_from(current_user.id):
|
||||
new_notification = NotificationSubscription(name=community.title, user_id=current_user.id, entity_id=community.id,
|
||||
new_notification = NotificationSubscription(name=shorten_string(_('New posts in %(community_name)s', community_name=community.title)),
|
||||
user_id=current_user.id, entity_id=community.id,
|
||||
type=NOTIF_COMMUNITY)
|
||||
db.session.add(new_notification)
|
||||
db.session.commit()
|
||||
|
|
|
@ -11,9 +11,9 @@ from pillow_heif import register_heif_opener
|
|||
from app import db, cache, celery
|
||||
from app.activitypub.signature import post_request
|
||||
from app.activitypub.util import find_actor_or_create, actor_json_to_model, post_json_to_model, default_context, ensure_domains_match
|
||||
from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_VIDEO
|
||||
from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_VIDEO, NOTIF_POST
|
||||
from app.models import Community, File, BannedInstances, PostReply, PostVote, Post, utcnow, CommunityMember, Site, \
|
||||
Instance, Notification, User, ActivityPubLog
|
||||
Instance, Notification, User, ActivityPubLog, NotificationSubscription
|
||||
from app.utils import get_request, gibberish, markdown_to_html, domain_from_url, allowlist_html, \
|
||||
is_image_url, ensure_directory_exists, inbox_domain, post_ranking, shorten_string, parse_page, \
|
||||
remove_tracking_from_link, ap_datetime, instance_banned, blocked_phrases
|
||||
|
@ -387,8 +387,24 @@ def save_post(form, post: Post, type: str):
|
|||
return
|
||||
|
||||
db.session.add(post)
|
||||
db.session.commit()
|
||||
|
||||
# Notify author about replies
|
||||
# Remove any subscription that currently exists
|
||||
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == post.id,
|
||||
NotificationSubscription.user_id == current_user.id,
|
||||
NotificationSubscription.type == NOTIF_POST).first()
|
||||
if existing_notification:
|
||||
db.session.delete(existing_notification)
|
||||
|
||||
# Add subscription if necessary
|
||||
if form.notify_author.data:
|
||||
new_notification = NotificationSubscription(name=post.title, user_id=current_user.id, entity_id=post.id,
|
||||
type=NOTIF_POST)
|
||||
db.session.add(new_notification)
|
||||
|
||||
g.site.last_active = utcnow()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def delete_post_from_community(post_id):
|
||||
|
|
|
@ -19,7 +19,7 @@ import jwt
|
|||
import os
|
||||
|
||||
from app.constants import SUBSCRIPTION_NONMEMBER, SUBSCRIPTION_MEMBER, SUBSCRIPTION_MODERATOR, SUBSCRIPTION_OWNER, \
|
||||
SUBSCRIPTION_BANNED, SUBSCRIPTION_PENDING, NOTIF_USER, NOTIF_COMMUNITY, NOTIF_TOPIC
|
||||
SUBSCRIPTION_BANNED, SUBSCRIPTION_PENDING, NOTIF_USER, NOTIF_COMMUNITY, NOTIF_TOPIC, NOTIF_POST, NOTIF_REPLY
|
||||
|
||||
|
||||
# datetime.utcnow() is depreciated in Python 3.12 so it will need to be swapped out eventually
|
||||
|
@ -990,6 +990,12 @@ class Post(db.Model):
|
|||
return name
|
||||
return False
|
||||
|
||||
def notify_new_replies(self, user_id: int) -> bool:
|
||||
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == self.id,
|
||||
NotificationSubscription.user_id == user_id,
|
||||
NotificationSubscription.type == NOTIF_POST).first()
|
||||
return existing_notification is not None
|
||||
|
||||
|
||||
class PostReply(db.Model):
|
||||
query_class = FullTextSearchQuery
|
||||
|
@ -1086,6 +1092,12 @@ class PostReply(db.Model):
|
|||
return name
|
||||
return False
|
||||
|
||||
def notify_new_replies(self, user_id: int) -> bool:
|
||||
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == self.id,
|
||||
NotificationSubscription.user_id == user_id,
|
||||
NotificationSubscription.type == NOTIF_REPLY).first()
|
||||
return existing_notification is not None
|
||||
|
||||
|
||||
class Domain(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
|
|
@ -9,17 +9,18 @@ from sqlalchemy import or_, desc
|
|||
|
||||
from app import db, constants, cache
|
||||
from app.activitypub.signature import HttpSignature, post_request
|
||||
from app.activitypub.util import default_context
|
||||
from app.activitypub.util import default_context, notify_about_post_reply
|
||||
from app.community.util import save_post, send_to_remote_instance
|
||||
from app.inoculation import inoculation
|
||||
from app.post.forms import NewReplyForm, ReportPostForm, MeaCulpaForm
|
||||
from app.community.forms import CreateLinkForm, CreateImageForm, CreateDiscussionForm, CreateVideoForm
|
||||
from app.post.util import post_replies, get_comment_branch, post_reply_count
|
||||
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_LINK, POST_TYPE_IMAGE, \
|
||||
POST_TYPE_ARTICLE, POST_TYPE_VIDEO
|
||||
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_LINK, \
|
||||
POST_TYPE_IMAGE, \
|
||||
POST_TYPE_ARTICLE, POST_TYPE_VIDEO, NOTIF_REPLY, NOTIF_POST
|
||||
from app.models import Post, PostReply, \
|
||||
PostReplyVote, PostVote, Notification, utcnow, UserBlock, DomainBlock, InstanceBlock, Report, Site, Community, \
|
||||
Topic, User, Instance
|
||||
Topic, User, Instance, NotificationSubscription
|
||||
from app.post import bp
|
||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
||||
shorten_string, markdown_to_text, gibberish, ap_datetime, return_304, \
|
||||
|
@ -98,14 +99,7 @@ def show_post(post_id: int):
|
|||
body_html=markdown_to_html(form.body.data), body_html_safe=True,
|
||||
from_bot=current_user.bot, nsfw=post.nsfw, nsfl=post.nsfl,
|
||||
notify_author=form.notify_author.data, instance_id=1)
|
||||
if post.notify_author and current_user.id != post.user_id:
|
||||
notification = Notification(title=shorten_string(_('Reply from %(name)s on %(post_title)s',
|
||||
name=current_user.display_name(),
|
||||
post_title=post.title), 50),
|
||||
user_id=post.user_id,
|
||||
author_id=current_user.id, url=url_for('activitypub.post_ap', post_id=post.id))
|
||||
db.session.add(notification)
|
||||
post.author.unread_notifications += 1
|
||||
|
||||
post.last_active = community.last_active = utcnow()
|
||||
post.reply_count += 1
|
||||
community.post_reply_count += 1
|
||||
|
@ -113,6 +107,17 @@ def show_post(post_id: int):
|
|||
db.session.add(reply)
|
||||
db.session.commit()
|
||||
|
||||
notify_about_post_reply(None, reply)
|
||||
|
||||
# Subscribe to own comment
|
||||
if form.notify_author.data:
|
||||
new_notification = NotificationSubscription(name=shorten_string(_('Replies to my comment on %(post_title)s',
|
||||
post_title=post.title), 50),
|
||||
user_id=current_user.id, entity_id=reply.id,
|
||||
type=NOTIF_REPLY)
|
||||
db.session.add(new_notification)
|
||||
db.session.commit()
|
||||
|
||||
# upvote own reply
|
||||
reply.score = 1
|
||||
reply.up_votes = 1
|
||||
|
@ -622,16 +627,19 @@ def add_reply(post_id: int, comment_id: int):
|
|||
if blocked_phrase in reply.body:
|
||||
abort(401)
|
||||
db.session.add(reply)
|
||||
if in_reply_to.notify_author and current_user.id != in_reply_to.user_id and in_reply_to.author.ap_id is None: # todo: check if replier is blocked
|
||||
notification = Notification(title=shorten_string(_('Reply from %(name)s on %(post_title)s',
|
||||
name=current_user.display_name(),
|
||||
post_title=post.title), 50),
|
||||
user_id=in_reply_to.user_id,
|
||||
author_id=current_user.id, url=url_for('activitypub.post_ap', post_id=post.id))
|
||||
db.session.add(notification)
|
||||
in_reply_to.author.unread_notifications += 1
|
||||
db.session.commit()
|
||||
|
||||
# Notify subscribers
|
||||
notify_about_post_reply(in_reply_to, reply)
|
||||
|
||||
# Subscribe to own comment
|
||||
if form.notify_author.data:
|
||||
new_notification = NotificationSubscription(name=shorten_string(_('Replies to my comment on %(post_title)s',
|
||||
post_title=post.title), 50),
|
||||
user_id=current_user.id, entity_id=reply.id,
|
||||
type=NOTIF_REPLY)
|
||||
db.session.add(new_notification)
|
||||
|
||||
# upvote own reply
|
||||
reply.score = 1
|
||||
reply.up_votes = 1
|
||||
|
@ -1663,20 +1671,43 @@ def post_reply_delete(post_id: int, comment_id: int):
|
|||
@bp.route('/post/<int:post_id>/notification', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def post_notification(post_id: int):
|
||||
# Toggle whether the current user is subscribed to notifications about top-level replies to this post or not
|
||||
post = Post.query.get_or_404(post_id)
|
||||
if post.user_id == current_user.id:
|
||||
post.notify_author = not post.notify_author
|
||||
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == post.id,
|
||||
NotificationSubscription.user_id == current_user.id,
|
||||
NotificationSubscription.type == NOTIF_POST).first()
|
||||
if existing_notification:
|
||||
db.session.delete(existing_notification)
|
||||
db.session.commit()
|
||||
else: # no subscription yet, so make one
|
||||
new_notification = NotificationSubscription(name=shorten_string(_('Replies to my post %(post_title)s',
|
||||
post_title=post.title)),
|
||||
user_id=current_user.id, entity_id=post.id,
|
||||
type=NOTIF_POST)
|
||||
db.session.add(new_notification)
|
||||
db.session.commit()
|
||||
|
||||
return render_template('post/_post_notification_toggle.html', post=post)
|
||||
|
||||
|
||||
@bp.route('/post_reply/<int:post_reply_id>/notification', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def post_reply_notification(post_reply_id: int):
|
||||
# Toggle whether the current user is subscribed to notifications about replies to this reply or not
|
||||
post_reply = PostReply.query.get_or_404(post_reply_id)
|
||||
if post_reply.user_id == current_user.id:
|
||||
post_reply.notify_author = not post_reply.notify_author
|
||||
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == post_reply.id,
|
||||
NotificationSubscription.user_id == current_user.id,
|
||||
NotificationSubscription.type == NOTIF_REPLY).first()
|
||||
if existing_notification:
|
||||
db.session.delete(existing_notification)
|
||||
db.session.commit()
|
||||
else: # no subscription yet, so make one
|
||||
new_notification = NotificationSubscription(name=shorten_string(_('Replies to my comment on %(post_title)s',
|
||||
post_title=post_reply.post.title)), user_id=current_user.id, entity_id=post_reply.id,
|
||||
type=NOTIF_REPLY)
|
||||
db.session.add(new_notification)
|
||||
db.session.commit()
|
||||
|
||||
return render_template('post/_reply_notification_toggle.html', comment={'comment': post_reply})
|
||||
|
||||
|
||||
|
|
|
@ -699,6 +699,15 @@ div.navbar {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.notif_toggle {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 30px;
|
||||
width: 41px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
width: 96%;
|
||||
}
|
||||
|
|
|
@ -290,6 +290,15 @@ div.navbar {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.notif_toggle {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 30px;
|
||||
width: 41px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
width: 96%;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{% include "post/_post_voting_buttons.html" %}
|
||||
</div>
|
||||
<h1 class="mt-2 post_title">{{ post.title }}
|
||||
{% if current_user.is_authenticated and post.user_id == current_user.id %}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% include 'post/_post_notification_toggle.html' %}
|
||||
{% endif %}
|
||||
{% if post.nsfw %}<span class="warning_badge nsfw" title="{{ _('Not safe for work') }}">nsfw</span>{% endif %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<a href="{{ url_for('post.post_notification', post_id=post.id) }}" rel="nofollow"
|
||||
class="small fe {{ 'fe-bell' if post.notify_author else 'fe-no-bell' }} no-underline"
|
||||
<a href="{{ url_for('post.post_notification', post_id=post.id) }}" rel="nofollow" aria-live="assertive"
|
||||
aria-label="{{ 'Notify about replies to this post.' if post.notify_new_replies(current_user.id) else 'Do not notify about new replies to this post.' }}"
|
||||
class="small fe {{ 'fe-bell' if post.notify_new_replies(current_user.id) else 'fe-no-bell' }} no-underline"
|
||||
hx-post="{{ url_for('post.post_notification', post_id=post.id) }}" hx-trigger="click throttle:1s" hx-swap="outerHTML"
|
||||
title="{{ _('Notify about replies') }}" aria-label="{{ _('Notify about replies') }}"></a>
|
|
@ -1,4 +1,4 @@
|
|||
<a href="{{ url_for('post.post_reply_notification', post_reply_id=comment['comment'].id) }}" rel="nofollow"
|
||||
class="notif_toggle fe {{ 'fe-bell' if comment['comment'].notify_author else 'fe-no-bell' }}"
|
||||
class="notif_toggle fe {{ 'fe-bell' if comment['comment'].notify_new_replies(current_user.id) else 'fe-no-bell' }}"
|
||||
hx-post="{{ url_for('post.post_reply_notification', post_reply_id=comment['comment'].id) }}" hx-trigger="click throttle:1s" hx-swap="outerHTML"
|
||||
title="{{ _('Notify about replies') }}" aria-label="{{ _('Notify about replies') }}"></a>
|
|
@ -128,7 +128,7 @@
|
|||
<a href='#'><span class="fe fe-collapse"></span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if current_user.is_authenticated and current_user.verified and current_user.id == comment['comment'].author.id %}
|
||||
{% if current_user.is_authenticated and current_user.verified %}
|
||||
{% include "post/_reply_notification_toggle.html" %}
|
||||
{% endif %}
|
||||
<a href="{{ url_for('post.post_reply_options', post_id=post.id, comment_id=comment['comment'].id) }}" class="comment_actions_link" rel="nofollow noindex" aria-label="{{ _('Comment options') }}"><span class="fe fe-options" title="Options"> </span></a>
|
||||
|
|
|
@ -710,9 +710,9 @@ def finalize_user_setup(user, application_required=False):
|
|||
send_welcome_email(user, application_required)
|
||||
|
||||
|
||||
def notification_subscribers(entity_id, entity_type) -> List[int]:
|
||||
return list(db.session.execute(text('SELECT user_id FROM "notification_subscription" WHERE entity_id = :community_id AND type = :type '),
|
||||
{'community_id': entity_id, 'type': entity_type}).scalars())
|
||||
def notification_subscribers(entity_id: int, entity_type: int) -> List[int]:
|
||||
return list(db.session.execute(text('SELECT user_id FROM "notification_subscription" WHERE entity_id = :entity_id AND type = :type '),
|
||||
{'entity_id': entity_id, 'type': entity_type}).scalars())
|
||||
|
||||
|
||||
# topics, in a tree
|
||||
|
|
Loading…
Reference in a new issue