notifications

This commit is contained in:
rimu 2024-01-06 11:01:44 +13:00
parent 48dac0fc3a
commit 20f5c7b7f8
8 changed files with 73 additions and 15 deletions

View file

@ -4,7 +4,7 @@ import os
from datetime import timedelta from datetime import timedelta
from random import randint from random import randint
from typing import Union, Tuple from typing import Union, Tuple
from flask import current_app, request, g from flask import current_app, request, g, url_for
from sqlalchemy import text from sqlalchemy import text
from app import db, cache, constants, celery from app import db, cache, constants, celery
from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, \ from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, \
@ -20,7 +20,8 @@ from PIL import Image, ImageOps
from io import BytesIO from io import BytesIO
from app.utils import get_request, allowlist_html, html_to_markdown, get_setting, ap_datetime, markdown_to_html, \ from app.utils import get_request, allowlist_html, html_to_markdown, 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
def public_key(): def public_key():
@ -466,6 +467,7 @@ def post_json_to_model(post_json, user, community) -> Post:
notify = Notification(title='Suspicious content', url=post.ap_id, user_id=community_member.user_id, author_id=user.id) notify = Notification(title='Suspicious content', url=post.ap_id, user_id=community_member.user_id, author_id=user.id)
db.session.add(notify) db.session.add(notify)
already_notified.add(community_member.user_id) already_notified.add(community_member.user_id)
if domain.notify_admins: if domain.notify_admins:
for admin in Site.admins(): for admin in Site.admins():
if admin.id not in already_notified: if admin.id not in already_notified:
@ -911,12 +913,36 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep
if post_id is not None: if post_id is not None:
post = Post.query.get(post_id) post = Post.query.get(post_id)
if post.comments_enabled: if post.comments_enabled:
anchor = None
if not parent_comment_id:
notification_target = post
else:
notification_target = PostReply.query.get(parent_comment_id)
if notification_target.author.has_blocked_user(post_reply.user_id):
activity_log.exception_message = 'Replier blocked, reply discarded'
activity_log.result = 'ignored'
return None
db.session.add(post_reply) db.session.add(post_reply)
post.reply_count += 1 post.reply_count += 1
community.post_reply_count += 1 community.post_reply_count += 1
community.last_active = post.last_active = utcnow() community.last_active = post.last_active = utcnow()
activity_log.result = 'success' activity_log.result = 'success'
db.session.commit() 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:
if isinstance(notification_target, PostReply):
anchor = f"comment_{post_reply.id}"
notification = Notification(title='Reply from ' + post_reply.author.display_name(),
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 user.reputation > 100: if user.reputation > 100:
vote = PostReplyVote(user_id=1, author_id=post_reply.user_id, vote = PostReplyVote(user_id=1, author_id=post_reply.user_id,
post_reply_id=post_reply.id, post_reply_id=post_reply.id,

View file

@ -433,10 +433,11 @@ def community_report(community_id: int):
# todo: find all instance admin(s). for now just load User.id == 1 # todo: find all instance admin(s). for now just load User.id == 1
admins = [User.query.get_or_404(1)] admins = [User.query.get_or_404(1)]
for admin in admins: for admin in admins:
notification = Notification(user_id=admin.id, title=_('A post has been reported'), notification = Notification(user_id=admin.id, title=_('A community has been reported'),
url=community.local_url(), url=community.local_url(),
author_id=current_user.id) author_id=current_user.id)
db.session.add(notification) db.session.add(notification)
admin.unread_notifications += 1
db.session.commit() db.session.commit()
# todo: federate report to originating instance # todo: federate report to originating instance

View file

@ -54,14 +54,19 @@ def show_post(post_id: int):
resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30)) resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
return resp return resp
if post.author.has_blocked_user(current_user.id):
flash(_('You cannot reply to %(name)s', name=post.author.display_name()))
return redirect(url_for('activitypub.post_ap', post_id=post_id))
reply = PostReply(user_id=current_user.id, post_id=post.id, community_id=community.id, body=form.body.data, reply = PostReply(user_id=current_user.id, post_id=post.id, community_id=community.id, body=form.body.data,
body_html=markdown_to_html(form.body.data), body_html_safe=True, body_html=markdown_to_html(form.body.data), body_html_safe=True,
from_bot=current_user.bot, up_votes=1, nsfw=post.nsfw, nsfl=post.nsfl, from_bot=current_user.bot, up_votes=1, nsfw=post.nsfw, nsfl=post.nsfl,
notify_author=form.notify_author.data) notify_author=form.notify_author.data)
if post.notify_author and current_user.id != post.user_id: # todo: check if replier is blocked if post.notify_author and current_user.id != post.user_id:
notification = Notification(title=_('Reply: ') + shorten_string(form.body.data, 42), user_id=post.user_id, notification = Notification(title=_('Reply from %(name)s ', name=current_user.display_name()), user_id=post.user_id,
author_id=current_user.id, url=url_for('activitypub.post_ap', post_id=post.id)) author_id=current_user.id, url=url_for('activitypub.post_ap', post_id=post.id))
db.session.add(notification) db.session.add(notification)
post.author.unread_notifications += 1
post.last_active = community.last_active = utcnow() post.last_active = community.last_active = utcnow()
post.reply_count += 1 post.reply_count += 1
community.post_reply_count += 1 community.post_reply_count += 1
@ -363,6 +368,11 @@ def add_reply(post_id: int, comment_id: int):
in_reply_to = PostReply.query.get_or_404(comment_id) in_reply_to = PostReply.query.get_or_404(comment_id)
mods = post.community.moderators() mods = post.community.moderators()
is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods) is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods)
if in_reply_to.author.has_blocked_user(current_user.id):
flash(_('You cannot reply to %(name)s', name=in_reply_to.author.display_name()))
return redirect(url_for('activitypub.post_ap', post_id=post_id))
form = NewReplyForm() form = NewReplyForm()
if form.validate_on_submit(): if form.validate_on_submit():
current_user.last_seen = utcnow() current_user.last_seen = utcnow()
@ -374,22 +384,21 @@ def add_reply(post_id: int, comment_id: int):
notify_author=form.notify_author.data) notify_author=form.notify_author.data)
db.session.add(reply) 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 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=_('Reply: ') + shorten_string(form.body.data, 42), user_id=in_reply_to.user_id, notification = Notification(title=_('Reply from %(name)s', name=current_user.display_name()), user_id=in_reply_to.user_id,
author_id=current_user.id, url=url_for('activitypub.post_ap', post_id=post.id)) author_id=current_user.id, url=url_for('activitypub.post_ap', post_id=post.id))
db.session.add(notification) db.session.add(notification)
in_reply_to.author.unread_notifications += 1
db.session.commit() db.session.commit()
reply.ap_id = reply.profile_id() reply.ap_id = reply.profile_id()
db.session.commit() db.session.commit()
if current_user.reputation > 100: if current_user.reputation > 100:
reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id, reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id, effect=1.0)
effect=1.0)
reply.up_votes += 1 reply.up_votes += 1
reply.score += 1 reply.score += 1
reply.ranking += 1 reply.ranking += 1
db.session.add(reply_vote) db.session.add(reply_vote)
elif current_user.reputation < -100: elif current_user.reputation < -100:
reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id, reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id, effect=-1.0)
effect=-1.0)
reply.score -= 1 reply.score -= 1
reply.ranking -= 1 reply.ranking -= 1
db.session.add(reply_vote) db.session.add(reply_vote)

