/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 ' ' + _('Banner removed!') + '
'
@bp.route('/community//delete', methods=['GET', 'POST'])
@login_required
def community_delete(community_id: int):
if current_user.banned:
return show_ban_message()
community = Community.query.get_or_404(community_id)
if community.is_owner() or current_user.is_admin():
form = DeleteCommunityForm()
if form.validate_on_submit():
if community.is_local():
community.banned = True
# todo: federate deletion out to all instances. At end of federation process, delete_dependencies() and delete community
community.delete_dependencies()
db.session.delete(community)
db.session.commit()
add_to_modlog('delete_community', community_id=community.id, link_text=community.display_name(),
link=community.link())
flash(_('Community deleted'))
return redirect('/communities')
return render_template('community/community_delete.html', title=_('Delete community'), form=form,
community=community, moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(), site=g.site)
else:
abort(401)
@bp.route('/community//moderators', methods=['GET', 'POST'])
@login_required
def community_mod_list(community_id: int):
if current_user.banned:
return show_ban_message()
community = Community.query.get_or_404(community_id)
if community.is_owner() or current_user.is_admin():
moderators = User.query.filter(User.banned == False).join(CommunityMember, CommunityMember.user_id == User.id).\
filter(CommunityMember.community_id == community_id, or_(CommunityMember.is_moderator == True, CommunityMember.is_owner == True)).all()
return render_template('community/community_mod_list.html', title=_('Moderators for %(community)s', community=community.display_name()),
moderators=moderators, community=community, current="moderators",
moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(), site=g.site
)
@bp.route('/community//moderators/add', methods=['GET', 'POST'])
@login_required
def community_add_moderator(community_id: int):
if current_user.banned:
return show_ban_message()
community = Community.query.get_or_404(community_id)
if community.is_owner() or current_user.is_admin():
form = AddModeratorForm()
if form.validate_on_submit():
new_moderator = search_for_user(form.user_name.data)
if new_moderator:
existing_member = CommunityMember.query.filter(CommunityMember.user_id == new_moderator.id, CommunityMember.community_id == community_id).first()
if existing_member:
existing_member.is_moderator = True
else:
new_member = CommunityMember(community_id=community_id, user_id=new_moderator.id, is_moderator=True)
db.session.add(new_member)
db.session.commit()
flash(_('Moderator added'))
# Notify new mod
if new_moderator.is_local():
notify = Notification(title=_('You are now a moderator of %(name)s', name=community.display_name()),
url='/c/' + community.name, user_id=new_moderator.id,
author_id=current_user.id)
new_moderator.unread_notifications += 1
db.session.add(notify)
db.session.commit()
else:
# for remote users, send a chat message to let them know
existing_conversation = Conversation.find_existing_conversation(recipient=new_moderator,
sender=current_user)
if not existing_conversation:
existing_conversation = Conversation(user_id=current_user.id)
existing_conversation.members.append(new_moderator)
existing_conversation.members.append(current_user)
db.session.add(existing_conversation)
db.session.commit()
server = current_app.config['SERVER_NAME']
send_message(f"Hi there. I've added you as a moderator to the community !{community.name}@{server}.",
existing_conversation.id)
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))
else:
flash(_('Account not found'), 'warning')
return render_template('community/community_add_moderator.html', title=_('Add moderator to %(community)s', community=community.display_name()),
community=community, form=form,
moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(), site=g.site
)
@bp.route('/community//moderators/remove/', methods=['GET', 'POST'])
@login_required
def community_remove_moderator(community_id: int, user_id: int):
if current_user.banned:
return show_ban_message()
community = Community.query.get_or_404(community_id)
if community.is_owner() or current_user.is_admin():
existing_member = CommunityMember.query.filter(CommunityMember.user_id == user_id,
CommunityMember.community_id == community_id).first()
if existing_member:
existing_member.is_moderator = False
db.session.commit()
flash(_('Moderator removed'))
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())
# 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))
@bp.route('/community//block_instance', methods=['GET', 'POST'])
@login_required
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())
@bp.route('/community///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():
# 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.
if not existing:
new_ban = CommunityBan(community_id=community_id, user_id=user.id, banned_by=current_user.id,
reason=form.reason.data)
if form.ban_until.data is not None and form.ban_until.data > utcnow().date():
new_ban.ban_until = form.ban_until.data
db.session.add(new_ban)
db.session.commit()
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()))
if form.delete_posts.data:
posts = Post.query.filter(Post.user_id == user.id, Post.community_id == community.id).all()
for post in posts:
delete_post_from_community(post.id)
if posts:
flash(_('Posts by %(name)s have been deleted.', name=user.display_name()))
if form.delete_post_replies.data:
post_replies = PostReply.query.filter(PostReply.user_id == user.id, Post.community_id == community.id).all()
for post_reply in post_replies:
delete_post_reply_from_community(post_reply.id)
if post_replies:
flash(_('Comments by %(name)s have been deleted.', name=user.display_name()))
# todo: federate ban to post author instance
# Notify banned person
if user.is_local():
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 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
# 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()
add_to_modlog('ban_user', community_id=community.id, link_text=user.display_name(), link=user.link())
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()),
menu_topics=menu_topics(), site=g.site,
inoculation=inoculation[randint(0, len(inoculation) - 1)]
)
@bp.route('/community///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
add_to_modlog('unban_user', community_id=community.id, link_text=user.display_name(), link=user.link())
return redirect(url_for('community.community_moderate_subscribers', actor=community.link()))
@bp.route('//notification', methods=['GET', 'POST'])
@login_required
def community_notification(community_id: int):
# Toggle whether the current user is subscribed to notifications about this community's posts or not
community = Community.query.get_or_404(community_id)
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
if community.id not in communities_banned_from(current_user.id):
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,
type=NOTIF_COMMUNITY)
db.session.add(new_notification)
db.session.commit()
member_info = CommunityMember.query.filter(CommunityMember.community_id == community.id,
CommunityMember.user_id == current_user.id).first()
# existing community members get their notification flag toggled
if member_info and not member_info.is_banned:
member_info.notify_new_posts = not member_info.notify_new_posts
db.session.commit()
else: # people who are not yet members become members, with notify on.
if community.id not in communities_banned_from(current_user.id):
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)
@bp.route('//moderate', methods=['GET'])
@login_required
def community_moderate(actor):
if current_user.banned:
return show_ban_message()
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':
reports = reports.filter(Report.source_instance_id == 1)
if local_remote == 'remote':
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)
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
return render_template('community/community_moderate.html', title=_('Moderation of %(community)s', community=community.display_name()),
community=community, reports=reports, current='reports',
next_url=next_url, prev_url=prev_url,
moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(), site=g.site,
inoculation=inoculation[randint(0, len(inoculation) - 1)]
)
else:
abort(401)
else:
abort(404)
@bp.route('//moderate/subscribers', methods=['GET'])
@login_required
def community_moderate_subscribers(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'
subscribers = User.query.join(CommunityMember, CommunityMember.user_id == User.id).filter(CommunityMember.community_id == community.id)
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
banned_people = User.query.join(CommunityBan, CommunityBan.user_id == User.id).filter(CommunityBan.community_id == community.id).all()
return render_template('community/community_moderate_subscribers.html', title=_('Moderation of %(community)s', community=community.display_name()),
community=community, current='subscribers', subscribers=subscribers, banned_people=banned_people,
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,
inoculation=inoculation[randint(0, len(inoculation) - 1)]
)
else:
abort(401)
else:
abort(404)
@bp.route('//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,
inoculation=inoculation[randint(0, len(inoculation) - 1)]
)
else:
abort(401)
else:
abort(404)
@bp.route('/community//moderate_report//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//moderate_report//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
# 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
db.session.commit()
# todo: remove unread notifications about this report
# todo: append to mod log
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()
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)
@bp.route('/community//moderate_report//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)
@bp.route('/lookup//')
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
new_community = search_for_community(address)
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('/')
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)