2024-03-09 14:59:05 +13:00
from collections import namedtuple
2024-01-25 20:16:08 +13:00
from io import BytesIO
2024-01-19 15:08:39 +13:00
from random import randint
2024-01-01 11:38:24 +13:00
from flask import redirect , url_for , flash , request , make_response , session , Markup , current_app , abort , g , json
2024-01-03 16:29:58 +13:00
from flask_login import current_user , login_required
2023-08-29 22:01:06 +12:00
from flask_babel import _
2023-11-21 23:05:07 +13:00
from sqlalchemy import or_ , desc
2023-09-17 21:19:51 +12:00
2023-12-03 22:41:15 +13:00
from app import db , constants , cache
2024-01-03 16:29:58 +13:00
from app . activitypub . signature import RsaKeys , post_request
2024-03-24 12:00:16 +13:00
from app . activitypub . util import default_context , notify_about_post , find_actor_or_create , make_image_sizes
2024-03-13 16:40:20 +13:00
from app . chat . util import send_message
from app . community . forms import SearchRemoteCommunity , CreatePostForm , ReportCommunityForm , \
2024-03-15 14:24:45 +13:00
DeleteCommunityForm , AddCommunityForm , EditCommunityForm , AddModeratorForm , BanUserCommunityForm
2023-11-30 06:36:08 +13:00
from app . community . util import search_for_community , community_url_exists , actor_to_community , \
2024-03-15 14:24:45 +13:00
opengraph_parse , url_to_thumbnail_file , save_post , save_icon_file , save_banner_file , send_to_remote_instance , \
delete_post_from_community , delete_post_reply_from_community
2023-12-03 22:41:15 +13:00
from app . constants import SUBSCRIPTION_MEMBER , SUBSCRIPTION_OWNER , POST_TYPE_LINK , POST_TYPE_ARTICLE , POST_TYPE_IMAGE , \
2024-01-11 20:52:09 +13:00
SUBSCRIPTION_PENDING , SUBSCRIPTION_MODERATOR
2024-01-19 15:08:39 +13:00
from app . inoculation import inoculation
2023-11-30 06:36:08 +13:00
from app . models import User , Community , CommunityMember , CommunityJoinRequest , CommunityBan , Post , \
2024-03-15 14:24:45 +13:00
File , PostVote , utcnow , Report , Notification , InstanceBlock , ActivityPubLog , Topic , Conversation , PostReply
2023-08-29 22:01:06 +12:00
from app . community import bp
2024-03-13 16:40:20 +13:00
from app . user . utils import search_for_user
2023-10-23 20:18:46 +13:00
from app . utils import get_setting , render_template , allowlist_html , markdown_to_html , validation_required , \
2024-01-25 20:16:08 +13:00
shorten_string , gibberish , community_membership , ap_datetime , \
2024-02-24 11:07:06 +13:00
request_etag_matches , return_304 , instance_banned , can_create_post , can_upvote , can_downvote , user_filters_posts , \
2024-03-13 16:40:20 +13:00
joined_communities , moderating_communities , blocked_domains , mimetype_from_url , blocked_instances , \
2024-03-21 21:19:50 +13:00
community_moderators , communities_banned_from
2023-12-12 18:28:49 +13:00
from feedgen . feed import FeedGenerator
2024-01-03 20:14:39 +13:00
from datetime import timezone , timedelta
2023-08-29 22:01:06 +12:00
@bp.route ( ' /add_local ' , methods = [ ' GET ' , ' POST ' ] )
2023-10-23 13:03:35 +13:00
@login_required
2023-08-29 22:01:06 +12:00
def add_local ( ) :
2024-01-06 14:54:10 +13:00
flash ( ' PieFed is still being tested so hosting communities on piefed.social is not advised except for testing purposes. ' , ' warning ' )
2024-03-13 16:40:20 +13:00
form = AddCommunityForm ( )
2023-12-31 12:09:20 +13:00
if g . site . enable_nsfw is False :
2023-09-03 16:30:20 +12:00
form . nsfw . render_kw = { ' disabled ' : True }
2023-09-05 20:25:02 +12:00
if form . validate_on_submit ( ) and not community_url_exists ( form . url . data ) :
# todo: more intense data validation
2023-09-17 21:19:51 +12:00
if form . url . data . strip ( ) . lower ( ) . startswith ( ' /c/ ' ) :
2023-09-05 20:25:02 +12:00
form . url . data = form . url . data [ 3 : ]
2023-09-03 16:30:20 +12:00
private_key , public_key = RsaKeys . generate_keypair ( )
community = Community ( title = form . community_name . data , name = form . url . data , description = form . description . data ,
2023-09-08 20:04:01 +12:00
rules = form . rules . data , nsfw = form . nsfw . data , private_key = private_key ,
2024-01-04 22:08:32 +13:00
public_key = public_key , description_html = markdown_to_html ( form . description . data ) ,
2024-01-27 11:19:23 +13:00
rules_html = markdown_to_html ( form . rules . data ) , local_only = form . local_only . data ,
2024-03-24 01:53:18 +00:00
ap_profile_id = ' https:// ' + current_app . config [ ' SERVER_NAME ' ] + ' /c/ ' + form . url . data . lower ( ) ,
2024-03-24 02:02:47 +00:00
ap_public_url = ' https:// ' + current_app . config [ ' SERVER_NAME ' ] + ' /c/ ' + form . url . data ,
2023-12-27 14:38:41 +13:00
ap_followers_url = ' https:// ' + current_app . config [ ' SERVER_NAME ' ] + ' /c/ ' + form . url . data + ' /followers ' ,
2024-02-14 09:50:13 +13:00
ap_domain = current_app . config [ ' SERVER_NAME ' ] ,
2023-12-21 22:14:43 +13:00
subscriptions_count = 1 , instance_id = 1 , low_quality = ' memes ' in form . url . data )
2023-12-08 17:13:38 +13:00
icon_file = request . files [ ' icon_file ' ]
if icon_file and icon_file . filename != ' ' :
file = save_icon_file ( icon_file )
if file :
community . icon = file
banner_file = request . files [ ' banner_file ' ]
if banner_file and banner_file . filename != ' ' :
file = save_banner_file ( banner_file )
if file :
community . image = file
2023-09-03 16:30:20 +12:00
db . session . add ( community )
db . session . commit ( )
membership = CommunityMember ( user_id = current_user . id , community_id = community . id , is_moderator = True ,
is_owner = True )
db . session . add ( membership )
db . session . commit ( )
flash ( _ ( ' Your new community has been created. ' ) )
2024-01-09 20:44:08 +13:00
cache . delete_memoized ( community_membership , current_user , community )
2024-01-12 12:34:08 +13:00
cache . delete_memoized ( joined_communities , current_user . id )
cache . delete_memoized ( moderating_communities , current_user . id )
2023-09-03 16:30:20 +12:00
return redirect ( ' /c/ ' + community . name )
2024-01-12 12:34:08 +13:00
return render_template ( ' community/add_local.html ' , title = _ ( ' Create community ' ) , form = form , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-01-27 11:19:23 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) , current_app = current_app )
2023-08-29 22:01:06 +12:00
@bp.route ( ' /add_remote ' , methods = [ ' GET ' , ' POST ' ] )
2023-10-23 13:03:35 +13:00
@login_required
2023-08-29 22:01:06 +12:00
def add_remote ( ) :
form = SearchRemoteCommunity ( )
new_community = None
if form . validate_on_submit ( ) :
2024-01-19 17:29:50 +13:00
address = form . address . data . strip ( ) . lower ( )
2023-08-29 22:01:06 +12:00
if address . startswith ( ' ! ' ) and ' @ ' in address :
new_community = search_for_community ( address )
elif address . startswith ( ' @ ' ) and ' @ ' in address [ 1 : ] :
# todo: the user is searching for a person instead
. . .
elif ' @ ' in address :
new_community = search_for_community ( ' ! ' + address )
else :
2023-09-08 20:04:01 +12:00
message = Markup (
' Type address in the format !community@server.name. Search on <a href= " https://lemmyverse.net/communities " >Lemmyverse.net</a> to find some. ' )
2023-08-29 22:01:06 +12:00
flash ( message , ' error ' )
2024-01-21 21:04:48 +13:00
if new_community is None :
if g . site . enable_nsfw :
flash ( _ ( ' Community not found. ' ) , ' warning ' )
else :
flash ( _ ( ' Community not found. If you are searching for a nsfw community it is blocked by this instance. ' ) , ' warning ' )
2024-03-24 00:15:10 +00:00
else :
if new_community . banned :
flash ( _ ( ' That community is banned from %(site)s . ' , site = g . site . name ) , ' warning ' )
2024-02-14 15:55:45 +13:00
2023-08-29 22:01:06 +12:00
return render_template ( ' community/add_remote.html ' ,
title = _ ( ' Add remote community ' ) , form = form , new_community = new_community ,
2024-01-12 12:34:08 +13:00
subscribed = community_membership ( current_user , new_community ) > = SUBSCRIPTION_MEMBER , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) )
2023-08-29 22:01:06 +12:00
# @bp.route('/c/<actor>', methods=['GET']) - defined in activitypub/routes.py, which calls this function for user requests. A bit weird.
def show_community ( community : Community ) :
2023-12-10 15:10:09 +13:00
2024-01-01 11:38:24 +13:00
if community . banned :
abort ( 404 )
2023-12-15 17:35:11 +13:00
page = request . args . get ( ' page ' , 1 , type = int )
2024-01-15 18:26:22 +13:00
sort = request . args . get ( ' sort ' , ' ' if current_user . is_anonymous else current_user . default_sort )
2024-03-20 20:41:45 +13:00
if sort is None :
sort = ' '
2024-01-21 15:44:13 +13:00
low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 '
2024-03-17 07:28:03 +00:00
if low_bandwidth :
post_layout = None
else :
if community . default_layout is not None :
post_layout = request . args . get ( ' layout ' , community . default_layout )
else :
post_layout = request . args . get ( ' layout ' , ' list ' )
2023-12-15 17:35:11 +13:00
2024-01-12 13:24:49 +13:00
# If nothing has changed since their last visit, return HTTP 304
2024-01-21 20:20:40 +13:00
current_etag = f " { community . id } { sort } { post_layout } _ { hash ( community . last_active ) } "
2024-01-12 13:24:49 +13:00
if current_user . is_anonymous and request_etag_matches ( current_etag ) :
return return_304 ( current_etag )
2024-03-13 16:40:20 +13:00
mods = community_moderators ( community . id )
2023-09-05 20:25:02 +12:00
2023-10-02 22:16:44 +13:00
is_moderator = current_user . is_authenticated and any ( mod . user_id == current_user . id for mod in mods )
2023-10-15 21:13:32 +13:00
is_owner = current_user . is_authenticated and any (
mod . user_id == current_user . id and mod . is_owner == True for mod in mods )
2023-12-22 14:05:39 +13:00
is_admin = current_user . is_authenticated and current_user . is_admin ( )
2023-09-05 20:25:02 +12:00
if community . private_mods :
mod_list = [ ]
else :
mod_user_ids = [ mod . user_id for mod in mods ]
mod_list = User . query . filter ( User . id . in_ ( mod_user_ids ) ) . all ( )
2024-02-01 11:59:09 +13:00
posts = community . posts
# filter out nsfw and nsfl if desired
if current_user . is_anonymous :
posts = posts . filter ( Post . from_bot == False , Post . nsfw == False , Post . nsfl == False )
2024-01-11 20:39:22 +13:00
content_filters = { }
2023-10-07 21:32:19 +13:00
else :
2024-02-01 11:59:09 +13:00
if current_user . ignore_bots :
posts = posts . filter ( Post . from_bot == False )
if current_user . show_nsfl is False :
posts = posts . filter ( Post . nsfl == False )
if current_user . show_nsfw is False :
posts = posts . filter ( Post . nsfw == False )
2024-01-11 20:39:22 +13:00
content_filters = user_filters_posts ( current_user . id )
2024-02-01 11:59:09 +13:00
2024-03-12 20:06:24 +13:00
# filter domains and instances
2024-02-05 07:35:09 +13:00
domains_ids = blocked_domains ( current_user . id )
if domains_ids :
posts = posts . filter ( or_ ( Post . domain_id . not_in ( domains_ids ) , Post . domain_id == None ) )
2024-03-12 20:06:24 +13:00
instance_ids = blocked_instances ( current_user . id )
if instance_ids :
posts = posts . filter ( or_ ( Post . instance_id . not_in ( instance_ids ) , Post . instance_id == None ) )
2024-02-02 17:22:32 +13:00
2024-01-03 20:14:39 +13:00
if sort == ' ' or sort == ' hot ' :
2024-03-20 20:41:45 +13:00
posts = posts . order_by ( desc ( Post . sticky ) ) . order_by ( desc ( Post . ranking ) ) . order_by ( desc ( Post . posted_at ) )
2024-01-03 20:14:39 +13:00
elif sort == ' top ' :
2024-03-20 20:41:45 +13:00
posts = posts . filter ( Post . posted_at > utcnow ( ) - timedelta ( days = 7 ) ) . order_by ( desc ( Post . sticky ) ) . order_by ( desc ( Post . score ) )
2024-01-03 20:14:39 +13:00
elif sort == ' new ' :
posts = posts . order_by ( desc ( Post . posted_at ) )
2024-01-15 18:26:22 +13:00
elif sort == ' active ' :
2024-03-20 20:41:45 +13:00
posts = posts . order_by ( desc ( Post . sticky ) ) . order_by ( desc ( Post . last_active ) )
2024-01-21 15:44:13 +13:00
per_page = 100
if post_layout == ' masonry ' :
per_page = 200
elif post_layout == ' masonry_wide ' :
per_page = 300
posts = posts . paginate ( page = page , per_page = per_page , error_out = False )
2023-10-07 21:32:19 +13:00
2024-03-09 14:59:05 +13:00
breadcrumbs = [ ]
breadcrumb = namedtuple ( " Breadcrumb " , [ ' text ' , ' url ' ] )
breadcrumb . text = _ ( ' Home ' )
breadcrumb . url = ' / '
breadcrumbs . append ( breadcrumb )
2024-01-15 18:32:58 +13:00
if community . topic_id :
related_communities = Community . query . filter_by ( topic_id = community . topic_id ) . \
filter ( Community . id != community . id , Community . banned == False ) . order_by ( Community . name )
2024-03-09 14:59:05 +13:00
topics = [ ]
previous_topic = Topic . query . get ( community . topic_id )
topics . append ( previous_topic )
while previous_topic . parent_id :
topic = Topic . query . get ( previous_topic . parent_id )
topics . append ( topic )
previous_topic = topic
topics = list ( reversed ( topics ) )
breadcrumb = namedtuple ( " Breadcrumb " , [ ' text ' , ' url ' ] )
breadcrumb . text = _ ( ' Topics ' )
breadcrumb . url = ' /topics '
breadcrumbs . append ( breadcrumb )
existing_url = ' /topic '
for topic in topics :
breadcrumb = namedtuple ( " Breadcrumb " , [ ' text ' , ' url ' ] )
breadcrumb . text = topic . name
breadcrumb . url = f " { existing_url } / { topic . machine_name } "
breadcrumbs . append ( breadcrumb )
existing_url = breadcrumb . url
2024-01-15 18:32:58 +13:00
else :
related_communities = [ ]
2024-03-09 14:59:05 +13:00
breadcrumb = namedtuple ( " Breadcrumb " , [ ' text ' , ' url ' ] )
breadcrumb . text = _ ( ' Communities ' )
breadcrumb . url = ' /communities '
breadcrumbs . append ( breadcrumb )
2024-01-15 18:32:58 +13:00
2023-10-23 20:18:46 +13:00
description = shorten_string ( community . description , 150 ) if community . description else None
og_image = community . image . source_url if community . image_id else None
2023-12-15 17:35:11 +13:00
next_url = url_for ( ' activitypub.community_profile ' , actor = community . ap_id if community . ap_id is not None else community . name ,
2024-01-21 20:20:40 +13:00
page = posts . next_num , sort = sort , layout = post_layout ) if posts . has_next else None
2023-12-15 17:35:11 +13:00
prev_url = url_for ( ' activitypub.community_profile ' , actor = community . ap_id if community . ap_id is not None else community . name ,
2024-01-21 20:20:40 +13:00
page = posts . prev_num , sort = sort , layout = post_layout ) if posts . has_prev and page != 1 else None
2023-12-15 17:35:11 +13:00
2024-03-09 14:59:05 +13:00
return render_template ( ' community/community.html ' , community = community , title = community . title , breadcrumbs = breadcrumbs ,
2023-12-22 14:05:39 +13:00
is_moderator = is_moderator , is_owner = is_owner , is_admin = is_admin , mods = mod_list , posts = posts , description = description ,
2023-12-03 22:41:15 +13:00
og_image = og_image , POST_TYPE_IMAGE = POST_TYPE_IMAGE , POST_TYPE_LINK = POST_TYPE_LINK , SUBSCRIPTION_PENDING = SUBSCRIPTION_PENDING ,
2024-01-11 20:52:09 +13:00
SUBSCRIPTION_MEMBER = SUBSCRIPTION_MEMBER , SUBSCRIPTION_OWNER = SUBSCRIPTION_OWNER , SUBSCRIPTION_MODERATOR = SUBSCRIPTION_MODERATOR ,
2024-01-21 20:20:40 +13:00
etag = f " { community . id } { sort } { post_layout } _ { hash ( community . last_active ) } " , related_communities = related_communities ,
2024-01-21 15:44:13 +13:00
next_url = next_url , prev_url = prev_url , low_bandwidth = low_bandwidth ,
2024-02-28 12:43:51 +13:00
rss_feed = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /community/ { community . link ( ) } /feed " , rss_feed_name = f " { community . title } on PieFed " ,
2024-01-12 12:34:08 +13:00
content_filters = content_filters , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-01-19 15:08:39 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) , sort = sort ,
2024-01-27 11:19:23 +13:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] , post_layout = post_layout , current_app = current_app )
2023-12-12 18:28:49 +13:00
# RSS feed of the community
@bp.route ( ' /<actor>/feed ' , methods = [ ' GET ' ] )
@cache.cached ( timeout = 600 )
def show_community_rss ( actor ) :
actor = actor . strip ( )
if ' @ ' in actor :
community : Community = Community . query . filter_by ( ap_id = actor , banned = False ) . first ( )
else :
community : Community = Community . query . filter_by ( name = actor , banned = False , ap_id = None ) . first ( )
if community is not None :
# If nothing has changed since their last visit, return HTTP 304
current_etag = f " { community . id } _ { hash ( community . last_active ) } "
if request_etag_matches ( current_etag ) :
return return_304 ( current_etag , ' application/rss+xml ' )
posts = community . posts . filter ( Post . from_bot == False ) . order_by ( desc ( Post . created_at ) ) . limit ( 100 ) . all ( )
description = shorten_string ( community . description , 150 ) if community . description else None
og_image = community . image . source_url if community . image_id else None
fg = FeedGenerator ( )
fg . id ( f " https:// { current_app . config [ ' SERVER_NAME ' ] } /c/ { actor } " )
2024-02-28 12:43:51 +13:00
fg . title ( f ' { community . title } on { g . site . name } ' )
2023-12-12 18:28:49 +13:00
fg . link ( href = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /c/ { actor } " , rel = ' alternate ' )
if og_image :
fg . logo ( og_image )
else :
fg . logo ( f " https:// { current_app . config [ ' SERVER_NAME ' ] } /static/images/apple-touch-icon.png " )
if description :
fg . subtitle ( description )
else :
fg . subtitle ( ' ' )
fg . link ( href = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /c/ { actor } /feed " , rel = ' self ' )
fg . language ( ' en ' )
for post in posts :
fe = fg . add_entry ( )
fe . title ( post . title )
fe . link ( href = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /post/ { post . id } " )
2024-02-28 12:55:30 +13:00
if post . url :
type = mimetype_from_url ( post . url )
if type and not type . startswith ( ' text/ ' ) :
fe . enclosure ( post . url , type = type )
2023-12-12 18:28:49 +13:00
fe . description ( post . body_html )
fe . guid ( post . profile_id ( ) , permalink = True )
fe . author ( name = post . author . user_name )
fe . pubDate ( post . created_at . replace ( tzinfo = timezone . utc ) )
response = make_response ( fg . rss_str ( ) )
response . headers . set ( ' Content-Type ' , ' application/rss+xml ' )
response . headers . add_header ( ' ETag ' , f " { community . id } _ { hash ( community . last_active ) } " )
response . headers . add_header ( ' Cache-Control ' , ' no-cache, max-age=600, must-revalidate ' )
return response
else :
abort ( 404 )
2023-09-05 20:25:02 +12:00
@bp.route ( ' /<actor>/subscribe ' , methods = [ ' GET ' ] )
2023-10-02 22:16:44 +13:00
@login_required
2023-10-23 13:03:35 +13:00
@validation_required
2023-09-05 20:25:02 +12:00
def subscribe ( actor ) :
2023-09-08 20:04:01 +12:00
remote = False
2023-09-05 20:25:02 +12:00
actor = actor . strip ( )
if ' @ ' in actor :
community = Community . query . filter_by ( banned = False , ap_id = actor ) . first ( )
2023-09-08 20:04:01 +12:00
remote = True
2023-09-05 20:25:02 +12:00
else :
community = Community . query . filter_by ( name = actor , banned = False , ap_id = None ) . first ( )
if community is not None :
2023-12-03 22:41:15 +13:00
if community_membership ( current_user , community ) != SUBSCRIPTION_MEMBER and community_membership ( current_user , community ) != SUBSCRIPTION_PENDING :
2024-01-09 20:44:08 +13:00
banned = CommunityBan . query . filter_by ( user_id = current_user . id , community_id = community . id ) . first ( )
if banned :
flash ( _ ( ' You cannot join this community ' ) )
2023-09-08 20:04:01 +12:00
if remote :
# send ActivityPub message to remote community, asking to follow. Accept message will be sent to our shared inbox
join_request = CommunityJoinRequest ( user_id = current_user . id , community_id = community . id )
db . session . add ( join_request )
db . session . commit ( )
follow = {
" actor " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /u/ { current_user . user_name } " ,
2023-11-17 22:02:44 +13:00
" to " : [ community . ap_profile_id ] ,
" object " : community . ap_profile_id ,
2023-09-08 20:04:01 +12:00
" type " : " Follow " ,
2023-09-17 21:19:51 +12:00
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/follow/ { join_request . id } "
2023-09-08 20:04:01 +12:00
}
2023-12-22 15:34:45 +13:00
success = post_request ( community . ap_inbox_url , follow , current_user . private_key ,
2023-11-17 22:02:44 +13:00
current_user . profile_id ( ) + ' #main-key ' )
2024-01-09 20:47:08 +13:00
if not success :
flash ( _ ( " There was a problem while trying to communicate with remote server. If other people have already joined this community it won ' t matter. " ) , ' error ' )
2024-01-09 20:44:08 +13:00
# for local communities, joining is instant
member = CommunityMember ( user_id = current_user . id , community_id = community . id )
db . session . add ( member )
db . session . commit ( )
flash ( ' You joined ' + community . title )
2023-09-05 20:25:02 +12:00
referrer = request . headers . get ( ' Referer ' , None )
2024-01-07 09:29:36 +13:00
cache . delete_memoized ( community_membership , current_user , community )
2024-01-12 12:34:08 +13:00
cache . delete_memoized ( joined_communities , current_user . id )
2023-09-05 20:25:02 +12:00
if referrer is not None :
return redirect ( referrer )
else :
return redirect ( ' /c/ ' + actor )
else :
abort ( 404 )
@bp.route ( ' /<actor>/unsubscribe ' , methods = [ ' GET ' ] )
2023-10-02 22:16:44 +13:00
@login_required
2023-09-05 20:25:02 +12:00
def unsubscribe ( actor ) :
2023-09-17 21:19:51 +12:00
community = actor_to_community ( actor )
2023-09-05 20:25:02 +12:00
if community is not None :
2023-12-03 22:41:15 +13:00
subscription = community_membership ( current_user , community )
2023-09-05 20:25:02 +12:00
if subscription :
if subscription != SUBSCRIPTION_OWNER :
2023-12-03 22:41:15 +13:00
proceed = True
# Undo the Follow
if ' @ ' in actor : # this is a remote community, so activitypub is needed
2024-01-01 11:38:24 +13:00
undo_id = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/undo/ " + gibberish ( 15 )
2023-12-03 22:41:15 +13:00
follow = {
" actor " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /u/ { current_user . user_name } " ,
" to " : [ community . ap_profile_id ] ,
" object " : community . ap_profile_id ,
" type " : " Follow " ,
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/follow/ { gibberish ( 15 ) } "
}
undo = {
' actor ' : current_user . profile_id ( ) ,
' to ' : [ community . ap_profile_id ] ,
' type ' : ' Undo ' ,
2024-01-01 11:38:24 +13:00
' id ' : undo_id ,
2023-12-03 22:41:15 +13:00
' object ' : follow
}
2023-12-22 15:34:45 +13:00
success = post_request ( community . ap_inbox_url , undo , current_user . private_key ,
2023-12-03 22:41:15 +13:00
current_user . profile_id ( ) + ' #main-key ' )
2023-12-22 15:34:45 +13:00
if not success :
2024-01-28 18:11:32 +13:00
flash ( ' There was a problem while trying to unsubscribe ' , ' error ' )
2023-12-22 15:34:45 +13:00
2023-12-03 22:41:15 +13:00
if proceed :
db . session . query ( CommunityMember ) . filter_by ( user_id = current_user . id , community_id = community . id ) . delete ( )
db . session . query ( CommunityJoinRequest ) . filter_by ( user_id = current_user . id , community_id = community . id ) . delete ( )
db . session . commit ( )
2024-01-09 20:44:08 +13:00
flash ( ' You have left ' + community . title )
2024-01-04 17:07:02 +13:00
cache . delete_memoized ( community_membership , current_user , community )
2024-01-12 12:34:08 +13:00
cache . delete_memoized ( joined_communities , current_user . id )
2023-09-05 20:25:02 +12:00
else :
# todo: community deletion
flash ( ' You need to make someone else the owner before unsubscribing. ' , ' warning ' )
# send them back where they came from
referrer = request . headers . get ( ' Referer ' , None )
if referrer is not None :
return redirect ( referrer )
else :
return redirect ( ' /c/ ' + actor )
else :
abort ( 404 )
2023-09-17 21:19:51 +12:00
2024-02-08 18:34:58 +13:00
@bp.route ( ' /<actor>/join_then_add ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@validation_required
def join_then_add ( actor ) :
community = actor_to_community ( actor )
if not current_user . subscribed ( community . id ) :
if not community . is_local ( ) :
# send ActivityPub message to remote community, asking to follow. Accept message will be sent to our shared inbox
join_request = CommunityJoinRequest ( user_id = current_user . id , community_id = community . id )
db . session . add ( join_request )
db . session . commit ( )
follow = {
" actor " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /u/ { current_user . user_name } " ,
" to " : [ community . ap_profile_id ] ,
" object " : community . ap_profile_id ,
" type " : " Follow " ,
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/follow/ { join_request . id } "
}
success = post_request ( community . ap_inbox_url , follow , current_user . private_key ,
current_user . profile_id ( ) + ' #main-key ' )
member = CommunityMember ( user_id = current_user . id , community_id = community . id )
db . session . add ( member )
db . session . commit ( )
flash ( ' You joined ' + community . title )
if not community . user_is_banned ( current_user ) :
return redirect ( url_for ( ' community.add_post ' , actor = community . link ( ) ) )
else :
abort ( 401 )
2023-09-17 21:19:51 +12:00
@bp.route ( ' /<actor>/submit ' , methods = [ ' GET ' , ' POST ' ] )
2023-10-02 22:16:44 +13:00
@login_required
2023-10-23 13:03:35 +13:00
@validation_required
2023-09-17 21:19:51 +12:00
def add_post ( actor ) :
community = actor_to_community ( actor )
2023-11-30 20:57:51 +13:00
form = CreatePostForm ( )
2023-12-31 12:09:20 +13:00
if g . site . enable_nsfl is False :
2023-09-17 21:19:51 +12:00
form . nsfl . render_kw = { ' disabled ' : True }
2023-12-09 22:14:16 +13:00
if community . nsfw :
form . nsfw . data = True
form . nsfw . render_kw = { ' disabled ' : True }
if community . nsfl :
form . nsfl . data = True
form . nsfw . render_kw = { ' disabled ' : True }
2024-03-20 20:41:45 +13:00
if not ( community . is_moderator ( ) or community . is_owner ( ) or current_user . is_admin ( ) ) :
form . sticky . render_kw = { ' disabled ' : True }
2023-09-17 21:19:51 +12:00
form . communities . choices = [ ( c . id , c . display_name ( ) ) for c in current_user . communities ( ) ]
2024-02-24 11:07:06 +13:00
if not can_create_post ( current_user , community ) :
2024-01-02 19:41:00 +13:00
abort ( 401 )
2023-09-17 21:19:51 +12:00
if form . validate_on_submit ( ) :
2023-12-26 21:39:52 +13:00
community = Community . query . get_or_404 ( form . communities . data )
2024-02-24 11:07:06 +13:00
if not can_create_post ( current_user , community ) :
2024-01-02 19:41:00 +13:00
abort ( 401 )
2023-12-17 00:12:49 +13:00
post = Post ( user_id = current_user . id , community_id = form . communities . data , instance_id = 1 )
2023-11-30 20:57:51 +13:00
save_post ( form , post )
2023-10-02 22:16:44 +13:00
community . post_count + = 1
2023-12-26 21:39:52 +13:00
community . last_active = g . site . last_active = utcnow ( )
2023-09-17 21:19:51 +12:00
db . session . commit ( )
2023-12-09 22:14:16 +13:00
post . ap_id = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /post/ { post . id } "
db . session . commit ( )
2024-03-24 12:00:16 +13:00
if post . image_id and post . image . file_path is None :
make_image_sizes ( post . image_id , 150 , 512 , ' posts ' ) # the 512 sized image is for masonry view
2023-09-17 21:19:51 +12:00
2024-01-07 12:47:06 +13:00
notify_about_post ( post )
2024-01-27 12:22:35 +13:00
if not community . local_only :
page = {
' type ' : ' Page ' ,
' id ' : post . ap_id ,
' attributedTo ' : current_user . ap_profile_id ,
' to ' : [
community . ap_profile_id ,
' https://www.w3.org/ns/activitystreams#Public '
] ,
' name ' : post . title ,
' cc ' : [ ] ,
' content ' : post . body_html if post . body_html else ' ' ,
' mediaType ' : ' text/html ' ,
' source ' : {
' content ' : post . body if post . body else ' ' ,
' mediaType ' : ' text/markdown '
} ,
' attachment ' : [ ] ,
' commentsEnabled ' : post . comments_enabled ,
' sensitive ' : post . nsfw ,
' nsfl ' : post . nsfl ,
2024-03-20 20:41:45 +13:00
' stickied ' : post . sticky ,
2024-01-27 12:22:35 +13:00
' published ' : ap_datetime ( utcnow ( ) ) ,
' audience ' : community . ap_profile_id
}
create = {
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/create/ { gibberish ( 15 ) } " ,
" actor " : current_user . ap_profile_id ,
2023-12-08 17:13:38 +13:00
" to " : [
" https://www.w3.org/ns/activitystreams#Public "
] ,
" cc " : [
2024-01-27 12:22:35 +13:00
community . ap_profile_id
2023-12-08 17:13:38 +13:00
] ,
2024-01-27 12:22:35 +13:00
" type " : " Create " ,
" audience " : community . ap_profile_id ,
" object " : page ,
' @context ' : default_context ( )
2023-12-08 17:13:38 +13:00
}
2024-01-27 12:22:35 +13:00
if post . type == POST_TYPE_LINK :
page [ ' attachment ' ] = [ { ' href ' : post . url , ' type ' : ' Link ' } ]
elif post . image_id :
if post . image . file_path :
image_url = post . image . file_path . replace ( ' app/static/ ' , f " https:// { current_app . config [ ' SERVER_NAME ' ] } /static/ " )
elif post . image . thumbnail_path :
image_url = post . image . thumbnail_path . replace ( ' app/static/ ' , f " https:// { current_app . config [ ' SERVER_NAME ' ] } /static/ " )
else :
image_url = post . image . source_url
# NB image is a dict while attachment is a list of dicts (usually just one dict in the list)
page [ ' image ' ] = { ' type ' : ' Image ' , ' url ' : image_url }
if post . type == POST_TYPE_IMAGE :
page [ ' attachment ' ] = [ { ' type ' : ' Link ' , ' href ' : post . image . source_url } ] # source_url is always a https link, no need for .replace() as done above
if not community . is_local ( ) : # this is a remote community - send the post to the instance that hosts it
success = post_request ( community . ap_inbox_url , create , current_user . private_key ,
current_user . ap_profile_id + ' #main-key ' )
if success :
flash ( _ ( ' Your post to %(name)s has been made. ' , name = community . title ) )
else :
flash ( ' There was a problem making your post to ' + community . title )
else : # local community - send (announce) post out to followers
announce = {
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/announce/ { gibberish ( 15 ) } " ,
" type " : ' Announce ' ,
" to " : [
" https://www.w3.org/ns/activitystreams#Public "
] ,
" actor " : community . ap_profile_id ,
" cc " : [
community . ap_followers_url
] ,
' @context ' : default_context ( ) ,
' object ' : create
}
2023-12-26 21:39:52 +13:00
2024-01-27 12:22:35 +13:00
sent_to = 0
for instance in community . following_instances ( ) :
if instance . inbox and not current_user . has_blocked_instance ( instance . id ) and not instance_banned ( instance . domain ) :
send_to_remote_instance ( instance . id , community . id , announce )
sent_to + = 1
if sent_to :
flash ( _ ( ' Your post to %(name)s has been made. ' , name = community . title ) )
else :
flash ( _ ( ' Your post to %(name)s has been made. ' , name = community . title ) )
2023-11-30 20:57:51 +13:00
2023-09-17 21:19:51 +12:00
return redirect ( f " /c/ { community . link ( ) } " )
else :
2024-01-25 20:16:08 +13:00
# when request.form has some data in it, it means form validation failed. Set the post_type so the correct tab is shown. See setupPostTypeTabs() in scripts.js
if request . form . get ( ' post_type ' , None ) :
form . post_type . data = request . form . get ( ' post_type ' )
2023-09-17 21:19:51 +12:00
form . communities . data = community . id
2023-11-30 20:57:51 +13:00
form . notify_author . data = True
2023-09-17 21:19:51 +12:00
return render_template ( ' community/add_post.html ' , title = _ ( ' Add post to community ' ) , form = form , community = community ,
2024-02-26 21:26:19 +13:00
markdown_editor = current_user . markdown_editor , low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 ' ,
2024-01-19 15:08:39 +13:00
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-01-25 20:16:08 +13:00
joined_communities = joined_communities ( current_user . id ) ,
2024-01-19 15:19:31 +13:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ]
2024-01-12 12:34:08 +13:00
)
2023-11-30 20:57:51 +13:00
2023-12-13 21:04:11 +13:00
@bp.route ( ' /community/<int:community_id>/report ' , methods = [ ' GET ' , ' POST ' ] )
2023-12-26 21:39:52 +13:00
@login_required
2023-12-13 21:04:11 +13:00
def community_report ( community_id : int ) :
community = Community . query . get_or_404 ( community_id )
form = ReportCommunityForm ( )
if form . validate_on_submit ( ) :
report = Report ( reasons = form . reasons_to_string ( form . reasons . data ) , description = form . description . data ,
2024-03-26 22:18:05 +13:00
type = 1 , reporter_id = current_user . id , suspect_community_id = community . id , source_instance_id = 1 )
2023-12-13 21:04:11 +13:00
db . session . add ( report )
# Notify admin
# todo: find all instance admin(s). for now just load User.id == 1
admins = [ User . query . get_or_404 ( 1 ) ]
for admin in admins :
2024-01-06 11:01:44 +13:00
notification = Notification ( user_id = admin . id , title = _ ( ' A community has been reported ' ) ,
2023-12-13 21:04:11 +13:00
url = community . local_url ( ) ,
author_id = current_user . id )
db . session . add ( notification )
2024-01-06 11:01:44 +13:00
admin . unread_notifications + = 1
2023-12-13 21:04:11 +13:00
db . session . commit ( )
# todo: federate report to originating instance
if not community . is_local ( ) and form . report_remote . data :
. . .
2023-11-30 20:57:51 +13:00
2023-12-13 21:04:11 +13:00
flash ( _ ( ' Community has been reported, thank you! ' ) )
return redirect ( community . local_url ( ) )
return render_template ( ' community/community_report.html ' , title = _ ( ' Report community ' ) , form = form , community = community )
2024-03-13 16:40:20 +13:00
@bp.route ( ' /community/<int:community_id>/edit ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_edit ( community_id : int ) :
from app . admin . util import topics_for_form
community = Community . query . get_or_404 ( community_id )
if community . is_owner ( ) or current_user . is_admin ( ) :
form = EditCommunityForm ( )
form . topic . choices = topics_for_form ( 0 )
if form . validate_on_submit ( ) :
community . title = form . title . data
community . description = form . description . data
community . description_html = markdown_to_html ( form . description . data )
community . rules = form . rules . data
community . rules_html = markdown_to_html ( form . rules . data )
community . nsfw = form . nsfw . data
community . local_only = form . local_only . data
community . restricted_to_mods = form . restricted_to_mods . data
community . new_mods_wanted = form . new_mods_wanted . data
community . topic_id = form . topic . data if form . topic . data != 0 else None
community . default_layout = form . default_layout . data
icon_file = request . files [ ' icon_file ' ]
if icon_file and icon_file . filename != ' ' :
if community . icon_id :
community . icon . delete_from_disk ( )
file = save_icon_file ( icon_file )
if file :
community . icon = file
banner_file = request . files [ ' banner_file ' ]
if banner_file and banner_file . filename != ' ' :
if community . image_id :
community . image . delete_from_disk ( )
file = save_banner_file ( banner_file )
if file :
community . image = file
db . session . commit ( )
2024-03-26 17:16:51 +01:00
if community . topic :
community . topic . num_communities = community . topic . communities . count ( )
2024-03-13 16:40:20 +13:00
db . session . commit ( )
flash ( _ ( ' Saved ' ) )
return redirect ( url_for ( ' activitypub.community_profile ' , actor = community . ap_id if community . ap_id is not None else community . name ) )
else :
form . title . data = community . title
form . description . data = community . description
form . rules . data = community . rules
form . nsfw . data = community . nsfw
form . local_only . data = community . local_only
form . new_mods_wanted . data = community . new_mods_wanted
form . restricted_to_mods . data = community . restricted_to_mods
form . topic . data = community . topic_id if community . topic_id else None
form . default_layout . data = community . default_layout
return render_template ( ' community/community_edit.html ' , title = _ ( ' Edit community ' ) , form = form ,
2024-03-21 21:55:05 +01:00
current_app = current_app , current = " edit_settings " ,
2024-03-13 16:40:20 +13:00
community = community , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) )
else :
abort ( 401 )
2023-12-21 22:14:43 +13:00
@bp.route ( ' /community/<int:community_id>/delete ' , methods = [ ' GET ' , ' POST ' ] )
2023-12-26 21:39:52 +13:00
@login_required
2023-12-21 22:14:43 +13:00
def community_delete ( community_id : int ) :
community = Community . query . get_or_404 ( community_id )
if community . is_owner ( ) or current_user . is_admin ( ) :
form = DeleteCommunityForm ( )
if form . validate_on_submit ( ) :
2024-01-01 11:38:24 +13:00
if community . is_local ( ) :
community . banned = True
# todo: federate deletion out to all instances. At end of federation process, delete_dependencies() and delete community
else :
community . delete_dependencies ( )
db . session . delete ( community )
2023-12-21 22:14:43 +13:00
db . session . commit ( )
flash ( _ ( ' Community deleted ' ) )
return redirect ( ' /communities ' )
return render_template ( ' community/community_delete.html ' , title = _ ( ' Delete community ' ) , form = form ,
2024-01-12 12:34:08 +13:00
community = community , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) )
2023-12-21 22:14:43 +13:00
else :
abort ( 401 )
2024-03-13 16:40:20 +13:00
@bp.route ( ' /community/<int:community_id>/moderators ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_mod_list ( community_id : int ) :
community = Community . query . get_or_404 ( community_id )
if community . is_owner ( ) or current_user . is_admin ( ) :
moderators = User . query . filter ( User . banned == False ) . join ( CommunityMember , CommunityMember . user_id == User . id ) . \
filter ( CommunityMember . community_id == community_id , or_ ( CommunityMember . is_moderator == True , CommunityMember . is_owner == True ) ) . all ( )
return render_template ( ' community/community_mod_list.html ' , title = _ ( ' Moderators for %(community)s ' , community = community . display_name ( ) ) ,
2024-03-21 21:55:05 +01:00
moderators = moderators , community = community , current = " moderators " ,
2024-03-13 16:40:20 +13:00
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) )
)
@bp.route ( ' /community/<int:community_id>/moderators/add ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_add_moderator ( community_id : int ) :
community = Community . query . get_or_404 ( community_id )
if community . is_owner ( ) or current_user . is_admin ( ) :
form = AddModeratorForm ( )
if form . validate_on_submit ( ) :
new_moderator = search_for_user ( form . user_name . data )
if new_moderator :
existing_member = CommunityMember . query . filter ( CommunityMember . user_id == new_moderator . id , CommunityMember . community_id == community_id ) . first ( )
if existing_member :
existing_member . is_moderator = True
else :
new_member = CommunityMember ( community_id = community_id , user_id = new_moderator . id , is_moderator = True )
db . session . add ( new_member )
db . session . commit ( )
flash ( _ ( ' Moderator added ' ) )
# Notify new mod
if new_moderator . is_local ( ) :
notify = Notification ( title = _ ( ' You are now a moderator of %(name)s ' , name = community . display_name ( ) ) ,
url = ' /c/ ' + community . name , user_id = new_moderator . id ,
author_id = current_user . id )
new_moderator . unread_notifications + = 1
db . session . add ( notify )
db . session . commit ( )
else :
# for remote users, send a chat message to let them know
existing_conversation = Conversation . find_existing_conversation ( recipient = new_moderator ,
sender = current_user )
if not existing_conversation :
existing_conversation = Conversation ( user_id = current_user . id )
existing_conversation . members . append ( new_moderator )
existing_conversation . members . append ( current_user )
db . session . add ( existing_conversation )
db . session . commit ( )
server = current_app . config [ ' SERVER_NAME ' ]
send_message ( f " Hi there. I ' ve added you as a moderator to the community ! { community . name } @ { server } . " , existing_conversation . id )
# Flush cache
cache . delete_memoized ( moderating_communities , new_moderator . id )
cache . delete_memoized ( joined_communities , new_moderator . id )
cache . delete_memoized ( community_moderators , community_id )
return redirect ( url_for ( ' community.community_mod_list ' , community_id = community . id ) )
else :
flash ( _ ( ' Account not found ' ) , ' warning ' )
return render_template ( ' community/community_add_moderator.html ' , title = _ ( ' Add moderator to %(community)s ' , community = community . display_name ( ) ) ,
community = community , form = form ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) )
)
@bp.route ( ' /community/<int:community_id>/moderators/remove/<int:user_id> ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_remove_moderator ( community_id : int , user_id : int ) :
community = Community . query . get_or_404 ( community_id )
if community . is_owner ( ) or current_user . is_admin ( ) :
existing_member = CommunityMember . query . filter ( CommunityMember . user_id == user_id ,
CommunityMember . community_id == community_id ) . first ( )
if existing_member :
existing_member . is_moderator = False
db . session . commit ( )
flash ( _ ( ' Moderator removed ' ) )
# Flush cache
cache . delete_memoized ( moderating_communities , user_id )
cache . delete_memoized ( joined_communities , user_id )
cache . delete_memoized ( community_moderators , community_id )
return redirect ( url_for ( ' community.community_mod_list ' , community_id = community . id ) )
2023-12-13 21:04:11 +13:00
@bp.route ( ' /community/<int:community_id>/block_instance ' , methods = [ ' GET ' , ' POST ' ] )
2023-12-26 21:39:52 +13:00
@login_required
2023-12-13 21:04:11 +13:00
def community_block_instance ( community_id : int ) :
community = Community . query . get_or_404 ( community_id )
existing = InstanceBlock . query . filter_by ( user_id = current_user . id , instance_id = community . instance_id ) . first ( )
if not existing :
db . session . add ( InstanceBlock ( user_id = current_user . id , instance_id = community . instance_id ) )
db . session . commit ( )
flash ( _ ( ' Content from %(name)s will be hidden. ' , name = community . instance . domain ) )
return redirect ( community . local_url ( ) )
2024-01-07 12:47:06 +13:00
2024-03-15 14:24:45 +13:00
@bp.route ( ' /community/<int:community_id>/<int:user_id>/ban_user_community ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_ban_user ( community_id : int , user_id : int ) :
community = Community . query . get_or_404 ( community_id )
user = User . query . get_or_404 ( user_id )
existing = CommunityBan . query . filter_by ( community_id = community . id , user_id = user . id ) . first ( )
form = BanUserCommunityForm ( )
if form . validate_on_submit ( ) :
2024-03-21 21:19:50 +13:00
# Both CommunityBan and CommunityMember need to be updated. CommunityBan is under the control of moderators while
# CommunityMember can be cleared by the user by leaving the group and rejoining. CommunityMember.is_banned stops
# posts from the community from showing up in the banned person's home feed.
2024-03-15 14:24:45 +13:00
if not existing :
new_ban = CommunityBan ( community_id = community_id , user_id = user . id , banned_by = current_user . id ,
reason = form . reason . data )
if form . ban_until . data is not None and form . ban_until . data < utcnow ( ) . date ( ) :
new_ban . ban_until = form . ban_until . data
db . session . add ( new_ban )
db . session . commit ( )
2024-03-21 21:19:50 +13:00
community_membership_record = CommunityMember . query . filter_by ( community_id = community . id , user_id = user . id ) . first ( )
if community_membership_record :
community_membership_record . is_banned = True
db . session . commit ( )
flash ( _ ( ' %(name)s has been banned. ' , name = user . display_name ( ) ) )
2024-03-15 14:24:45 +13:00
if form . delete_posts . data :
posts = Post . query . filter ( Post . user_id == user . id , Post . community_id == community . id ) . all ( )
for post in posts :
delete_post_from_community ( post . id )
if posts :
flash ( _ ( ' Posts by %(name)s have been deleted. ' , name = user . display_name ( ) ) )
if form . delete_post_replies . data :
post_replies = PostReply . query . filter ( PostReply . user_id == user . id , Post . community_id == community . id ) . all ( )
for post_reply in post_replies :
delete_post_reply_from_community ( post_reply . id )
if post_replies :
flash ( _ ( ' Comments by %(name)s have been deleted. ' , name = user . display_name ( ) ) )
# todo: federate ban to post author instance
# notify banned person
if user . is_local ( ) :
2024-03-21 21:19:50 +13:00
cache . delete_memoized ( communities_banned_from , user . id )
cache . delete_memoized ( joined_communities , user . id )
cache . delete_memoized ( moderating_communities , user . id )
2024-03-15 14:24:45 +13:00
notify = Notification ( title = shorten_string ( ' You have been banned from ' + community . title ) ,
2024-03-21 21:19:50 +13:00
url = f ' /notifications ' , user_id = user . id ,
2024-03-15 14:24:45 +13:00
author_id = 1 )
db . session . add ( notify )
user . unread_notifications + = 1
db . session . commit ( )
else :
. . .
# todo: send chatmessage to remote user and federate it
return redirect ( community . local_url ( ) )
else :
return render_template ( ' community/community_ban_user.html ' , title = _ ( ' Ban from community ' ) , form = form , community = community ,
user = user ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ]
)
2024-03-21 21:19:50 +13:00
@bp.route ( ' /community/<int:community_id>/<int:user_id>/unban_user_community ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_unban_user ( community_id : int , user_id : int ) :
community = Community . query . get_or_404 ( community_id )
user = User . query . get_or_404 ( user_id )
existing_ban = CommunityBan . query . filter_by ( community_id = community . id , user_id = user . id ) . first ( )
if existing_ban :
db . session . delete ( existing_ban )
db . session . commit ( )
community_membership_record = CommunityMember . query . filter_by ( community_id = community . id , user_id = user . id ) . first ( )
if community_membership_record :
community_membership_record . is_banned = False
db . session . commit ( )
flash ( _ ( ' %(name)s has been unbanned. ' , name = user . display_name ( ) ) )
# todo: federate ban to post author instance
# notify banned person
if user . is_local ( ) :
cache . delete_memoized ( communities_banned_from , user . id )
cache . delete_memoized ( joined_communities , user . id )
cache . delete_memoized ( moderating_communities , user . id )
notify = Notification ( title = shorten_string ( ' You have been un-banned from ' + community . title ) ,
url = f ' /notifications ' , user_id = user . id ,
author_id = 1 )
db . session . add ( notify )
user . unread_notifications + = 1
db . session . commit ( )
else :
. . .
# todo: send chatmessage to remote user and federate it
return redirect ( url_for ( ' community.community_moderate_banned ' , actor = community . link ( ) ) )
2024-03-15 14:24:45 +13:00
2024-01-07 12:47:06 +13:00
@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 )
2024-03-18 21:05:13 +13:00
@bp.route ( ' /<actor>/moderate ' , methods = [ ' GET ' ] )
@login_required
def community_moderate ( actor ) :
community = actor_to_community ( actor )
if community is not None :
if community . is_moderator ( ) or current_user . is_admin ( ) :
page = request . args . get ( ' page ' , 1 , type = int )
search = request . args . get ( ' search ' , ' ' )
local_remote = request . args . get ( ' local_remote ' , ' ' )
reports = Report . query . filter_by ( status = 0 , in_community_id = community . id )
if local_remote == ' local ' :
2024-03-26 22:18:05 +13:00
reports = reports . filter ( Report . source_instance_id == 1 )
2024-03-18 21:05:13 +13:00
if local_remote == ' remote ' :
2024-03-26 22:18:05 +13:00
reports = reports . filter ( Report . source_instance_id != 1 )
reports = reports . filter ( Report . status > = 0 ) . order_by ( desc ( Report . created_at ) ) . paginate ( page = page , per_page = 1000 , error_out = False )
2024-03-18 21:05:13 +13:00
2024-03-26 22:18:05 +13:00
next_url = url_for ( ' community.community_moderate ' , page = reports . next_num ) if reports . has_next else None
prev_url = url_for ( ' community.community_moderate ' , page = reports . prev_num ) if reports . has_prev and page != 1 else None
2024-03-18 21:05:13 +13:00
return render_template ( ' community/community_moderate.html ' , title = _ ( ' Moderation of %(community)s ' , community = community . display_name ( ) ) ,
community = community , reports = reports , current = ' reports ' ,
next_url = next_url , prev_url = prev_url ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ]
)
else :
abort ( 401 )
else :
abort ( 404 )
@bp.route ( ' /<actor>/moderate/banned ' , methods = [ ' GET ' ] )
@login_required
def community_moderate_banned ( actor ) :
community = actor_to_community ( actor )
if community is not None :
if community . is_moderator ( ) or current_user . is_admin ( ) :
banned_people = User . query . join ( CommunityBan , CommunityBan . user_id == User . id ) . filter ( CommunityBan . community_id == community . id ) . all ( )
return render_template ( ' community/community_moderate_banned.html ' ,
title = _ ( ' People banned from of %(community)s ' , community = community . display_name ( ) ) ,
community = community , banned_people = banned_people , current = ' banned ' ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ]
)
else :
abort ( 401 )
else :
2024-03-24 00:15:10 +00:00
abort ( 404 )