View file

@ -198,6 +198,10 @@
content: "\e967"; content: "\e967";
} }
.fe-bell {
position: relative;
top: 1px;
}
.fe-bell::before { .fe-bell::before {
content: "\e91e"; content: "\e91e";
} }

View file

@ -225,6 +225,11 @@ nav, etc which are used site-wide */
content: "\e967"; content: "\e967";
} }
.fe-bell {
position: relative;
top: 1px;
}
.fe-bell::before { .fe-bell::before {
content: "\e91e"; content: "\e91e";
} }

View file

@ -224,6 +224,11 @@
content: "\e967"; content: "\e967";
} }
.fe-bell {
position: relative;
top: 1px;
}
.fe-bell::before { .fe-bell::before {
content: "\e91e"; content: "\e91e";
} }

View file

@ -96,8 +96,15 @@
<li class="nav-item"><a class="nav-link" href="/admin/">{{ _('Admin') }}</a></li> <li class="nav-item"><a class="nav-link" href="/admin/">{{ _('Admin') }}</a></li>
{% endif %} {% endif %}
<li class="nav-item"><a class="nav-link" href="/auth/logout">{{ _('Log out') }}</a></li> <li class="nav-item"><a class="nav-link" href="/auth/logout">{{ _('Log out') }}</a></li>
<li class="nav-item"><a class="nav-link" href="/notifications"><span class="fe fe-bell"></span> <li class="nav-item">
{{ current_user.unread_notifications if current_user.unread_notifications else '' }}</a></li> <a class="nav-link" href="/notifications">
{% if current_user.unread_notifications %}
<span class="fe fe-bell red"></span> <span class="red">{{ current_user.unread_notifications }}</span>
{% else %}
<span class="fe fe-bell"></span>
{% endif %}
</a>
</li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>

View file

@ -286,6 +286,7 @@ def report_profile(actor):
if admin.id not in already_notified: if admin.id not in already_notified:
notify = Notification(title='Reported user', url=user.ap_id, user_id=admin.id, author_id=current_user.id) notify = Notification(title='Reported user', url=user.ap_id, user_id=admin.id, author_id=current_user.id)
db.session.add(notify) db.session.add(notify)
admin.unread_notifications += 1
user.reports += 1 user.reports += 1
db.session.commit() db.session.commit()
@ -426,12 +427,12 @@ def ban_purge_profile(actor):
@bp.route('/notifications', methods=['GET', 'POST']) @bp.route('/notifications', methods=['GET', 'POST'])
@login_required @login_required
def notifications(): def notifications():
"""Remove notifications older than 30 days""" """Remove notifications older than 90 days"""
db.session.query(Notification).filter( db.session.query(Notification).filter(
Notification.created_at < utcnow() - timedelta(days=30)).delete() Notification.created_at < utcnow() - timedelta(days=90)).delete()
db.session.commit() db.session.commit()
# Update unread notifications count # Update unread notifications count, just to be sure
current_user.unread_notifications = Notification.query.filter_by(user_id=current_user.id, read=False).count() current_user.unread_notifications = Notification.query.filter_by(user_id=current_user.id, read=False).count()
db.session.commit() db.session.commit()