mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
notify about new posts in communities
This commit is contained in:
parent
f777ea5dfd
commit
2bc1ab47d0
10 changed files with 144 additions and 13 deletions
|
@ -7,6 +7,7 @@ from typing import Union, Tuple
|
|||
from flask import current_app, request, g, url_for
|
||||
from sqlalchemy import text
|
||||
from app import db, cache, constants, celery
|
||||
from app.community.util import notify_about_post
|
||||
from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, \
|
||||
PostVote, PostReplyVote, ActivityPubLog, Notification, Site
|
||||
import time
|
||||
|
@ -1040,6 +1041,9 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
|||
|
||||
if post.image_id:
|
||||
make_image_sizes(post.image_id, 266, None, 'posts')
|
||||
|
||||
notify_about_post(post)
|
||||
|
||||
if user.reputation > 100:
|
||||
vote = PostVote(user_id=1, author_id=post.user_id,
|
||||
post_id=post.id,
|
||||
|
|
|
@ -9,7 +9,8 @@ from app.activitypub.util import default_context
|
|||
from app.community.forms import SearchRemoteCommunity, AddLocalCommunity, CreatePostForm, ReportCommunityForm, \
|
||||
DeleteCommunityForm
|
||||
from app.community.util import search_for_community, community_url_exists, actor_to_community, \
|
||||
opengraph_parse, url_to_thumbnail_file, save_post, save_icon_file, save_banner_file, send_to_remote_instance
|
||||
opengraph_parse, url_to_thumbnail_file, save_post, save_icon_file, save_banner_file, send_to_remote_instance, \
|
||||
notify_about_post
|
||||
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \
|
||||
SUBSCRIPTION_PENDING
|
||||
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \
|
||||
|
@ -223,13 +224,13 @@ def subscribe(actor):
|
|||
success = post_request(community.ap_inbox_url, follow, current_user.private_key,
|
||||
current_user.profile_id() + '#main-key')
|
||||
if success:
|
||||
flash('Your request to join has been sent to ' + community.title)
|
||||
flash(_('You have joined %(name)s', name=community.title))
|
||||
else:
|
||||
flash('There was a problem while trying to join.', 'error')
|
||||
flash(_('There was a problem while trying to join.'), 'error')
|
||||
else: # for local communities, joining is instant
|
||||
banned = CommunityBan.query.filter_by(user_id=current_user.id, community_id=community.id).first()
|
||||
if banned:
|
||||
flash('You cannot join this community')
|
||||
flash(_('You cannot join this community'))
|
||||
else:
|
||||
member = CommunityMember(user_id=current_user.id, community_id=community.id)
|
||||
db.session.add(member)
|
||||
|
@ -340,6 +341,8 @@ def add_post(actor):
|
|||
post.ap_id = f"https://{current_app.config['SERVER_NAME']}/post/{post.id}"
|
||||
db.session.commit()
|
||||
|
||||
notify_about_post(post)
|
||||
|
||||
page = {
|
||||
'type': 'Page',
|
||||
'id': post.ap_id,
|
||||
|
@ -486,3 +489,22 @@ def community_block_instance(community_id: int):
|
|||
db.session.commit()
|
||||
flash(_('Content from %(name)s will be hidden.', name=community.instance.domain))
|
||||
return redirect(community.local_url())
|
||||
|
||||
|
||||
@bp.route('/<int:community_id>/notification', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def community_notification(community_id: int):
|
||||
community = Community.query.get_or_404(community_id)
|
||||
member_info = CommunityMember.query.filter(CommunityMember.community_id == community.id,
|
||||
CommunityMember.user_id == current_user.id).first()
|
||||
# existing community members get their notification flag toggled
|
||||
if member_info and not member_info.is_banned:
|
||||
member_info.notify_new_posts = not member_info.notify_new_posts
|
||||
db.session.commit()
|
||||
else: # people who are not yet members become members, with notify on.
|
||||
if not community.user_is_banned(current_user):
|
||||
new_member = CommunityMember(community_id=community.id, user_id=current_user.id, notify_new_posts=True)
|
||||
db.session.add(new_member)
|
||||
db.session.commit()
|
||||
|
||||
return render_template('community/_notification_toggle.html', community=community)
|
||||
|
|
|
@ -13,9 +13,9 @@ from app.activitypub.signature import post_request
|
|||
from app.activitypub.util import find_actor_or_create, actor_json_to_model, post_json_to_model
|
||||
from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE
|
||||
from app.models import Community, File, BannedInstances, PostReply, PostVote, Post, utcnow, CommunityMember, Site, \
|
||||
Instance
|
||||
Instance, Notification, User
|
||||
from app.utils import get_request, gibberish, markdown_to_html, domain_from_url, validate_image, allowlist_html, \
|
||||
html_to_markdown, is_image_url, ensure_directory_exists, inbox_domain, post_ranking
|
||||
html_to_markdown, is_image_url, ensure_directory_exists, inbox_domain, post_ranking, shorten_string
|
||||
from sqlalchemy import desc, text
|
||||
import os
|
||||
from opengraph_parse import parse_page
|
||||
|
@ -387,4 +387,15 @@ def send_to_remote_instance_task(instance_id: int, community_id: int, payload):
|
|||
instance.start_trying_again = utcnow() + timedelta(seconds=instance.failures ** 4)
|
||||
if instance.failures > 2:
|
||||
instance.dormant = True
|
||||
db.session.commit()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def notify_about_post(post: Post):
|
||||
people_to_notify = CommunityMember.query.filter_by(community_id=post.community_id, notify_new_posts=True, is_banned=False)
|
||||
for person in people_to_notify:
|
||||
if person.user_id != post.user_id:
|
||||
new_notification = Notification(title=shorten_string(post.title, 25), url=f"/post/{post.id}", user_id=person.user_id, author_id=post.user_id)
|
||||
db.session.add(new_notification)
|
||||
user = User.query.get(person.user_id) # todo: make this more efficient by doing a join with CommunityMember at the start of the function
|
||||
user.unread_notifications += 1
|
||||
db.session.commit()
|
||||
|
|
|
@ -253,6 +253,11 @@ class Community(db.Model):
|
|||
else:
|
||||
return any(moderator.user_id == user.id and moderator.is_owner for moderator in self.moderators())
|
||||
|
||||
def user_is_banned(self, user):
|
||||
membership = CommunityMember.query.filter(CommunityMember.community_id == self.id, CommunityMember.user_id == user.id).first()
|
||||
return membership.is_banned if membership else False
|
||||
|
||||
|
||||
def profile_id(self):
|
||||
return self.ap_profile_id if self.ap_profile_id else f"https://{current_app.config['SERVER_NAME']}/c/{self.name}"
|
||||
|
||||
|
@ -265,6 +270,14 @@ class Community(db.Model):
|
|||
else:
|
||||
return f"https://{current_app.config['SERVER_NAME']}/c/{self.ap_id}"
|
||||
|
||||
def notify_new_posts(self, user_id: int) -> bool:
|
||||
member_info = CommunityMember.query.filter(CommunityMember.community_id == self.id,
|
||||
CommunityMember.is_banned == False,
|
||||
CommunityMember.user_id == user_id).first()
|
||||
if not member_info:
|
||||
return False
|
||||
return member_info.notify_new_posts
|
||||
|
||||
# instances that have users which are members of this community. (excluding the current instance)
|
||||
def following_instances(self, include_dormant=False) -> List[Instance]:
|
||||
instances = Instance.query.join(User, User.instance_id == Instance.id).join(CommunityMember, CommunityMember.user_id == User.id)
|
||||
|
@ -782,6 +795,7 @@ class CommunityMember(db.Model):
|
|||
is_moderator = db.Column(db.Boolean, default=False)
|
||||
is_owner = db.Column(db.Boolean, default=False)
|
||||
is_banned = db.Column(db.Boolean, default=False)
|
||||
notify_new_posts = db.Column(db.Boolean, default=False)
|
||||
created_at = db.Column(db.DateTime, default=utcnow)
|
||||
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@
|
|||
content: "\e967";
|
||||
}
|
||||
|
||||
.fe-bell {
|
||||
.fe-bell, .fe-no-bell {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
@ -206,6 +206,18 @@
|
|||
content: "\e91e";
|
||||
}
|
||||
|
||||
.fe-no-bell::before {
|
||||
content: "\e91f";
|
||||
}
|
||||
|
||||
h1 {
|
||||
.fe-bell, .fe-no-bell {
|
||||
font-size: 18px;
|
||||
top: -5px;
|
||||
left: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.fe-magnify {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
|
|
@ -225,7 +225,7 @@ nav, etc which are used site-wide */
|
|||
content: "\e967";
|
||||
}
|
||||
|
||||
.fe-bell {
|
||||
.fe-bell, .fe-no-bell {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
@ -234,6 +234,16 @@ nav, etc which are used site-wide */
|
|||
content: "\e91e";
|
||||
}
|
||||
|
||||
.fe-no-bell::before {
|
||||
content: "\e91f";
|
||||
}
|
||||
|
||||
h1 .fe-bell, h1 .fe-no-bell {
|
||||
font-size: 18px;
|
||||
top: -5px;
|
||||
left: 17px;
|
||||
}
|
||||
|
||||
.fe-magnify {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
|
|
@ -224,7 +224,7 @@
|
|||
content: "\e967";
|
||||
}
|
||||
|
||||
.fe-bell {
|
||||
.fe-bell, .fe-no-bell {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
@ -233,6 +233,16 @@
|
|||
content: "\e91e";
|
||||
}
|
||||
|
||||
.fe-no-bell::before {
|
||||
content: "\e91f";
|
||||
}
|
||||
|
||||
h1 .fe-bell, h1 .fe-no-bell {
|
||||
font-size: 18px;
|
||||
top: -5px;
|
||||
left: 17px;
|
||||
}
|
||||
|
||||
.fe-magnify {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
|
4
app/templates/community/_notification_toggle.html
Normal file
4
app/templates/community/_notification_toggle.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<a href="/community/{{ community.id }}/notification"
|
||||
class="fe {{ 'fe-bell' if community.notify_new_posts(current_user.id) else 'fe-no-bell' }} no-underline"
|
||||
hx-post="/community/{{ community.id }}/notification" hx-trigger="click throttle:1s" hx-swap="outerHTML"
|
||||
title="{{ _('Notify about every new post. Not advisable in high traffic communities!') }}"></a>
|
|
@ -15,7 +15,11 @@
|
|||
</nav>
|
||||
</div>
|
||||
<img class="community_icon_big bump_up rounded-circle" src="{{ community.icon_image() }}" />
|
||||
<h1 class="mt-2">{{ community.title }}</h1>
|
||||
<h1 class="mt-2">{{ community.title }}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% include 'community/_notification_toggle.html' %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% elif community.icon_id %}
|
||||
<div class="row">
|
||||
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||
|
@ -29,7 +33,11 @@
|
|||
<img class="community_icon_big rounded-circle" src="{{ community.icon_image() }}" />
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<h1 class="mt-3">{{ community.title }}</h1>
|
||||
<h1 class="mt-3">{{ community.title }}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% include 'community/_notification_toggle.html' %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
|
@ -40,7 +48,11 @@
|
|||
<li class="breadcrumb-item active">{{ community.title|shorten }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1 class="mt-2">{{ community.title }}</h1>
|
||||
<h1 class="mt-2">{{ community.title }}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% include 'community/_notification_toggle.html' %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% endif %}
|
||||
{% include "community/_community_nav.html" %}
|
||||
<div class="post_list">
|
||||
|
|
32
migrations/versions/dc49309fc13e_community_notifications.py
Normal file
32
migrations/versions/dc49309fc13e_community_notifications.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
"""community notifications
|
||||
|
||||
Revision ID: dc49309fc13e
|
||||
Revises: fd5d3a9cb584
|
||||
Create Date: 2024-01-07 10:35:55.484246
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'dc49309fc13e'
|
||||
down_revision = 'fd5d3a9cb584'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('community_member', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('notify_new_posts', sa.Boolean(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('community_member', schema=None) as batch_op:
|
||||
batch_op.drop_column('notify_new_posts')
|
||||
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in a new issue