mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
topic notifications #20
This commit is contained in:
parent
2008ce89a6
commit
e1204bc267
7 changed files with 62 additions and 28 deletions
|
@ -28,7 +28,8 @@ import pytesseract
|
||||||
from app.utils import get_request, allowlist_html, get_setting, ap_datetime, markdown_to_html, \
|
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, \
|
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, \
|
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():
|
def public_key():
|
||||||
|
@ -1524,7 +1525,8 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
||||||
post.cross_posts.append(op.id)
|
post.cross_posts.append(op.id)
|
||||||
db.session.commit()
|
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:
|
if user.reputation > 100:
|
||||||
post.up_votes += 1
|
post.up_votes += 1
|
||||||
|
@ -1537,20 +1539,12 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
||||||
def notify_about_post(post: Post):
|
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.
|
# 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()
|
notifications_sent_to = set()
|
||||||
for notify_id in post.author.notification_subscribers():
|
send_notifs_to = set(notification_subscribers(post.author_id, NOTIF_USER) +
|
||||||
if notify_id != post.user_id:
|
notification_subscribers(post.community_id, NOTIF_COMMUNITY) +
|
||||||
new_notification = Notification(title=shorten_string(post.title, 50), url=f"/post/{post.id}",
|
notification_subscribers(post.community.topic_id, NOTIF_TOPIC))
|
||||||
user_id=notify_id, author_id=post.user_id)
|
for notify_id in send_notifs_to:
|
||||||
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():
|
|
||||||
if notify_id != post.user_id and notify_id not in notifications_sent_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}",
|
new_notification = Notification(title=shorten_string(post.title, 50), url=f"/post/{post.id}",
|
||||||
user_id=notify_id, author_id=post.user_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 = User.query.get(notify_id)
|
||||||
user.unread_notifications += 1
|
user.unread_notifications += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
notifications_sent_to.add(notify_id)
|
||||||
|
|
||||||
|
|
||||||
def update_post_reply_from_activity(reply: PostReply, request_json: dict):
|
def update_post_reply_from_activity(reply: PostReply, request_json: dict):
|
||||||
|
|
|
@ -497,7 +497,8 @@ def add_discussion_post(actor):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
upvote_own_post(post)
|
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:
|
if not community.local_only:
|
||||||
federate_post(community, post)
|
federate_post(community, post)
|
||||||
|
|
|
@ -19,7 +19,7 @@ import jwt
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from app.constants import SUBSCRIPTION_NONMEMBER, SUBSCRIPTION_MEMBER, SUBSCRIPTION_MODERATOR, SUBSCRIPTION_OWNER, \
|
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
|
# 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_value = list(reversed(return_value))
|
||||||
return '/'.join(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):
|
class Community(db.Model):
|
||||||
query_class = FullTextSearchQuery
|
query_class = FullTextSearchQuery
|
||||||
|
@ -476,14 +484,9 @@ class Community(db.Model):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def user_is_banned(self, user):
|
def user_is_banned(self, user):
|
||||||
membership = CommunityMember.query.filter(CommunityMember.community_id == self.id, CommunityMember.user_id == user.id).first()
|
# use communities_banned_from() instead of this method, where possible. Redis caches the result of communities_banned_from()
|
||||||
if membership and membership.is_banned:
|
community_bans = CommunityBan.query.filter(CommunityBan.user_id == user.id).all()
|
||||||
return True
|
return self.id in [cb.community_id for cb in community_bans]
|
||||||
banned = CommunityBan.query.filter(CommunityBan.community_id == self.id, CommunityBan.user_id == user.id).first()
|
|
||||||
if banned:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def profile_id(self):
|
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}"
|
retval = self.ap_profile_id if self.ap_profile_id else f"https://{current_app.config['SERVER_NAME']}/c/{self.name}"
|
||||||
|
|
5
app/templates/topic/_notification_toggle.html
Normal file
5
app/templates/topic/_notification_toggle.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<a href="/topic/{{ topic.id }}/notification" rel="nofollow" aria-live="assertive"
|
||||||
|
aria-label="{{ 'Notify about new posts in this topic' if topic.notify_new_posts(current_user.id) else 'Do not notify about new posts' }}"
|
||||||
|
class="fe {{ 'fe-bell' if topic.notify_new_posts(current_user.id) else 'fe-no-bell' }} no-underline"
|
||||||
|
hx-post="/topic/{{ topic.id }}/notification" hx-trigger="click throttle:1s" hx-swap="outerHTML"
|
||||||
|
title="{{ _('Notify about every new post. Not advisable in high traffic topics!') }}"></a>
|
|
@ -18,6 +18,9 @@
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<h1 class="mt-2">{{ topic.name }}
|
<h1 class="mt-2">{{ topic.name }}
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
{% include 'topic/_notification_toggle.html' %}
|
||||||
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
{% if sub_topics %}
|
{% if sub_topics %}
|
||||||
<h5 class="mb-0" id="sub-topics">{{ _('Sub-topics') }}</h5>
|
<h5 class="mb-0" id="sub-topics">{{ _('Sub-topics') }}</h5>
|
||||||
|
|
|
@ -9,9 +9,11 @@ from flask_babel import _
|
||||||
from sqlalchemy import text, desc, or_
|
from sqlalchemy import text, desc, or_
|
||||||
|
|
||||||
from app.activitypub.signature import post_request
|
from app.activitypub.signature import post_request
|
||||||
from app.constants import SUBSCRIPTION_NONMEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_IMAGE, POST_TYPE_LINK, POST_TYPE_VIDEO
|
from app.constants import SUBSCRIPTION_NONMEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_IMAGE, \
|
||||||
|
POST_TYPE_LINK, POST_TYPE_VIDEO, NOTIF_TOPIC
|
||||||
from app.inoculation import inoculation
|
from app.inoculation import inoculation
|
||||||
from app.models import Topic, Community, Post, utcnow, CommunityMember, CommunityJoinRequest, User
|
from app.models import Topic, Community, Post, utcnow, CommunityMember, CommunityJoinRequest, User, \
|
||||||
|
NotificationSubscription
|
||||||
from app.topic import bp
|
from app.topic import bp
|
||||||
from app import db, celery, cache
|
from app import db, celery, cache
|
||||||
from app.topic.forms import ChooseTopicsForm
|
from app.topic.forms import ChooseTopicsForm
|
||||||
|
@ -206,6 +208,26 @@ def topic_create_post(topic_name):
|
||||||
SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR)
|
SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/topic/<int:topic_id>/notification', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def topic_notification(topic_id: int):
|
||||||
|
# Toggle whether the current user is subscribed to notifications about this community's posts or not
|
||||||
|
topic = Topic.query.get_or_404(topic_id)
|
||||||
|
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == topic.id,
|
||||||
|
NotificationSubscription.user_id == current_user.id,
|
||||||
|
NotificationSubscription.type == NOTIF_TOPIC).first()
|
||||||
|
if existing_notification:
|
||||||
|
db.session.delete(existing_notification)
|
||||||
|
db.session.commit()
|
||||||
|
else: # no subscription yet, so make one
|
||||||
|
new_notification = NotificationSubscription(name=topic.name, user_id=current_user.id, entity_id=topic.id,
|
||||||
|
type=NOTIF_TOPIC)
|
||||||
|
db.session.add(new_notification)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return render_template('topic/_notification_toggle.html', topic=topic)
|
||||||
|
|
||||||
|
|
||||||
def topics_for_form():
|
def topics_for_form():
|
||||||
topics = Topic.query.filter_by(parent_id=None).order_by(Topic.name).all()
|
topics = Topic.query.filter_by(parent_id=None).order_by(Topic.name).all()
|
||||||
result = []
|
result = []
|
||||||
|
|
|
@ -331,7 +331,7 @@ def community_membership(user: User, community: Community) -> int:
|
||||||
|
|
||||||
|
|
||||||
@cache.memoize(timeout=86400)
|
@cache.memoize(timeout=86400)
|
||||||
def communities_banned_from(user_id) -> List[int]:
|
def communities_banned_from(user_id: int) -> List[int]:
|
||||||
community_bans = CommunityBan.query.filter(CommunityBan.user_id == user_id).all()
|
community_bans = CommunityBan.query.filter(CommunityBan.user_id == user_id).all()
|
||||||
return [cb.community_id for cb in community_bans]
|
return [cb.community_id for cb in community_bans]
|
||||||
|
|
||||||
|
@ -710,6 +710,11 @@ def finalize_user_setup(user, application_required=False):
|
||||||
send_welcome_email(user, application_required)
|
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())
|
||||||
|
|
||||||
|
|
||||||
# topics, in a tree
|
# topics, in a tree
|
||||||
def topic_tree() -> List:
|
def topic_tree() -> List:
|
||||||
topics = Topic.query.order_by(Topic.name)
|
topics = Topic.query.order_by(Topic.name)
|
||||||
|
|
Loading…
Add table
Reference in a new issue