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-01-07 13:35:36 +13:00
from app . activitypub . util import default_context , notify_about_post
2023-12-21 22:14:43 +13:00
from app . community . forms import SearchRemoteCommunity , AddLocalCommunity , CreatePostForm , ReportCommunityForm , \
DeleteCommunityForm
2023-11-30 06:36:08 +13:00
from app . community . util import search_for_community , community_url_exists , actor_to_community , \
2024-01-07 13:35:36 +13:00
opengraph_parse , url_to_thumbnail_file , save_post , save_icon_file , save_banner_file , send_to_remote_instance
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-01-01 11:38:24 +13:00
File , PostVote , utcnow , Report , Notification , InstanceBlock , ActivityPubLog
2023-08-29 22:01:06 +12:00
from app . community import bp
2023-10-23 20:18:46 +13:00
from app . utils import get_setting , render_template , allowlist_html , markdown_to_html , validation_required , \
2023-12-10 15:10:09 +13:00
shorten_string , markdown_to_text , domain_from_url , validate_image , gibberish , community_membership , ap_datetime , \
2024-01-12 12:34:08 +13:00
request_etag_matches , return_304 , instance_banned , can_create , can_upvote , can_downvote , user_filters_posts , \
joined_communities , moderating_communities
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 ' )
2023-08-29 22:01:06 +12:00
form = AddLocalCommunity ( )
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 ) ,
rules_html = markdown_to_html ( form . rules . data ) ,
2023-12-13 21:04:11 +13:00
ap_profile_id = ' 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 ' ,
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 ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) )
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 ' )
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-01-21 15:44:13 +13:00
low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 '
post_layout = request . args . get ( ' layout ' , community . default_layout if not low_bandwidth else ' ' )
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
current_etag = f " { community . id } { sort } _ { hash ( community . last_active ) } "
if current_user . is_anonymous and request_etag_matches ( current_etag ) :
return return_304 ( current_etag )
2023-09-17 21:19:51 +12:00
mods = community . moderators ( )
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 ( )
2023-10-16 21:38:36 +13:00
if current_user . is_anonymous or current_user . ignore_bots :
2024-01-03 20:14:39 +13:00
posts = community . posts . filter ( Post . from_bot == False )
2024-01-11 20:39:22 +13:00
content_filters = { }
2023-10-07 21:32:19 +13:00
else :
2024-01-11 20:39:22 +13:00
content_filters = user_filters_posts ( current_user . id )
2024-01-03 20:14:39 +13:00
posts = community . posts
if sort == ' ' or sort == ' hot ' :
posts = posts . order_by ( desc ( Post . ranking ) )
elif sort == ' top ' :
posts = posts . filter ( Post . posted_at > utcnow ( ) - timedelta ( days = 7 ) ) . order_by ( desc ( Post . score ) )
elif sort == ' new ' :
posts = posts . order_by ( desc ( Post . posted_at ) )
2024-01-15 18:26:22 +13:00
elif sort == ' active ' :
posts = posts . 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-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 )
else :
related_communities = [ ]
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-03 20:14:39 +13:00
page = posts . next_num , sort = sort ) 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-03 20:14:39 +13:00
page = posts . prev_num , sort = sort ) if posts . has_prev and page != 1 else None
2023-12-15 17:35:11 +13:00
2023-09-05 20:25:02 +12:00
return render_template ( ' community/community.html ' , community = community , title = community . title ,
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-15 18:32:58 +13:00
etag = f " { community . id } { sort } _ { 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-01-11 20:39:22 +13:00
rss_feed = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /community/ { community . link ( ) } /feed " , rss_feed_name = f " { community . title } posts 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-21 15:44:13 +13:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] , post_layout = post_layout )
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 } " )
fg . title ( community . title )
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 } " )
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
}
2024-01-01 11:38:24 +13:00
activity = ActivityPubLog ( direction = ' out ' , activity_id = undo_id , activity_type = ' Undo ' ,
activity_json = json . dumps ( undo ) , result = ' processing ' )
db . session . add ( activity )
db . session . commit ( )
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 ' )
2024-01-01 11:38:24 +13:00
activity . result = ' success '
db . session . commit ( )
2023-12-22 15:34:45 +13:00
if not success :
2024-01-05 14:09:46 +13:00
flash ( ' There was a problem while trying to join ' , ' 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
@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_nsfw is False :
2023-09-17 21:19:51 +12:00
form . nsfw . render_kw = { ' disabled ' : True }
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 }
2023-12-08 17:13:38 +13:00
images_disabled = ' disabled ' if not get_setting ( ' allow_local_image_posts ' , True ) else ' ' # bug: this will disable posting of images to *remote* hosts too
2023-09-17 21:19:51 +12:00
form . communities . choices = [ ( c . id , c . display_name ( ) ) for c in current_user . communities ( ) ]
2024-01-02 19:41:00 +13:00
if not can_create ( current_user , community ) :
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-01-02 19:41:00 +13:00
if not can_create ( current_user , community ) :
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 ( )
2023-09-17 21:19:51 +12:00
2024-01-07 12:47:06 +13:00
notify_about_post ( post )
2023-12-26 21:39:52 +13:00
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 ' : [ ] ,
2024-01-07 18:30:27 +13:00
' content ' : post . body_html if post . body_html else ' ' ,
2023-12-26 21:39:52 +13:00
' mediaType ' : ' text/html ' ,
' source ' : {
2024-01-07 18:20:04 +13:00
' content ' : post . body if post . body else ' ' ,
2023-12-26 21:39:52 +13:00
' mediaType ' : ' text/markdown '
} ,
' attachment ' : [ ] ,
' commentsEnabled ' : post . comments_enabled ,
' sensitive ' : post . nsfw ,
' nsfl ' : post . nsfl ,
' 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 ,
" to " : [
" https://www.w3.org/ns/activitystreams#Public "
] ,
" cc " : [
community . ap_profile_id
] ,
" type " : " Create " ,
" audience " : community . ap_profile_id ,
" object " : page ,
' @context ' : default_context ( )
}
2024-01-02 16:07:41 +13:00
if post . type == POST_TYPE_LINK :
2024-01-07 18:02:52 +13:00
page [ ' attachment ' ] = [ { ' href ' : post . url , ' type ' : ' Link ' } ]
2024-01-08 22:48:03 +13:00
elif post . image_id :
2024-01-08 21:33:23 +13:00
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
2024-01-07 19:12:18 +13:00
# NB image is a dict while attachment is a list of dicts (usually just one dict in the list)
2024-01-08 21:33:23 +13:00
page [ ' image ' ] = { ' type ' : ' Image ' , ' url ' : image_url }
2024-01-09 20:44:08 +13:00
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
2023-12-09 22:14:16 +13:00
if not community . is_local ( ) : # this is a remote community - send the post to the instance that hosts it
2023-12-26 21:39:52 +13:00
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 )
2024-01-03 16:29:58 +13:00
else : # local community - send (announce) post out to followers
2023-12-26 21:39:52 +13:00
announce = {
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/announce/ { gibberish ( 15 ) } " ,
" type " : ' Announce ' ,
2023-12-08 17:13:38 +13:00
" to " : [
" https://www.w3.org/ns/activitystreams#Public "
] ,
2023-12-26 21:39:52 +13:00
" actor " : community . ap_profile_id ,
2023-12-08 17:13:38 +13:00
" cc " : [
2023-12-26 21:39:52 +13:00
community . ap_followers_url
2023-12-08 17:13:38 +13:00
] ,
2023-12-26 21:39:52 +13:00
' @context ' : default_context ( ) ,
' object ' : create
2023-12-08 17:13:38 +13:00
}
2023-12-26 21:39:52 +13:00
sent_to = 0
for instance in community . following_instances ( ) :
2024-01-03 16:29:58 +13:00
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 )
2023-12-26 21:39:52 +13:00
sent_to + = 1
if sent_to :
flash ( _ ( ' Your post to %(name)s has been made. ' , name = community . title ) )
2023-12-22 15:34:45 +13:00
else :
2023-12-26 21:39:52 +13:00
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 :
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-01-12 12:34:08 +13:00
images_disabled = images_disabled , markdown_editor = True , 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-19 15:19:31 +13:00
joined_communities = joined_communities ( current_user . id ) ,
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 ,
type = 1 , reporter_id = current_user . id , suspect_community_id = community . id )
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 )
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 )
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
@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 )