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-08-15 19:39:33 +12:00
import flask
from flask import redirect , url_for , flash , request , make_response , session , Markup , current_app , abort , g , json , \
jsonify
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 _
2024-04-03 20:53:50 +13:00
from slugify import slugify
2024-03-27 20:20:08 +13:00
from sqlalchemy import or_ , desc , text
2023-09-17 21:19:51 +12:00
2024-09-13 11:10:10 -04:00
from app import db , constants , cache , celery
2024-07-24 20:51:50 +08:00
from app . activitypub . signature import RsaKeys , post_request , default_context , post_request_in_background
2024-09-04 13:55:23 -04:00
from app . activitypub . util import notify_about_post , make_image_sizes , resolve_remote_post , extract_domain_and_actor
2024-03-13 16:40:20 +13:00
from app . chat . util import send_message
2024-04-16 20:59:58 +12:00
from app . community . forms import SearchRemoteCommunity , CreateDiscussionForm , CreateImageForm , CreateLinkForm , \
ReportCommunityForm , \
2024-03-27 20:20:08 +13:00
DeleteCommunityForm , AddCommunityForm , EditCommunityForm , AddModeratorForm , BanUserCommunityForm , \
2024-07-17 22:11:31 +08:00
EscalateReportForm , ResolveReportForm , CreateVideoForm , CreatePollForm , RetrieveRemotePost , \
EditCommunityWikiPageForm
2024-04-30 21:29:49 +12:00
from app . community . util import search_for_community , actor_to_community , \
2024-06-22 14:18:26 +08:00
save_post , save_icon_file , save_banner_file , send_to_remote_instance , \
2024-07-14 14:01:22 +08:00
delete_post_from_community , delete_post_reply_from_community , community_in_list , find_local_users
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-04-06 14:10:23 +13:00
SUBSCRIPTION_PENDING , SUBSCRIPTION_MODERATOR , REPORT_STATE_NEW , REPORT_STATE_ESCALATED , REPORT_STATE_RESOLVED , \
2024-05-30 01:45:57 +01:00
REPORT_STATE_DISCARDED , POST_TYPE_VIDEO , NOTIF_COMMUNITY , POST_TYPE_POLL , MICROBLOG_APPS
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-04-22 20:53:03 +12:00
File , PostVote , utcnow , Report , Notification , InstanceBlock , ActivityPubLog , Topic , Conversation , PostReply , \
2024-07-17 22:11:31 +08:00
NotificationSubscription , UserFollower , Instance , Language , Poll , PollChoice , ModLog , CommunityWikiPage , \
CommunityWikiPageRevision
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-04-14 08:57:46 +12:00
community_moderators , communities_banned_from , show_ban_message , recently_upvoted_posts , recently_downvoted_posts , \
2024-08-12 20:54:10 +12:00
blocked_users , post_ranking , languages_for_form , english_language_id , menu_topics , add_to_modlog , \
2024-09-22 13:42:02 +00:00
blocked_communities , remove_tracking_from_link , piefed_markdown_to_lemmy_markdown
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
2024-06-03 15:40:10 +01:00
from copy import copy
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-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
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 }
2024-05-08 21:07:22 +12:00
form . languages . choices = languages_for_form ( )
2024-04-30 21:29:49 +12:00
if form . validate_on_submit ( ) :
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 : ]
2024-04-30 21:29:49 +12:00
form . url . data = slugify ( form . url . data . strip ( ) , separator = ' _ ' ) . lower ( )
2023-09-03 16:30:20 +12:00
private_key , public_key = RsaKeys . generate_keypair ( )
2024-09-22 13:42:02 +00:00
community = Community ( title = form . community_name . data , name = form . url . data , description = piefed_markdown_to_lemmy_markdown ( 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 )
2024-05-08 21:07:22 +12:00
# Languages of the community
for language_choice in form . languages . data :
community . languages . append ( Language . query . get ( language_choice ) )
# Always include the undetermined language, so posts with no language will be accepted
community . languages . append ( Language . query . filter ( Language . code == ' und ' ) . first ( ) )
2023-09-03 16:30:20 +12:00
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-05-30 21:54:25 +12:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
current_app = current_app ,
menu_topics = menu_topics ( ) ,
site = g . site )
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 ( ) :
2024-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
2023-08-29 22:01:06 +12:00
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 :
2024-08-30 17:43:22 +12:00
try :
new_community = search_for_community ( address )
except Exception as e :
if ' is blocked. ' in str ( e ) :
flash ( _ ( ' Sorry, that instance is blocked, check https://gui.fediseer.com/ for reasons. ' ) , ' warning ' )
2023-08-29 22:01:06 +12:00
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 )
2024-09-04 13:55:23 -04:00
elif address . startswith ( ' https:// ' ) :
server , community = extract_domain_and_actor ( address )
new_community = search_for_community ( ' ! ' + community + ' @ ' + server )
2023-08-29 22:01:06 +12:00
else :
2023-09-08 20:04:01 +12:00
message = Markup (
2024-09-04 13:55:23 -04:00
' Accepted address formats: !community@server.name or https://server.name/ { c|m}/community. 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 ( ) ) ,
2024-05-30 21:54:25 +12:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) ,
site = g . site )
2023-08-29 22:01:06 +12:00
2024-05-27 23:51:14 +01:00
@bp.route ( ' /retrieve_remote_post/<int:community_id> ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def retrieve_remote_post ( community_id : int ) :
if current_user . banned :
return show_ban_message ( )
form = RetrieveRemotePost ( )
new_post = None
community = Community . query . get_or_404 ( community_id )
if form . validate_on_submit ( ) :
address = form . address . data . strip ( )
2024-05-28 15:40:09 +12:00
new_post = resolve_remote_post ( address , community_id )
2024-05-27 23:51:14 +01:00
if new_post is None :
flash ( _ ( ' Post not found. ' ) , ' warning ' )
return render_template ( ' community/retrieve_remote_post.html ' ,
title = _ ( ' Retrieve Remote Post ' ) , form = form , new_post = new_post , community = community )
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 :
2024-08-20 22:45:26 +00:00
if community . default_layout is not None and community . default_layout != ' ' :
2024-03-17 07:28:03 +00:00
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
2024-05-26 14:04:36 +12:00
if current_user . is_authenticated and community . id not in communities_banned_from ( current_user . id ) :
is_moderator = any ( mod . user_id == current_user . id for mod in mods )
is_owner = any ( mod . user_id == current_user . id and mod . is_owner == True for mod in mods )
is_admin = current_user . is_admin ( )
else :
is_moderator = False
is_owner = False
is_admin = False
2023-09-05 20:25:02 +12:00
2024-08-18 13:44:19 +12:00
# Build list of moderators and set un-moderated flag
mod_user_ids = [ mod . user_id for mod in mods ]
un_moderated = False
2023-09-05 20:25:02 +12:00
if community . private_mods :
mod_list = [ ]
2024-08-18 13:44:19 +12:00
inactive_mods = User . query . filter ( User . id . in_ ( mod_user_ids ) , User . last_seen < utcnow ( ) - timedelta ( days = 60 ) ) . all ( )
2023-09-05 20:25:02 +12:00
else :
mod_list = User . query . filter ( User . id . in_ ( mod_user_ids ) ) . all ( )
2024-08-18 13:44:19 +12:00
inactive_mods = [ ]
for mod in mod_list :
if mod . last_seen < utcnow ( ) - timedelta ( days = 60 ) :
inactive_mods . append ( mod )
if current_user . is_authenticated and ( current_user . is_admin ( ) or current_user . is_staff ( ) ) :
un_moderated = len ( mod_user_ids ) == len ( inactive_mods )
2023-09-05 20:25:02 +12:00
2024-02-01 11:59:09 +13:00
posts = community . posts
# filter out nsfw and nsfl if desired
if current_user . is_anonymous :
2024-06-02 16:45:21 +12:00
posts = posts . filter ( Post . from_bot == False , Post . nsfw == False , Post . nsfl == False , Post . deleted == False )
2024-01-11 20:39:22 +13:00
content_filters = { }
2023-10-07 21:32:19 +13:00
else :
2024-06-28 13:22:15 +02:00
if current_user . ignore_bots == 1 :
2024-02-01 11:59:09 +13:00
posts = posts . filter ( Post . from_bot == False )
2024-06-28 15:04:06 +00:00
if current_user . hide_nsfl == 1 :
2024-02-01 11:59:09 +13:00
posts = posts . filter ( Post . nsfl == False )
2024-06-28 15:04:06 +00:00
if current_user . hide_nsfw == 1 :
2024-02-01 11:59:09 +13:00
posts = posts . filter ( Post . nsfw == False )
2024-01-11 20:39:22 +13:00
content_filters = user_filters_posts ( current_user . id )
2024-06-02 16:45:21 +12:00
posts = posts . filter ( Post . deleted == False )
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-08-12 20:54:10 +12:00
community_ids = blocked_communities ( current_user . id )
if community_ids :
posts = posts . filter ( Post . community_id . not_in ( community_ids ) )
2024-02-02 17:22:32 +13:00
2024-04-14 08:57:46 +12:00
# filter blocked users
blocked_accounts = blocked_users ( current_user . id )
if blocked_accounts :
posts = posts . filter ( Post . user_id . not_in ( blocked_accounts ) )
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-04-14 07:59:24 +12:00
posts = posts . filter ( Post . posted_at > utcnow ( ) - timedelta ( days = 7 ) ) . order_by ( desc ( Post . sticky ) ) . order_by ( desc ( Post . up_votes - Post . down_votes ) )
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-04-11 14:04:57 +12:00
# Voting history
if current_user . is_authenticated :
recently_upvoted = recently_upvoted_posts ( current_user . id )
recently_downvoted = recently_downvoted_posts ( current_user . id )
else :
recently_upvoted = [ ]
recently_downvoted = [ ]
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 ,
2024-05-19 11:30:41 +12:00
og_image = og_image , POST_TYPE_IMAGE = POST_TYPE_IMAGE , POST_TYPE_LINK = POST_TYPE_LINK ,
POST_TYPE_VIDEO = POST_TYPE_VIDEO , POST_TYPE_POLL = POST_TYPE_POLL , 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-08-18 13:44:19 +12:00
next_url = next_url , prev_url = prev_url , low_bandwidth = low_bandwidth , un_moderated = un_moderated ,
2024-04-11 14:04:57 +12:00
recently_upvoted = recently_upvoted , recently_downvoted = recently_downvoted ,
2024-06-15 11:59:22 +08:00
rss_feed = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /community/ { community . link ( ) } /feed " , rss_feed_name = f " { community . title } on { g . site . name } " ,
2024-01-12 12:34:08 +13:00
content_filters = content_filters , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-05-30 21:54:25 +12:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site , sort = sort ,
2024-07-12 19:56:57 +08:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None ,
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 ' )
2024-06-02 16:45:21 +12:00
posts = community . posts . filter ( Post . from_bot == False , Post . deleted == False ) . order_by ( desc ( Post . created_at ) ) . limit ( 100 ) . all ( )
2023-12-12 18:28:49 +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
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 ) :
2024-09-13 11:34:14 -04:00
do_subscribe ( actor , current_user . id )
2024-09-13 12:49:10 -04:00
referrer = request . headers . get ( ' Referer ' , None )
if referrer is not None :
return redirect ( referrer )
else :
return redirect ( ' /c/ ' + actor )
2024-09-12 14:45:38 -04:00
2024-09-14 15:50:23 +12:00
2024-09-13 13:02:17 -04:00
# this is separated out from the subscribe route so it can be used by the
2024-09-12 14:45:38 -04:00
# admin.admin_federation.preload_form as well
2024-09-13 11:10:10 -04:00
@celery.task
2024-09-13 11:34:14 -04:00
def do_subscribe ( actor , user_id , main_user_name = True ) :
2023-09-08 20:04:01 +12:00
remote = False
2023-09-05 20:25:02 +12:00
actor = actor . strip ( )
2024-09-13 11:34:14 -04:00
user = User . query . get ( user_id )
2024-09-12 14:45:38 -04:00
pre_load_message = { }
2023-09-05 20:25:02 +12:00
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 :
2024-09-12 14:45:38 -04:00
pre_load_message [ ' community ' ] = community . ap_id
2024-09-13 11:26:00 -04:00
if community . id in communities_banned_from ( user . id ) :
2024-09-12 14:45:38 -04:00
if main_user_name :
abort ( 401 )
else :
pre_load_message [ ' user_banned ' ] = True
2024-09-13 11:26:00 -04:00
if community_membership ( user , community ) != SUBSCRIPTION_MEMBER and community_membership ( user , community ) != SUBSCRIPTION_PENDING :
banned = CommunityBan . query . filter_by ( user_id = user . id , community_id = community . id ) . first ( )
2024-01-09 20:44:08 +13:00
if banned :
2024-09-12 14:45:38 -04:00
if main_user_name :
flash ( _ ( ' You cannot join this community ' ) )
else :
pre_load_message [ ' community_banned_by_local_instance ' ] = True
2024-08-30 17:25:37 +12:00
success = True
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
2024-09-13 11:26:00 -04:00
join_request = CommunityJoinRequest ( user_id = user . id , community_id = community . id )
2023-09-08 20:04:01 +12:00
db . session . add ( join_request )
db . session . commit ( )
2024-08-30 17:25:37 +12:00
if community . instance . online ( ) :
2024-08-20 20:32:16 +00:00
follow = {
2024-09-13 11:26:00 -04:00
" actor " : user . public_url ( main_user_name = main_user_name ) ,
2024-08-20 20:32:16 +00:00
" to " : [ community . public_url ( ) ] ,
" object " : community . public_url ( ) ,
" type " : " Follow " ,
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/follow/ { join_request . id } "
}
2024-09-13 11:26:00 -04:00
success = post_request ( community . ap_inbox_url , follow , user . private_key ,
user . public_url ( main_user_name = main_user_name ) + ' #main-key ' , timeout = 10 )
2024-08-30 17:25:37 +12:00
if success is False or isinstance ( success , str ) :
if ' is not in allowlist ' in success :
2024-09-12 14:45:38 -04:00
msg_to_user = f ' { community . instance . domain } does not allow us to join their communities. '
if main_user_name :
flash ( _ ( msg_to_user ) , ' error ' )
else :
pre_load_message [ ' status ' ] = msg_to_user
2024-08-30 17:25:37 +12:00
else :
2024-09-12 14:45:38 -04:00
msg_to_user = " There was a problem while trying to communicate with remote server. If other people have already joined this community it won ' t matter. "
if main_user_name :
flash ( _ ( msg_to_user ) , ' error ' )
else :
pre_load_message [ ' status ' ] = msg_to_user
2024-01-09 20:44:08 +13:00
# for local communities, joining is instant
2024-09-13 11:26:00 -04:00
member = CommunityMember ( user_id = user . id , community_id = community . id )
2024-01-09 20:44:08 +13:00
db . session . add ( member )
db . session . commit ( )
2024-08-30 17:25:37 +12:00
if success is True :
2024-09-12 14:45:38 -04:00
if main_user_name :
flash ( ' You joined ' + community . title )
else :
pre_load_message [ ' status ' ] = ' joined '
2024-09-13 11:10:10 -04:00
else :
2024-09-13 12:49:10 -04:00
if not main_user_name :
2024-09-13 11:10:10 -04:00
pre_load_message [ ' status ' ] = ' already subscribed, or subsciption pending '
2024-09-13 11:44:37 -04:00
2024-09-13 11:26:00 -04:00
cache . delete_memoized ( community_membership , user , community )
cache . delete_memoized ( joined_communities , user . id )
2024-09-13 12:49:10 -04:00
if not main_user_name :
2024-09-12 14:45:38 -04:00
return pre_load_message
2023-09-05 20:25:02 +12:00
else :
2024-09-12 14:45:38 -04:00
if main_user_name :
abort ( 404 )
else :
pre_load_message [ ' community ' ] = actor
pre_load_message [ ' status ' ] = ' community not found '
return pre_load_message
2023-09-05 20:25:02 +12:00
@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-08-20 20:32:16 +00:00
success = True
if not community . instance . gone_forever :
undo_id = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/undo/ " + gibberish ( 15 )
follow = {
" actor " : current_user . public_url ( ) ,
" to " : [ community . public_url ( ) ] ,
" object " : community . public_url ( ) ,
" type " : " Follow " ,
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/follow/ { gibberish ( 15 ) } "
}
undo = {
' actor ' : current_user . public_url ( ) ,
' to ' : [ community . public_url ( ) ] ,
' type ' : ' Undo ' ,
' id ' : undo_id ,
' object ' : follow
}
success = post_request ( community . ap_inbox_url , undo , current_user . private_key ,
2024-07-30 08:50:07 +08:00
current_user . public_url ( ) + ' #main-key ' , timeout = 10 )
2024-08-30 17:25:37 +12:00
if success is False or isinstance ( success , str ) :
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 ( )
2024-08-21 13:50:11 +00:00
if not community . instance . gone_forever :
follow = {
" actor " : current_user . public_url ( ) ,
" to " : [ community . public_url ( ) ] ,
" object " : community . public_url ( ) ,
" type " : " Follow " ,
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/follow/ { join_request . id } "
}
post_request ( community . ap_inbox_url , follow , current_user . private_key ,
current_user . public_url ( ) + ' #main-key ' )
2024-02-08 18:34:58 +13:00
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 ) :
2024-07-09 08:31:33 +02:00
return redirect ( url_for ( ' community.add_post ' , actor = community . link ( ) , type = ' discussion ' ) )
2024-02-08 18:34:58 +13:00
else :
abort ( 401 )
2024-07-09 08:31:33 +02:00
@bp.route ( ' /<actor>/submit/<string:type> ' , methods = [ ' GET ' , ' POST ' ] )
@bp.route ( ' /<actor>/submit ' , defaults = { ' type ' : ' discussion ' } , methods = [ ' GET ' , ' POST ' ] )
2023-10-02 22:16:44 +13:00
@login_required
2023-10-23 13:03:35 +13:00
@validation_required
2024-07-09 08:31:33 +02:00
def add_post ( actor , type ) :
2024-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
2023-09-17 21:19:51 +12:00
community = actor_to_community ( actor )
2024-04-07 11:15:04 +12:00
2024-07-09 08:31:33 +02:00
if type == ' discussion ' :
post_type = POST_TYPE_ARTICLE
form = CreateDiscussionForm ( )
elif type == ' link ' :
post_type = POST_TYPE_LINK
form = CreateLinkForm ( )
elif type == ' image ' :
post_type = POST_TYPE_IMAGE
form = CreateImageForm ( )
elif type == ' video ' :
post_type = POST_TYPE_VIDEO
form = CreateVideoForm ( )
elif type == ' poll ' :
post_type = POST_TYPE_POLL
form = CreatePollForm ( )
2024-04-07 11:15:04 +12:00
else :
2024-07-09 08:31:33 +02:00
abort ( 404 )
2024-04-07 11:15:04 +12:00
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-05-08 19:44:23 +12:00
if not community_in_list ( community . id , form . communities . choices ) :
form . communities . choices . append ( ( community . id , community . display_name ( ) ) )
2023-09-17 21:19:51 +12:00
2024-05-09 17:54:30 +12:00
form . language_id . choices = languages_for_form ( )
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 )
2024-07-09 08:31:33 +02:00
save_post ( form , post , post_type )
2023-10-02 22:16:44 +13:00
community . post_count + = 1
2024-09-13 11:08:04 +12:00
current_user . 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-04-07 11:15:04 +12:00
if post . image_id and post . image . file_path is None :
2024-07-01 20:44:08 +08:00
make_image_sizes ( post . image_id , 170 , 512 , ' posts ' ) # the 512 sized image is for masonry view
2024-04-07 11:15:04 +12:00
# Update list of cross posts
if post . url :
2024-09-27 10:07:41 +00:00
other_posts = Post . query . filter ( Post . id != post . id , Post . url == post . url , Post . deleted == False ,
2024-04-07 11:15:04 +12:00
Post . posted_at > post . posted_at - timedelta ( days = 6 ) ) . all ( )
for op in other_posts :
if op . cross_posts is None :
op . cross_posts = [ post . id ]
2024-01-27 12:22:35 +13:00
else :
2024-04-07 11:15:04 +12:00
op . cross_posts . append ( post . id )
if post . cross_posts is None :
post . cross_posts = [ op . id ]
2024-01-27 12:22:35 +13:00
else :
2024-04-07 11:15:04 +12:00
post . cross_posts . append ( op . id )
db . session . commit ( )
2023-12-26 21:39:52 +13:00
2024-04-22 16:14:32 +01:00
upvote_own_post ( post )
2024-04-07 11:15:04 +12:00
notify_about_post ( post )
2024-07-09 08:31:33 +02:00
if post_type == POST_TYPE_POLL :
poll = Poll . query . filter_by ( post_id = post . id ) . first ( )
if not poll . local_only :
federate_post_to_user_followers ( post )
if not community . local_only and not poll . local_only :
federate_post ( community , post )
else :
2024-05-29 23:16:38 +01:00
federate_post_to_user_followers ( post )
2024-07-09 08:31:33 +02:00
if not community . local_only :
federate_post ( community , post )
2024-05-16 21:53:38 +12:00
return redirect ( f " /post/ { post . id } " )
else :
form . communities . data = community . id
form . notify_author . data = True
2024-07-09 08:31:33 +02:00
if post_type == POST_TYPE_POLL :
form . finish_in . data = ' 3d '
2024-05-16 21:53:38 +12:00
if community . posting_warning :
flash ( community . posting_warning )
2024-08-16 16:35:16 -04:00
2024-08-17 14:34:01 -04:00
# empty post to pass since add_post.html extends edit_post.html
2024-08-16 16:35:16 -04:00
# and that one checks for a post.image_id for editing image posts
post = None
2024-05-16 21:53:38 +12:00
2024-07-09 08:31:33 +02:00
return render_template ( ' community/add_post.html ' , title = _ ( ' Add post to community ' ) , form = form ,
2024-08-16 16:35:16 -04:00
post_type = post_type , community = community , post = post ,
2024-05-16 21:53:38 +12:00
markdown_editor = current_user . markdown_editor , low_bandwidth = False , actor = actor ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . id ) ,
2024-05-30 21:54:25 +12:00
menu_topics = menu_topics ( ) , site = g . site ,
2024-07-12 19:56:57 +08:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
2024-05-16 21:53:38 +12:00
)
2024-04-07 11:15:04 +12:00
def federate_post ( community , post ) :
page = {
' type ' : ' Page ' ,
' id ' : post . ap_id ,
2024-06-05 20:32:25 +12:00
' attributedTo ' : current_user . public_url ( ) ,
2024-04-07 11:15:04 +12:00
' to ' : [
2024-06-05 20:32:25 +12:00
community . public_url ( ) ,
2024-04-07 11:15:04 +12:00
' https://www.w3.org/ns/activitystreams#Public '
] ,
' name ' : post . title ,
' cc ' : [ ] ,
' content ' : post . body_html if post . body_html else ' ' ,
' mediaType ' : ' text/html ' ,
2024-09-22 13:42:02 +00:00
' source ' : { ' content ' : post . body if post . body else ' ' , ' mediaType ' : ' text/markdown ' } ,
2024-04-07 11:15:04 +12:00
' attachment ' : [ ] ,
' commentsEnabled ' : post . comments_enabled ,
' sensitive ' : post . nsfw ,
' nsfl ' : post . nsfl ,
' stickied ' : post . sticky ,
' published ' : ap_datetime ( utcnow ( ) ) ,
2024-06-05 20:32:25 +12:00
' audience ' : community . public_url ( ) ,
2024-05-09 17:54:30 +12:00
' language ' : {
' identifier ' : post . language_code ( ) ,
' name ' : post . language_name ( )
2024-05-12 13:02:45 +12:00
} ,
' tag ' : post . tags_for_activitypub ( )
2024-04-07 11:15:04 +12:00
}
create = {
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/create/ { gibberish ( 15 ) } " ,
2024-06-05 20:32:25 +12:00
" actor " : current_user . public_url ( ) ,
2024-04-07 11:15:04 +12:00
" to " : [
" https://www.w3.org/ns/activitystreams#Public "
] ,
" cc " : [
2024-06-05 20:32:25 +12:00
community . public_url ( )
2024-04-07 11:15:04 +12:00
] ,
" type " : " Create " ,
2024-06-05 20:32:25 +12:00
" audience " : community . public_url ( ) ,
2024-04-07 11:15:04 +12:00
" object " : page ,
' @context ' : default_context ( )
}
2024-04-16 20:59:58 +12:00
if post . type == POST_TYPE_LINK or post . type == POST_TYPE_VIDEO :
2024-04-07 11:15:04 +12:00
page [ ' attachment ' ] = [ { ' href ' : post . url , ' type ' : ' Link ' } ]
elif post . image_id :
2024-04-19 08:50:37 +12:00
image_url = ' '
if post . image . source_url :
image_url = post . image . source_url
elif post . image . file_path :
2024-04-07 11:15:04 +12:00
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/ " )
# 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 :
2024-09-01 19:14:05 +01:00
page [ ' attachment ' ] = [ { ' type ' : ' Image ' ,
' url ' : post . image . source_url , # source_url is always a https link, no need for .replace() as done above
' name ' : post . image . alt_text } ]
2024-05-18 21:06:57 +12:00
if post . type == POST_TYPE_POLL :
poll = Poll . query . filter_by ( post_id = post . id ) . first ( )
page [ ' type ' ] = ' Question '
page [ ' endTime ' ] = ap_datetime ( poll . end_poll )
page [ ' votersCount ' ] = 0
choices = [ ]
for choice in PollChoice . query . filter_by ( post_id = post . id ) . all ( ) :
choices . append ( {
" type " : " Note " ,
" name " : choice . choice_text ,
" replies " : {
" type " : " Collection " ,
" totalItems " : 0
}
} )
page [ ' oneOf ' if poll . mode == ' single ' else ' anyOf ' ] = choices
2024-04-07 11:15:04 +12:00
if not community . is_local ( ) : # this is a remote community - send the post to the instance that hosts it
2024-07-24 20:51:50 +08:00
post_request_in_background ( community . ap_inbox_url , create , current_user . private_key ,
2024-06-05 13:21:41 +12:00
current_user . public_url ( ) + ' #main-key ' )
2024-07-24 20:51:50 +08:00
flash ( _ ( ' Your post to %(name)s has been made. ' , name = community . title ) )
2024-04-07 11:15:04 +12:00
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 "
] ,
2024-06-05 20:32:25 +12:00
" actor " : community . public_url ( ) ,
2024-04-07 11:15:04 +12:00
" cc " : [
community . ap_followers_url
] ,
' @context ' : default_context ( ) ,
' object ' : create
}
2024-06-03 15:40:10 +01:00
microblog_announce = copy ( announce )
2024-05-29 23:53:32 +01:00
microblog_announce [ ' object ' ] = post . ap_id
2024-04-07 11:15:04 +12: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 ) :
2024-05-30 01:45:57 +01:00
if instance . software in MICROBLOG_APPS :
2024-05-29 23:53:32 +01:00
send_to_remote_instance ( instance . id , community . id , microblog_announce )
else :
send_to_remote_instance ( instance . id , community . id , announce )
2024-04-07 11:15:04 +12:00
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 ) )
2024-04-30 12:31:25 +01:00
def federate_post_to_user_followers ( post ) :
followers = UserFollower . query . filter_by ( local_user_id = post . user_id )
if not followers :
return
note = {
' type ' : ' Note ' ,
' id ' : post . ap_id ,
' inReplyTo ' : None ,
2024-09-01 17:49:46 +01:00
' attributedTo ' : current_user . public_url ( ) ,
2024-04-30 12:31:25 +01:00
' to ' : [
' https://www.w3.org/ns/activitystreams#Public '
] ,
' cc ' : [
2024-09-01 17:49:46 +01:00
current_user . followers_url ( )
2024-04-30 12:31:25 +01:00
] ,
' content ' : ' ' ,
' mediaType ' : ' text/html ' ,
' attachment ' : [ ] ,
' commentsEnabled ' : post . comments_enabled ,
' sensitive ' : post . nsfw ,
' nsfl ' : post . nsfl ,
' stickied ' : post . sticky ,
2024-05-09 17:54:30 +12:00
' published ' : ap_datetime ( utcnow ( ) ) ,
' language ' : {
' identifier ' : post . language_code ( ) ,
' name ' : post . language_name ( )
2024-05-12 13:02:45 +12:00
} ,
' tag ' : post . tags_for_activitypub ( )
2024-04-30 12:31:25 +01:00
}
create = {
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /activities/create/ { gibberish ( 15 ) } " ,
2024-09-01 17:49:46 +01:00
" actor " : current_user . public_url ( ) ,
2024-04-30 12:31:25 +01:00
" to " : [
" https://www.w3.org/ns/activitystreams#Public "
] ,
" cc " : [
2024-09-01 17:49:46 +01:00
current_user . followers_url ( )
2024-04-30 12:31:25 +01:00
] ,
" type " : " Create " ,
" object " : note ,
' @context ' : default_context ( )
}
if post . type == POST_TYPE_ARTICLE :
note [ ' content ' ] = ' <p> ' + post . title + ' </p> '
elif post . type == POST_TYPE_LINK or post . type == POST_TYPE_VIDEO :
note [ ' content ' ] = ' <p><a href= ' + post . url + ' > ' + post . title + ' </a></p> '
elif post . type == POST_TYPE_IMAGE :
note [ ' content ' ] = ' <p> ' + post . title + ' </p> '
2024-06-25 21:11:57 +08:00
if post . image_id and post . image . source_url :
2024-09-01 19:14:05 +01:00
note [ ' attachment ' ] = [ { ' type ' : ' Image ' , ' url ' : post . image . source_url , ' name ' : post . image . alt_text } ]
2024-04-30 12:31:25 +01:00
if post . body_html :
note [ ' content ' ] = note [ ' content ' ] + ' <p> ' + post . body_html + ' </p> '
2024-05-18 21:06:57 +12:00
if post . type == POST_TYPE_POLL :
poll = Poll . query . filter_by ( post_id = post . id ) . first ( )
note [ ' type ' ] = ' Question '
note [ ' endTime ' ] = ap_datetime ( poll . end_poll )
note [ ' votersCount ' ] = 0
choices = [ ]
for choice in PollChoice . query . filter_by ( post_id = post . id ) . all ( ) :
choices . append ( {
" type " : " Note " ,
" name " : choice . choice_text ,
" replies " : {
" type " : " Collection " ,
" totalItems " : 0
}
} )
note [ ' oneOf ' if poll . mode == ' single ' else ' anyOf ' ] = choices
2024-04-30 12:31:25 +01:00
instances = Instance . query . join ( User , User . instance_id == Instance . id ) . join ( UserFollower , UserFollower . remote_user_id == User . id )
2024-05-18 21:06:57 +12:00
instances = instances . filter ( UserFollower . local_user_id == post . user_id ) . filter ( Instance . gone_forever == False )
2024-07-24 20:51:50 +08:00
for instance in instances :
if instance . inbox and not current_user . has_blocked_instance ( instance . id ) and not instance_banned ( instance . domain ) :
post_request_in_background ( instance . inbox , create , current_user . private_key , current_user . public_url ( ) + ' #main-key ' )
2024-04-30 12:31:25 +01: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
2024-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
2024-03-13 16:40:20 +13:00
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 )
2024-05-08 21:07:22 +12:00
form . languages . choices = languages_for_form ( )
2024-03-13 16:40:20 +13:00
if form . validate_on_submit ( ) :
community . title = form . title . data
2024-09-22 13:42:02 +00:00
community . description = piefed_markdown_to_lemmy_markdown ( form . description . data )
2024-07-18 15:14:55 +08:00
community . description_html = markdown_to_html ( form . description . data , anchors_new_tab = False )
2024-03-13 16:40:20 +13:00
community . rules = form . rules . data
2024-07-18 15:14:55 +08:00
community . rules_html = markdown_to_html ( form . rules . data , anchors_new_tab = False )
2024-03-13 16:40:20 +13:00
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
2024-05-26 18:24:13 +12:00
cache . delete_memoized ( Community . icon_image , community )
2024-03-13 16:40:20 +13:00
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
2024-05-26 18:24:13 +12:00
cache . delete_memoized ( Community . header_image , community )
2024-03-13 16:40:20 +13:00
2024-05-08 21:07:22 +12:00
# Languages of the community
db . session . execute ( text ( ' DELETE FROM " community_language " WHERE community_id = :community_id ' ) ,
{ ' community_id ' : community_id } )
for language_choice in form . languages . data :
community . languages . append ( Language . query . get ( language_choice ) )
# Always include the undetermined language, so posts with no language will be accepted
community . languages . append ( Language . query . filter ( Language . code == ' und ' ) . first ( ) )
2024-03-13 16:40:20 +13:00
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
2024-05-08 21:07:22 +12:00
form . languages . data = community . language_ids ( )
2024-03-13 16:40:20 +13:00
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 ( ) ) ,
2024-05-30 21:54:25 +12:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site )
2024-03-13 16:40:20 +13:00
else :
abort ( 401 )
2024-05-26 18:24:13 +12:00
@bp.route ( ' /community/<int:community_id>/remove_icon ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def remove_icon ( community_id ) :
community = Community . query . get_or_404 ( community_id )
if community . icon_id :
community . icon . delete_from_disk ( )
if community . icon_id :
file = File . query . get ( community . icon_id )
file . delete_from_disk ( )
community . icon_id = None
db . session . delete ( file )
db . session . commit ( )
cache . delete_memoized ( Community . icon_image , community )
return _ ( ' Icon removed! ' )
@bp.route ( ' /community/<int:community_id>/remove_header ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def remove_header ( community_id ) :
community = Community . query . get_or_404 ( community_id )
if community . image_id :
community . image . delete_from_disk ( )
if community . image_id :
file = File . query . get ( community . image_id )
file . delete_from_disk ( )
community . image_id = None
db . session . delete ( file )
db . session . commit ( )
cache . delete_memoized ( Community . header_image , community )
return ' <div> ' + _ ( ' Banner removed! ' ) + ' </div> '
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 ) :
2024-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
2023-12-21 22:14:43 +13:00
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
2024-08-09 15:27:10 -04:00
# record for modlog
reason = f " Community { community . name } deleted by { current_user . user_name } "
add_to_modlog ( ' delete_community ' , reason = reason )
# actually delete the community
2024-04-03 20:29:24 +13:00
community . delete_dependencies ( )
db . session . delete ( community )
2023-12-21 22:14:43 +13:00
db . session . commit ( )
2024-07-07 15:01:52 +08:00
2023-12-21 22:14:43 +13:00
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 ( ) ) ,
2024-05-30 21:54:25 +12:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site )
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 ) :
2024-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
2024-03-13 16:40:20 +13:00
community = Community . query . get_or_404 ( community_id )
2024-07-16 19:52:44 +08:00
if community . is_owner ( ) or current_user . is_admin ( ) or community . is_moderator ( current_user ) :
2024-03-13 16:40:20 +13:00
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 ( ) ) ,
2024-05-30 21:54:25 +12:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site
2024-03-13 16:40:20 +13:00
)
2024-07-16 19:52:44 +08:00
else :
abort ( 401 )
2024-03-13 16:40:20 +13:00
2024-07-14 14:01:22 +08:00
@bp.route ( ' /community/<int:community_id>/moderators/add/<int:user_id> ' , methods = [ ' GET ' , ' POST ' ] )
2024-03-13 16:40:20 +13:00
@login_required
2024-07-14 14:01:22 +08:00
def community_add_moderator ( community_id : int , user_id : int ) :
if current_user . banned :
return show_ban_message ( )
community = Community . query . get_or_404 ( community_id )
new_moderator = User . query . get_or_404 ( user_id )
if community . is_owner ( ) or current_user . is_admin ( ) and not new_moderator . banned :
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 )
add_to_modlog ( ' add_mod ' , community_id = community_id , link_text = new_moderator . display_name ( ) ,
link = new_moderator . link ( ) )
# 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 ) )
@bp.route ( ' /community/<int:community_id>/moderators/find ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_find_moderator ( community_id : int ) :
2024-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
2024-03-13 16:40:20 +13:00
community = Community . query . get_or_404 ( community_id )
if community . is_owner ( ) or current_user . is_admin ( ) :
form = AddModeratorForm ( )
2024-07-14 14:01:22 +08:00
potential_moderators = None
2024-03-13 16:40:20 +13:00
if form . validate_on_submit ( ) :
2024-07-14 14:01:22 +08:00
potential_moderators = find_local_users ( form . user_name . data )
2024-03-13 16:40:20 +13:00
2024-07-14 14:01:22 +08:00
return render_template ( ' community/community_find_moderator.html ' , title = _ ( ' Add moderator to %(community)s ' ,
community = community . display_name ( ) ) ,
community = community , form = form , potential_moderators = potential_moderators ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site
)
2024-07-16 19:52:44 +08:00
else :
abort ( 401 )
2024-03-13 16:40:20 +13:00
@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 ) :
2024-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
2024-03-13 16:40:20 +13:00
community = Community . query . get_or_404 ( community_id )
2024-07-16 19:52:44 +08:00
if community . is_owner ( ) or current_user . is_admin ( ) or user_id == current_user . id :
2024-03-13 16:40:20 +13:00
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 ' ) )
2024-07-07 15:01:52 +08:00
removed_mod = User . query . get ( existing_member . user_id )
add_to_modlog ( ' remove_mod ' , community_id = community_id , link_text = removed_mod . display_name ( ) ,
link = removed_mod . link ( ) )
2024-03-13 16:40:20 +13:00
# 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 ) )
2024-07-16 19:52:44 +08:00
else :
abort ( 401 )
2024-03-13 16:40:20 +13:00
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 )
2024-05-10 10:03:39 +01:00
if form . ban_until . data is not None and form . ban_until . data > utcnow ( ) . date ( ) :
2024-03-15 14:24:45 +13:00
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
2024-04-22 20:53:03 +12:00
# Notify banned person
2024-03-15 14:24:45 +13:00
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
2024-04-22 20:53:03 +12:00
# Remove their notification subscription, if any
db . session . query ( NotificationSubscription ) . filter ( NotificationSubscription . entity_id == community . id ,
NotificationSubscription . user_id == user . id ,
NotificationSubscription . type == NOTIF_COMMUNITY ) . delete ( )
2024-07-07 15:01:52 +08:00
add_to_modlog ( ' ban_user ' , community_id = community . id , link_text = user . display_name ( ) , link = user . link ( ) )
2024-03-15 14:24:45 +13:00
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 ( ) ) ,
2024-05-30 21:54:25 +12:00
menu_topics = menu_topics ( ) , site = g . site ,
2024-07-12 19:56:57 +08:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
2024-03-15 14:24:45 +13:00
)
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
2024-07-07 15:01:52 +08:00
add_to_modlog ( ' unban_user ' , community_id = community . id , link_text = user . display_name ( ) , link = user . link ( ) )
2024-05-10 09:50:05 +01:00
return redirect ( url_for ( ' community.community_moderate_subscribers ' , actor = community . link ( ) ) )
2024-03-21 21:19:50 +13:00
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 ) :
2024-04-22 20:53:03 +12:00
# Toggle whether the current user is subscribed to notifications about this community's posts or not
2024-01-07 12:47:06 +13:00
community = Community . query . get_or_404 ( community_id )
2024-04-22 20:53:03 +12:00
existing_notification = NotificationSubscription . query . filter ( NotificationSubscription . entity_id == community . id ,
NotificationSubscription . user_id == current_user . id ,
NotificationSubscription . type == NOTIF_COMMUNITY ) . first ( )
if existing_notification :
db . session . delete ( existing_notification )
db . session . commit ( )
else : # no subscription yet, so make one
2024-04-29 16:08:35 +12:00
if community . id not in communities_banned_from ( current_user . id ) :
2024-04-29 21:43:37 +12:00
new_notification = NotificationSubscription ( name = shorten_string ( _ ( ' New posts in %(community_name)s ' , community_name = community . title ) ) ,
user_id = current_user . id , entity_id = community . id ,
2024-04-22 21:00:04 +12:00
type = NOTIF_COMMUNITY )
2024-04-22 20:53:03 +12:00
db . session . add ( new_notification )
db . session . commit ( )
2024-01-07 12:47:06 +13:00
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.
2024-04-29 16:08:35 +12:00
if community . id not in communities_banned_from ( current_user . id ) :
2024-01-07 12:47:06 +13:00
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
2024-03-30 15:44:38 +13:00
2024-03-18 21:05:13 +13:00
@bp.route ( ' /<actor>/moderate ' , methods = [ ' GET ' ] )
@login_required
def community_moderate ( actor ) :
2024-04-03 20:48:39 +13:00
if current_user . banned :
return show_ban_message ( )
2024-03-18 21:05:13 +13:00
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 ' ,
2024-03-29 16:55:36 +01:00
next_url = next_url , prev_url = prev_url ,
2024-03-18 21:05:13 +13:00
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
2024-05-30 21:54:25 +12:00
menu_topics = menu_topics ( ) , site = g . site ,
2024-07-12 19:56:57 +08:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
2024-03-18 21:05:13 +13:00
)
else :
abort ( 401 )
else :
abort ( 404 )
2024-03-30 15:44:38 +13:00
2024-03-29 13:41:43 +01:00
@bp.route ( ' /<actor>/moderate/subscribers ' , methods = [ ' GET ' ] )
2024-03-18 21:05:13 +13:00
@login_required
2024-03-29 13:41:43 +01:00
def community_moderate_subscribers ( actor ) :
2024-03-18 21:05:13 +13:00
community = actor_to_community ( actor )
if community is not None :
if community . is_moderator ( ) or current_user . is_admin ( ) :
2024-03-29 13:41:43 +01:00
2024-03-30 15:56:45 +13:00
page = request . args . get ( ' page ' , 1 , type = int )
low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 '
2024-03-30 15:49:26 +13:00
subscribers = User . query . join ( CommunityMember , CommunityMember . user_id == User . id ) . filter ( CommunityMember . community_id == community . id )
2024-03-30 15:56:45 +13:00
subscribers = subscribers . filter ( CommunityMember . is_banned == False )
# Pagination
subscribers = subscribers . paginate ( page = page , per_page = 100 if not low_bandwidth else 50 , error_out = False )
next_url = url_for ( ' community.community_moderate_subscribers ' , actor = actor , page = subscribers . next_num ) if subscribers . has_next else None
prev_url = url_for ( ' community.community_moderate_subscribers ' , actor = actor , page = subscribers . prev_num ) if subscribers . has_prev and page != 1 else None
2024-03-29 13:41:43 +01:00
2024-03-18 21:05:13 +13:00
banned_people = User . query . join ( CommunityBan , CommunityBan . user_id == User . id ) . filter ( CommunityBan . community_id == community . id ) . all ( )
2024-03-29 13:41:43 +01:00
return render_template ( ' community/community_moderate_subscribers.html ' , title = _ ( ' Moderation of %(community)s ' , community = community . display_name ( ) ) ,
2024-03-30 15:49:26 +13:00
community = community , current = ' subscribers ' , subscribers = subscribers , banned_people = banned_people ,
2024-03-30 15:56:45 +13:00
next_url = next_url , prev_url = prev_url , low_bandwidth = low_bandwidth ,
2024-03-18 21:05:13 +13:00
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
2024-05-30 21:54:25 +12:00
menu_topics = menu_topics ( ) , site = g . site ,
2024-07-12 19:56:57 +08:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
2024-03-18 21:05:13 +13:00
)
else :
abort ( 401 )
else :
2024-03-24 00:15:10 +00:00
abort ( 404 )
2024-03-27 20:20:08 +13:00
2024-03-30 15:44:38 +13:00
2024-09-23 15:05:13 +00:00
@bp.route ( ' /community/<int:community_id>/<int:user_id>/kick_user_community ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_kick_user ( community_id : int , user_id : int ) :
community = Community . query . get_or_404 ( community_id )
user = User . query . get_or_404 ( user_id )
if community is not None :
if current_user . is_admin ( ) :
db . session . query ( CommunityMember ) . filter_by ( user_id = user_id , community_id = community . id ) . delete ( )
db . session . commit ( )
else :
abort ( 401 )
else :
abort ( 404 )
return redirect ( url_for ( ' community.community_moderate_subscribers ' , actor = community . name ) )
2024-07-17 22:11:31 +08:00
@bp.route ( ' /<actor>/moderate/wiki ' , methods = [ ' GET ' ] )
@login_required
def community_wiki_list ( actor ) :
community = actor_to_community ( actor )
if community is not None :
if community . is_moderator ( ) or current_user . is_admin ( ) :
low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 '
pages = CommunityWikiPage . query . filter ( CommunityWikiPage . community_id == community . id ) . order_by ( CommunityWikiPage . title ) . all ( )
return render_template ( ' community/community_wiki_list.html ' , title = _ ( ' Community Wiki ' ) , community = community ,
pages = pages , low_bandwidth = low_bandwidth , current = ' wiki ' ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site ,
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
)
else :
abort ( 401 )
else :
abort ( 404 )
@bp.route ( ' /<actor>/moderate/wiki/add ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_wiki_add ( actor ) :
community = actor_to_community ( actor )
if community is not None :
if community . is_moderator ( ) or current_user . is_admin ( ) :
low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 '
form = EditCommunityWikiPageForm ( )
if form . validate_on_submit ( ) :
new_page = CommunityWikiPage ( community_id = community . id , slug = form . slug . data , title = form . title . data ,
body = form . body . data , who_can_edit = form . who_can_edit . data )
new_page . body_html = markdown_to_html ( new_page . body )
db . session . add ( new_page )
db . session . commit ( )
initial_revision = CommunityWikiPageRevision ( wiki_page_id = new_page . id , user_id = current_user . id ,
community_id = community . id , title = form . title . data ,
body = form . body . data , body_html = new_page . body_html )
db . session . add ( initial_revision )
db . session . commit ( )
flash ( _ ( ' Saved ' ) )
return redirect ( url_for ( ' community.community_wiki_list ' , actor = community . link ( ) ) )
return render_template ( ' community/community_wiki_edit.html ' , title = _ ( ' Add wiki page ' ) , community = community ,
form = form , low_bandwidth = low_bandwidth , current = ' wiki ' ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site ,
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
)
else :
abort ( 401 )
else :
abort ( 404 )
@bp.route ( ' /<actor>/wiki/<slug> ' , methods = [ ' GET ' , ' POST ' ] )
def community_wiki_view ( actor , slug ) :
community = actor_to_community ( actor )
if community is not None :
page : CommunityWikiPage = CommunityWikiPage . query . filter_by ( slug = slug , community_id = community . id ) . first ( )
if page is None :
abort ( 404 )
else :
# Breadcrumbs
breadcrumbs = [ ]
breadcrumb = namedtuple ( " Breadcrumb " , [ ' text ' , ' url ' ] )
breadcrumb . text = _ ( ' Home ' )
breadcrumb . url = ' / '
breadcrumbs . append ( breadcrumb )
if community . topic_id :
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
else :
breadcrumb = namedtuple ( " Breadcrumb " , [ ' text ' , ' url ' ] )
breadcrumb . text = _ ( ' Communities ' )
breadcrumb . url = ' /communities '
breadcrumbs . append ( breadcrumb )
return render_template ( ' community/community_wiki_page_view.html ' , title = page . title , page = page ,
community = community , breadcrumbs = breadcrumbs , is_moderator = community . is_moderator ( ) ,
is_owner = community . is_owner ( ) ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site ,
inoculation = inoculation [
randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
)
2024-07-18 15:14:55 +08:00
@bp.route ( ' /<actor>/wiki/<slug>/<revision_id> ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_wiki_view_revision ( actor , slug , revision_id ) :
community = actor_to_community ( actor )
if community is not None :
page : CommunityWikiPage = CommunityWikiPage . query . filter_by ( slug = slug , community_id = community . id ) . first ( )
revision : CommunityWikiPageRevision = CommunityWikiPageRevision . query . get_or_404 ( revision_id )
if page is None or revision is None :
abort ( 404 )
else :
# Breadcrumbs
breadcrumbs = [ ]
breadcrumb = namedtuple ( " Breadcrumb " , [ ' text ' , ' url ' ] )
breadcrumb . text = _ ( ' Home ' )
breadcrumb . url = ' / '
breadcrumbs . append ( breadcrumb )
if community . topic_id :
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
else :
breadcrumb = namedtuple ( " Breadcrumb " , [ ' text ' , ' url ' ] )
breadcrumb . text = _ ( ' Communities ' )
breadcrumb . url = ' /communities '
breadcrumbs . append ( breadcrumb )
return render_template ( ' community/community_wiki_revision_view.html ' , title = page . title , page = page ,
community = community , breadcrumbs = breadcrumbs , is_moderator = community . is_moderator ( ) ,
is_owner = community . is_owner ( ) , revision = revision ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site ,
inoculation = inoculation [
randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
)
@bp.route ( ' /<actor>/wiki/<slug>/<revision_id>/revert ' , methods = [ ' GET ' ] )
@login_required
def community_wiki_revert_revision ( actor , slug , revision_id ) :
community = actor_to_community ( actor )
if community is not None :
page : CommunityWikiPage = CommunityWikiPage . query . filter_by ( slug = slug , community_id = community . id ) . first ( )
revision : CommunityWikiPageRevision = CommunityWikiPageRevision . query . get_or_404 ( revision_id )
if page is None or revision is None :
abort ( 404 )
else :
if page . can_edit ( current_user , community ) :
page . body = revision . body
page . body_html = revision . body_html
page . edited_at = utcnow ( )
new_revision = CommunityWikiPageRevision ( wiki_page_id = page . id , user_id = current_user . id ,
community_id = community . id , title = revision . title ,
body = revision . body , body_html = revision . body_html )
db . session . add ( new_revision )
db . session . commit ( )
flash ( _ ( ' Reverted to old version of the page. ' ) )
return redirect ( url_for ( ' community.community_wiki_revisions ' , actor = community . link ( ) , page_id = page . id ) )
else :
abort ( 401 )
2024-07-17 22:11:31 +08:00
@bp.route ( ' /<actor>/moderate/wiki/<int:page_id>/edit ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_wiki_edit ( actor , page_id ) :
community = actor_to_community ( actor )
if community is not None :
page : CommunityWikiPage = CommunityWikiPage . query . get_or_404 ( page_id )
if page . can_edit ( current_user , community ) :
low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 '
form = EditCommunityWikiPageForm ( )
if form . validate_on_submit ( ) :
page . title = form . title . data
page . slug = form . slug . data
page . body = form . body . data
page . body_html = markdown_to_html ( page . body )
page . who_can_edit = form . who_can_edit . data
page . edited_at = utcnow ( )
new_revision = CommunityWikiPageRevision ( wiki_page_id = page . id , user_id = current_user . id ,
community_id = community . id , title = form . title . data ,
body = form . body . data , body_html = page . body_html )
db . session . add ( new_revision )
db . session . commit ( )
flash ( _ ( ' Saved ' ) )
if request . args . get ( ' return ' ) == ' list ' :
return redirect ( url_for ( ' community.community_wiki_list ' , actor = community . link ( ) ) )
elif request . args . get ( ' return ' ) == ' page ' :
return redirect ( url_for ( ' community.community_wiki_view ' , actor = community . link ( ) , slug = page . slug ) )
else :
form . title . data = page . title
form . slug . data = page . slug
form . body . data = page . body
form . who_can_edit . data = page . who_can_edit
return render_template ( ' community/community_wiki_edit.html ' , title = _ ( ' Edit wiki page ' ) , community = community ,
form = form , low_bandwidth = low_bandwidth ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site ,
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
)
else :
abort ( 401 )
else :
abort ( 404 )
2024-07-18 15:14:55 +08:00
@bp.route ( ' /<actor>/moderate/wiki/<int:page_id>/revisions ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_wiki_revisions ( actor , page_id ) :
community = actor_to_community ( actor )
if community is not None :
page : CommunityWikiPage = CommunityWikiPage . query . get_or_404 ( page_id )
if page . can_edit ( current_user , community ) :
low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 '
revisions = CommunityWikiPageRevision . query . filter_by ( wiki_page_id = page . id ) . \
order_by ( desc ( CommunityWikiPageRevision . edited_at ) ) . all ( )
most_recent_revision = revisions [ 0 ] . id
return render_template ( ' community/community_wiki_revisions.html ' , title = _ ( ' %(title)s revisions ' , title = page . title ) ,
community = community , page = page , revisions = revisions , most_recent_revision = most_recent_revision ,
low_bandwidth = low_bandwidth ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site ,
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
)
else :
abort ( 401 )
else :
abort ( 404 )
2024-07-17 22:11:31 +08:00
@bp.route ( ' /<actor>/moderate/wiki/<int:page_id>/delete ' , methods = [ ' GET ' ] )
@login_required
def community_wiki_delete ( actor , page_id ) :
community = actor_to_community ( actor )
if community is not None :
page : CommunityWikiPage = CommunityWikiPage . query . get_or_404 ( page_id )
if page . can_edit ( current_user , community ) :
db . session . delete ( page )
db . session . commit ( )
flash ( _ ( ' Page deleted ' ) )
return redirect ( url_for ( ' community.community_wiki_list ' , actor = community . link ( ) ) )
else :
abort ( 404 )
2024-07-07 15:01:52 +08:00
@bp.route ( ' /<actor>/moderate/modlog ' , methods = [ ' GET ' ] )
@login_required
def community_modlog ( 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 )
low_bandwidth = request . cookies . get ( ' low_bandwidth ' , ' 0 ' ) == ' 1 '
modlog_entries = ModLog . query . filter ( ModLog . community_id == community . id ) . order_by ( desc ( ModLog . created_at ) )
# Pagination
modlog_entries = modlog_entries . paginate ( page = page , per_page = 100 if not low_bandwidth else 50 , error_out = False )
next_url = url_for ( ' community.community_modlog ' , actor = actor ,
page = modlog_entries . next_num ) if modlog_entries . has_next else None
prev_url = url_for ( ' community.community_modlog ' , actor = actor ,
page = modlog_entries . prev_num ) if modlog_entries . has_prev and page != 1 else None
return render_template ( ' community/community_modlog.html ' ,
title = _ ( ' Mod Log of %(community)s ' , community = community . display_name ( ) ) ,
community = community , current = ' modlog ' , modlog_entries = modlog_entries ,
next_url = next_url , prev_url = prev_url , low_bandwidth = low_bandwidth ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
menu_topics = menu_topics ( ) , site = g . site ,
2024-07-12 19:56:57 +08:00
inoculation = inoculation [ randint ( 0 , len ( inoculation ) - 1 ) ] if g . site . show_inoculation_block else None
2024-07-07 15:01:52 +08:00
)
else :
abort ( 401 )
else :
abort ( 404 )
2024-03-27 20:20:08 +13:00
@bp.route ( ' /community/<int:community_id>/moderate_report/<int:report_id>/escalate ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_moderate_report_escalate ( community_id , report_id ) :
community = Community . query . get_or_404 ( community_id )
if community . is_moderator ( ) or current_user . is_admin ( ) :
report = Report . query . filter_by ( in_community_id = community . id , id = report_id , status = REPORT_STATE_NEW ) . first ( )
if report :
form = EscalateReportForm ( )
if form . validate_on_submit ( ) :
notify = Notification ( title = ' Escalated report ' , url = ' /admin/reports ' , user_id = 1 ,
author_id = current_user . id )
db . session . add ( notify )
report . description = form . reason . data
report . status = REPORT_STATE_ESCALATED
db . session . commit ( )
flash ( _ ( ' Admin has been notified about this report. ' ) )
# todo: remove unread notifications about this report
# todo: append to mod log
return redirect ( url_for ( ' community.community_moderate ' , actor = community . link ( ) ) )
else :
form . reason . data = report . description
return render_template ( ' community/community_moderate_report_escalate.html ' , form = form )
else :
abort ( 401 )
@bp.route ( ' /community/<int:community_id>/moderate_report/<int:report_id>/resolve ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_moderate_report_resolve ( community_id , report_id ) :
community = Community . query . get_or_404 ( community_id )
if community . is_moderator ( ) or current_user . is_admin ( ) :
report = Report . query . filter_by ( in_community_id = community . id , id = report_id ) . first ( )
if report :
form = ResolveReportForm ( )
if form . validate_on_submit ( ) :
report . status = REPORT_STATE_RESOLVED
2024-04-06 13:53:41 +13:00
# Reset the 'reports' counter on the comment, post or user
if report . suspect_post_reply_id :
post_reply = PostReply . query . get ( report . suspect_post_reply_id )
post_reply . reports = 0
elif report . suspect_post_id :
post = Post . query . get ( report . suspect_post_id )
post . reports = 0
elif report . suspect_user_id :
user = User . query . get ( report . suspect_user_id )
user . reports = 0
2024-03-27 20:20:08 +13:00
db . session . commit ( )
2024-04-06 13:53:41 +13:00
2024-03-27 20:20:08 +13:00
# todo: remove unread notifications about this report
# todo: append to mod log
2024-03-27 20:31:36 +13:00
if form . also_resolve_others . data :
if report . suspect_post_reply_id :
db . session . execute ( text ( ' UPDATE " report " SET status = :new_status WHERE suspect_post_reply_id = :suspect_post_reply_id ' ) ,
{ ' new_status ' : REPORT_STATE_RESOLVED ,
' suspect_post_reply_id ' : report . suspect_post_reply_id } )
# todo: remove unread notifications about these reports
elif report . suspect_post_id :
db . session . execute ( text ( ' UPDATE " report " SET status = :new_status WHERE suspect_post_id = :suspect_post_id ' ) ,
{ ' new_status ' : REPORT_STATE_RESOLVED ,
' suspect_post_id ' : report . suspect_post_id } )
# todo: remove unread notifications about these reports
db . session . commit ( )
2024-03-27 20:20:08 +13:00
flash ( _ ( ' Report resolved. ' ) )
return redirect ( url_for ( ' community.community_moderate ' , actor = community . link ( ) ) )
else :
return render_template ( ' community/community_moderate_report_resolve.html ' , form = form )
2024-03-28 03:40:42 +00:00
2024-04-06 14:10:23 +13:00
@bp.route ( ' /community/<int:community_id>/moderate_report/<int:report_id>/ignore ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
def community_moderate_report_ignore ( community_id , report_id ) :
community = Community . query . get_or_404 ( community_id )
if community . is_moderator ( ) or current_user . is_admin ( ) :
report = Report . query . filter_by ( in_community_id = community . id , id = report_id ) . first ( )
if report :
# Set the 'reports' counter on the comment, post or user to -1 to ignore all future reports
if report . suspect_post_reply_id :
post_reply = PostReply . query . get ( report . suspect_post_reply_id )
post_reply . reports = - 1
elif report . suspect_post_id :
post = Post . query . get ( report . suspect_post_id )
post . reports = - 1
elif report . suspect_user_id :
user = User . query . get ( report . suspect_user_id )
user . reports = - 1
db . session . commit ( )
# todo: append to mod log
if report . suspect_post_reply_id :
db . session . execute ( text ( ' UPDATE " report " SET status = :new_status WHERE suspect_post_reply_id = :suspect_post_reply_id ' ) ,
{ ' new_status ' : REPORT_STATE_DISCARDED ,
' suspect_post_reply_id ' : report . suspect_post_reply_id } )
# todo: remove unread notifications about these reports
elif report . suspect_post_id :
db . session . execute ( text ( ' UPDATE " report " SET status = :new_status WHERE suspect_post_id = :suspect_post_id ' ) ,
{ ' new_status ' : REPORT_STATE_DISCARDED ,
' suspect_post_id ' : report . suspect_post_id } )
# todo: remove unread notifications about these reports
db . session . commit ( )
flash ( _ ( ' Report ignored. ' ) )
return redirect ( url_for ( ' community.community_moderate ' , actor = community . link ( ) ) )
else :
abort ( 404 )
2024-03-28 03:40:42 +00:00
@bp.route ( ' /lookup/<community>/<domain> ' )
def lookup ( community , domain ) :
if domain == current_app . config [ ' SERVER_NAME ' ] :
return redirect ( ' /c/ ' + community )
exists = Community . query . filter_by ( name = community , ap_domain = domain ) . first ( )
if exists :
return redirect ( ' /c/ ' + community + ' @ ' + domain )
else :
address = ' ! ' + community + ' @ ' + domain
if current_user . is_authenticated :
new_community = None
2024-08-30 17:43:22 +12:00
try :
new_community = search_for_community ( address )
except Exception as e :
if ' is blocked. ' in str ( e ) :
flash ( _ ( ' Sorry, that instance is blocked, check https://gui.fediseer.com/ for reasons. ' ) , ' warning ' )
2024-03-28 03:40:42 +00: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 ' )
else :
if new_community . banned :
flash ( _ ( ' That community is banned from %(site)s . ' , site = g . site . name ) , ' warning ' )
return render_template ( ' community/lookup_remote.html ' ,
title = _ ( ' Search result for remote community ' ) , new_community = new_community ,
subscribed = community_membership ( current_user , new_community ) > = SUBSCRIPTION_MEMBER )
else :
# send them back where they came from
flash ( ' Searching for remote communities requires login ' , ' error ' )
referrer = request . headers . get ( ' Referer ' , None )
if referrer is not None :
return redirect ( referrer )
else :
return redirect ( ' / ' )
2024-08-15 19:39:33 +12:00
@bp.route ( ' /check_url_already_posted ' )
def check_url_already_posted ( ) :
url = request . args . get ( ' link_url ' )
if url :
url = remove_tracking_from_link ( url . strip ( ) )
2024-09-27 07:20:24 +00:00
communities = Community . query . filter_by ( banned = False ) . join ( Post ) . filter ( Post . url == url , Post . deleted == False ) . all ( )
2024-08-15 19:39:33 +12:00
return flask . render_template ( ' community/check_url_posted.html ' , communities = communities )
else :
abort ( 404 )
2024-04-22 16:14:32 +01:00
def upvote_own_post ( post ) :
post . score = 1
post . up_votes = 1
post . ranking = post_ranking ( post . score , utcnow ( ) )
vote = PostVote ( user_id = current_user . id , post_id = post . id , author_id = current_user . id , effect = 1 )
db . session . add ( vote )
db . session . commit ( )
cache . delete_memoized ( recently_upvoted_posts , current_user . id )