diff --git a/app/activitypub/util.py b/app/activitypub/util.py index 4337892f..78fa99ee 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -28,7 +28,8 @@ import pytesseract from app.utils import get_request, allowlist_html, get_setting, ap_datetime, markdown_to_html, \ is_image_url, domain_from_url, gibberish, ensure_directory_exists, markdown_to_text, head_request, post_ranking, \ shorten_string, reply_already_exists, reply_is_just_link_to_gif_reaction, confidence, remove_tracking_from_link, \ - blocked_phrases, microblog_content_to_title, generate_image_from_video_url, is_video_url, reply_is_stupid + blocked_phrases, microblog_content_to_title, generate_image_from_video_url, is_video_url, reply_is_stupid, \ + notification_subscribers, communities_banned_from def public_key(): @@ -1524,7 +1525,8 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json post.cross_posts.append(op.id) db.session.commit() - notify_about_post(post) + if post.community_id not in communities_banned_from(user.id): + notify_about_post(post) if user.reputation > 100: post.up_votes += 1 @@ -1537,20 +1539,12 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json def notify_about_post(post: Post): # todo: eventually this function could trigger a lot of DB activity. This function will need to be a celery task. - # Send notifications based on subscriptions to the author + # Send notifications based on subscriptions notifications_sent_to = set() - for notify_id in post.author.notification_subscribers(): - if notify_id != post.user_id: - new_notification = Notification(title=shorten_string(post.title, 50), url=f"/post/{post.id}", - user_id=notify_id, author_id=post.user_id) - db.session.add(new_notification) - user = User.query.get(notify_id) - user.unread_notifications += 1 - db.session.commit() - notifications_sent_to.add(notify_id) - - # Send notifications based on subscriptions to the community - for notify_id in post.community.notification_subscribers(): + send_notifs_to = set(notification_subscribers(post.author_id, NOTIF_USER) + + notification_subscribers(post.community_id, NOTIF_COMMUNITY) + + notification_subscribers(post.community.topic_id, NOTIF_TOPIC)) + for notify_id in send_notifs_to: if notify_id != post.user_id and notify_id not in notifications_sent_to: new_notification = Notification(title=shorten_string(post.title, 50), url=f"/post/{post.id}", user_id=notify_id, author_id=post.user_id) @@ -1558,6 +1552,7 @@ def notify_about_post(post: Post): user = User.query.get(notify_id) user.unread_notifications += 1 db.session.commit() + notifications_sent_to.add(notify_id) def update_post_reply_from_activity(reply: PostReply, request_json: dict): diff --git a/app/community/routes.py b/app/community/routes.py index 6c7051a3..4b825920 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -497,7 +497,8 @@ def add_discussion_post(actor): db.session.commit() upvote_own_post(post) - notify_about_post(post) + if not post.community.user_is_banned(current_user): + notify_about_post(post) if not community.local_only: federate_post(community, post) diff --git a/app/models.py b/app/models.py index f3f0db7f..5736bf17 100644 --- a/app/models.py +++ b/app/models.py @@ -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 + SUBSCRIPTION_BANNED, SUBSCRIPTION_PENDING, NOTIF_USER, NOTIF_COMMUNITY, NOTIF_TOPIC # datetime.utcnow() is depreciated in Python 3.12 so it will need to be swapped out eventually @@ -327,6 +327,14 @@ class Topic(db.Model): return_value = list(reversed(return_value)) return '/'.join(return_value) + def notify_new_posts(self, user_id: int) -> bool: + existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == self.id, + NotificationSubscription.user_id == user_id, + NotificationSubscription.type == NOTIF_TOPIC).first() + return existing_notification is not None + + + class Community(db.Model): query_class = FullTextSearchQuery @@ -476,14 +484,9 @@ class Community(db.Model): return False def user_is_banned(self, user): - membership = CommunityMember.query.filter(CommunityMember.community_id == self.id, CommunityMember.user_id == user.id).first() - if membership and membership.is_banned: - return True - banned = CommunityBan.query.filter(CommunityBan.community_id == self.id, CommunityBan.user_id == user.id).first() - if banned: - return True - return False - + # use communities_banned_from() instead of this method, where possible. Redis caches the result of communities_banned_from() + community_bans = CommunityBan.query.filter(CommunityBan.user_id == user.id).all() + return self.id in [cb.community_id for cb in community_bans] def profile_id(self): retval = self.ap_profile_id if self.ap_profile_id else f"https://{current_app.config['SERVER_NAME']}/c/{self.name}" diff --git a/app/templates/topic/_notification_toggle.html b/app/templates/topic/_notification_toggle.html new file mode 100644 index 00000000..c39105cb --- /dev/null +++ b/app/templates/topic/_notification_toggle.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/templates/topic/show_topic.html b/app/templates/topic/show_topic.html index 9531c9a1..de8761fd 100644 --- a/app/templates/topic/show_topic.html +++ b/app/templates/topic/show_topic.html @@ -18,6 +18,9 @@