mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
admin area to respond to moderation reports
This commit is contained in:
parent
b4dcdf98e7
commit
520db4a924
17 changed files with 278 additions and 101 deletions
4
LICENSE
4
LICENSE
|
@ -629,8 +629,8 @@ to attach them to the start of each source file to most effectively
|
||||||
state the exclusion of warranty; and each file should have at least
|
state the exclusion of warranty; and each file should have at least
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
PieFed, a federated forum
|
||||||
Copyright (C) <year> <name of author>
|
Copyright (C) 2024 Rimu Atkinson
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
|
|
@ -893,10 +893,15 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep
|
||||||
community.last_active = post.last_active = utcnow()
|
community.last_active = post.last_active = utcnow()
|
||||||
activity_log.result = 'success'
|
activity_log.result = 'success'
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
vote = PostReplyVote(user_id=user.id, author_id=post_reply.user_id,
|
if user.reputation > 100:
|
||||||
post_reply_id=post_reply.id,
|
vote = PostReplyVote(user_id=1, author_id=post_reply.user_id,
|
||||||
effect=instance_weight(user.ap_domain))
|
post_reply_id=post_reply.id,
|
||||||
db.session.add(vote)
|
effect=instance_weight(user.ap_domain))
|
||||||
|
db.session.add(vote)
|
||||||
|
post_reply.up_votes += 1
|
||||||
|
post_reply.score += 1
|
||||||
|
post_reply.ranking += 1
|
||||||
|
db.session.commit()
|
||||||
else:
|
else:
|
||||||
activity_log.exception_message = 'Comments disabled, reply discarded'
|
activity_log.exception_message = 'Comments disabled, reply discarded'
|
||||||
activity_log.result = 'ignored'
|
activity_log.result = 'ignored'
|
||||||
|
@ -920,7 +925,8 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
||||||
ap_announce_id=announce_id,
|
ap_announce_id=announce_id,
|
||||||
type=constants.POST_TYPE_ARTICLE,
|
type=constants.POST_TYPE_ARTICLE,
|
||||||
up_votes=1,
|
up_votes=1,
|
||||||
score=instance_weight(user.ap_domain)
|
score=instance_weight(user.ap_domain),
|
||||||
|
instance_id=user.instance_id
|
||||||
)
|
)
|
||||||
if 'source' in request_json['object'] and request_json['object']['source']['mediaType'] == 'text/markdown':
|
if 'source' in request_json['object'] and request_json['object']['source']['mediaType'] == 'text/markdown':
|
||||||
post.body = request_json['object']['source']['content']
|
post.body = request_json['object']['source']['content']
|
||||||
|
@ -972,11 +978,15 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
||||||
|
|
||||||
if post.image_id:
|
if post.image_id:
|
||||||
make_image_sizes(post.image_id, 266, None, 'posts')
|
make_image_sizes(post.image_id, 266, None, 'posts')
|
||||||
|
if user.reputation > 100:
|
||||||
vote = PostVote(user_id=user.id, author_id=post.user_id,
|
vote = PostVote(user_id=1, author_id=post.user_id,
|
||||||
post_id=post.id,
|
post_id=post.id,
|
||||||
effect=instance_weight(user.ap_domain))
|
effect=instance_weight(user.ap_domain))
|
||||||
db.session.add(vote)
|
db.session.add(vote)
|
||||||
|
post.up_votes += 1
|
||||||
|
post.score += 1
|
||||||
|
post.ranking += 1
|
||||||
|
db.session.commit()
|
||||||
return post
|
return post
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,10 @@ from app.activitypub.routes import process_inbox_request, process_delete_request
|
||||||
from app.activitypub.signature import post_request
|
from app.activitypub.signature import post_request
|
||||||
from app.activitypub.util import default_context
|
from app.activitypub.util import default_context
|
||||||
from app.admin.forms import FederationForm, SiteMiscForm, SiteProfileForm, EditCommunityForm, EditUserForm
|
from app.admin.forms import FederationForm, SiteMiscForm, SiteProfileForm, EditCommunityForm, EditUserForm
|
||||||
|
from app.admin.util import unsubscribe_from_everything_then_delete, unsubscribe_from_community
|
||||||
from app.community.util import save_icon_file, save_banner_file
|
from app.community.util import save_icon_file, save_banner_file
|
||||||
from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \
|
from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \
|
||||||
User, Instance, File
|
User, Instance, File, Report
|
||||||
from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html
|
from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html
|
||||||
from app.admin import bp
|
from app.admin import bp
|
||||||
|
|
||||||
|
@ -277,13 +278,21 @@ def unsubscribe_everyone_then_delete_task(community_id):
|
||||||
def admin_users():
|
def admin_users():
|
||||||
|
|
||||||
page = request.args.get('page', 1, type=int)
|
page = request.args.get('page', 1, type=int)
|
||||||
|
search = request.args.get('search', '')
|
||||||
|
local_remote = request.args.get('local_remote', '')
|
||||||
|
|
||||||
users = User.query.filter_by(deleted=False).order_by(User.user_name).paginate(page=page, per_page=1000, error_out=False)
|
users = User.query.filter_by(deleted=False)
|
||||||
|
if local_remote == 'local':
|
||||||
|
users = users.filter_by(ap_id=None)
|
||||||
|
if local_remote == 'remote':
|
||||||
|
users = users.filter(User.ap_id != None)
|
||||||
|
users = users.order_by(User.user_name).paginate(page=page, per_page=1000, error_out=False)
|
||||||
|
|
||||||
next_url = url_for('admin.admin_users', page=users.next_num) if users.has_next else None
|
next_url = url_for('admin.admin_users', page=users.next_num) if users.has_next else None
|
||||||
prev_url = url_for('admin.admin_users', page=users.prev_num) if users.has_prev and page != 1 else None
|
prev_url = url_for('admin.admin_users', page=users.prev_num) if users.has_prev and page != 1 else None
|
||||||
|
|
||||||
return render_template('admin/users.html', title=_('Users'), next_url=next_url, prev_url=prev_url, users=users)
|
return render_template('admin/users.html', title=_('Users'), next_url=next_url, prev_url=prev_url, users=users,
|
||||||
|
local_remote=local_remote, search=search)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/user/<int:user_id>/edit', methods=['GET', 'POST'])
|
@bp.route('/user/<int:user_id>/edit', methods=['GET', 'POST'])
|
||||||
|
@ -370,68 +379,24 @@ def admin_user_delete(user_id):
|
||||||
return redirect(url_for('admin.admin_users'))
|
return redirect(url_for('admin.admin_users'))
|
||||||
|
|
||||||
|
|
||||||
def unsubscribe_from_everything_then_delete(user_id):
|
@bp.route('/reports', methods=['GET'])
|
||||||
if current_app.debug:
|
@login_required
|
||||||
unsubscribe_from_everything_then_delete_task(user_id)
|
@permission_required('administer all users')
|
||||||
else:
|
def admin_reports():
|
||||||
unsubscribe_from_everything_then_delete_task.delay(user_id)
|
|
||||||
|
|
||||||
|
page = request.args.get('page', 1, type=int)
|
||||||
|
search = request.args.get('search', '')
|
||||||
|
local_remote = request.args.get('local_remote', '')
|
||||||
|
|
||||||
@celery.task
|
reports = Report.query.filter_by(status=0)
|
||||||
def unsubscribe_from_everything_then_delete_task(user_id):
|
if local_remote == 'local':
|
||||||
user = User.query.get(user_id)
|
reports = reports.filter_by(ap_id=None)
|
||||||
if user:
|
if local_remote == 'remote':
|
||||||
|
reports = reports.filter(Report.ap_id != None)
|
||||||
|
reports = reports.order_by(desc(Report.created_at)).paginate(page=page, per_page=1000, error_out=False)
|
||||||
|
|
||||||
# unsubscribe
|
next_url = url_for('admin.admin_reports', page=reports.next_num) if reports.has_next else None
|
||||||
communities = CommunityMember.query.filter_by(user_id=user_id).all()
|
prev_url = url_for('admin.admin_reports', page=reports.prev_num) if reports.has_prev and page != 1 else None
|
||||||
for membership in communities:
|
|
||||||
community = Community.query.get(membership.community_id)
|
|
||||||
unsubscribe_from_community(community, user)
|
|
||||||
|
|
||||||
# federate deletion of account
|
return render_template('admin/reports.html', title=_('Reports'), next_url=next_url, prev_url=prev_url, reports=reports,
|
||||||
if user.is_local():
|
local_remote=local_remote, search=search)
|
||||||
instances = Instance.query.all()
|
|
||||||
site = Site.query.get(1)
|
|
||||||
payload = {
|
|
||||||
"@context": default_context(),
|
|
||||||
"actor": user.ap_profile_id,
|
|
||||||
"id": f"{user.ap_profile_id}#delete",
|
|
||||||
"object": user.ap_profile_id,
|
|
||||||
"to": [
|
|
||||||
"https://www.w3.org/ns/activitystreams#Public"
|
|
||||||
],
|
|
||||||
"type": "Delete"
|
|
||||||
}
|
|
||||||
for instance in instances:
|
|
||||||
if instance.inbox and instance.id != 1:
|
|
||||||
post_request(instance.inbox, payload, site.private_key,
|
|
||||||
f"https://{current_app.config['SERVER_NAME']}#main-key")
|
|
||||||
|
|
||||||
user.deleted = True
|
|
||||||
user.delete_dependencies()
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def unsubscribe_from_community(community, user):
|
|
||||||
undo_id = f"https://{current_app.config['SERVER_NAME']}/activities/undo/" + gibberish(15)
|
|
||||||
follow = {
|
|
||||||
"actor": f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}",
|
|
||||||
"to": [community.ap_profile_id],
|
|
||||||
"object": community.ap_profile_id,
|
|
||||||
"type": "Follow",
|
|
||||||
"id": f"https://{current_app.config['SERVER_NAME']}/activities/follow/{gibberish(15)}"
|
|
||||||
}
|
|
||||||
undo = {
|
|
||||||
'actor': user.profile_id(),
|
|
||||||
'to': [community.ap_profile_id],
|
|
||||||
'type': 'Undo',
|
|
||||||
'id': undo_id,
|
|
||||||
'object': follow
|
|
||||||
}
|
|
||||||
activity = ActivityPubLog(direction='out', activity_id=undo_id, activity_type='Undo',
|
|
||||||
activity_json=json.dumps(undo), result='processing')
|
|
||||||
db.session.add(activity)
|
|
||||||
db.session.commit()
|
|
||||||
post_request(community.ap_inbox_url, undo, user.private_key, user.profile_id() + '#main-key')
|
|
||||||
activity.result = 'success'
|
|
||||||
db.session.commit()
|
|
72
app/admin/util.py
Normal file
72
app/admin/util.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
from flask import request, abort, g, current_app, json
|
||||||
|
from app import db, cache, celery
|
||||||
|
from app.activitypub.signature import post_request
|
||||||
|
from app.activitypub.util import default_context
|
||||||
|
from app.models import User, Community, Instance, Site, ActivityPubLog, CommunityMember
|
||||||
|
from app.utils import gibberish
|
||||||
|
|
||||||
|
|
||||||
|
def unsubscribe_from_everything_then_delete(user_id):
|
||||||
|
if current_app.debug:
|
||||||
|
unsubscribe_from_everything_then_delete_task(user_id)
|
||||||
|
else:
|
||||||
|
unsubscribe_from_everything_then_delete_task.delay(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task
|
||||||
|
def unsubscribe_from_everything_then_delete_task(user_id):
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
if user:
|
||||||
|
# unsubscribe
|
||||||
|
communities = CommunityMember.query.filter_by(user_id=user_id).all()
|
||||||
|
for membership in communities:
|
||||||
|
community = Community.query.get(membership.community_id)
|
||||||
|
unsubscribe_from_community(community, user)
|
||||||
|
|
||||||
|
# federate deletion of account
|
||||||
|
if user.is_local():
|
||||||
|
instances = Instance.query.all()
|
||||||
|
site = Site.query.get(1)
|
||||||
|
payload = {
|
||||||
|
"@context": default_context(),
|
||||||
|
"actor": user.ap_profile_id,
|
||||||
|
"id": f"{user.ap_profile_id}#delete",
|
||||||
|
"object": user.ap_profile_id,
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"type": "Delete"
|
||||||
|
}
|
||||||
|
for instance in instances:
|
||||||
|
if instance.inbox and instance.id != 1:
|
||||||
|
post_request(instance.inbox, payload, site.private_key,
|
||||||
|
f"https://{current_app.config['SERVER_NAME']}#main-key")
|
||||||
|
|
||||||
|
user.deleted = True
|
||||||
|
user.delete_dependencies()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def unsubscribe_from_community(community, user):
|
||||||
|
undo_id = f"https://{current_app.config['SERVER_NAME']}/activities/undo/" + gibberish(15)
|
||||||
|
follow = {
|
||||||
|
"actor": f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}",
|
||||||
|
"to": [community.ap_profile_id],
|
||||||
|
"object": community.ap_profile_id,
|
||||||
|
"type": "Follow",
|
||||||
|
"id": f"https://{current_app.config['SERVER_NAME']}/activities/follow/{gibberish(15)}"
|
||||||
|
}
|
||||||
|
undo = {
|
||||||
|
'actor': user.profile_id(),
|
||||||
|
'to': [community.ap_profile_id],
|
||||||
|
'type': 'Undo',
|
||||||
|
'id': undo_id,
|
||||||
|
'object': follow
|
||||||
|
}
|
||||||
|
activity = ActivityPubLog(direction='out', activity_id=undo_id, activity_type='Undo',
|
||||||
|
activity_json=json.dumps(undo), result='processing')
|
||||||
|
db.session.add(activity)
|
||||||
|
db.session.commit()
|
||||||
|
post_request(community.ap_inbox_url, undo, user.private_key, user.profile_id() + '#main-key')
|
||||||
|
activity.result = 'success'
|
||||||
|
db.session.commit()
|
|
@ -17,7 +17,7 @@ from app.models import User, Community, CommunityMember, CommunityJoinRequest, C
|
||||||
from app.community import bp
|
from app.community import bp
|
||||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
||||||
shorten_string, markdown_to_text, domain_from_url, validate_image, gibberish, community_membership, ap_datetime, \
|
shorten_string, markdown_to_text, domain_from_url, validate_image, gibberish, community_membership, ap_datetime, \
|
||||||
request_etag_matches, return_304
|
request_etag_matches, return_304, instance_banned
|
||||||
from feedgen.feed import FeedGenerator
|
from feedgen.feed import FeedGenerator
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
|
|
||||||
|
@ -360,6 +360,10 @@ def add_post(actor):
|
||||||
"object": page,
|
"object": page,
|
||||||
'@context': default_context()
|
'@context': default_context()
|
||||||
}
|
}
|
||||||
|
if post.type == POST_TYPE_LINK:
|
||||||
|
page.attachment = [{'href': post.url, 'type': 'Link'}]
|
||||||
|
if post.image_id:
|
||||||
|
page.image = [{'type': 'Image', 'url': post.image.source_url}]
|
||||||
if not community.is_local(): # this is a remote community - send the post to the instance that hosts it
|
if not community.is_local(): # this is a remote community - send the post to the instance that hosts it
|
||||||
success = post_request(community.ap_inbox_url, create, current_user.private_key,
|
success = post_request(community.ap_inbox_url, create, current_user.private_key,
|
||||||
current_user.ap_profile_id + '#main-key')
|
current_user.ap_profile_id + '#main-key')
|
||||||
|
@ -384,7 +388,7 @@ def add_post(actor):
|
||||||
|
|
||||||
sent_to = 0
|
sent_to = 0
|
||||||
for instance in community.following_instances():
|
for instance in community.following_instances():
|
||||||
if instance[1] and not current_user.has_blocked_instance(instance[0]):
|
if instance[1] and not current_user.has_blocked_instance(instance[0]) and not instance_banned(instance[1]):
|
||||||
send_to_remote_instance(instance[1], community.id, announce)
|
send_to_remote_instance(instance[1], community.id, announce)
|
||||||
sent_to += 1
|
sent_to += 1
|
||||||
if sent_to:
|
if sent_to:
|
||||||
|
|
|
@ -255,10 +255,17 @@ def save_post(form, post: Post):
|
||||||
else:
|
else:
|
||||||
raise Exception('invalid post type')
|
raise Exception('invalid post type')
|
||||||
if post.id is None:
|
if post.id is None:
|
||||||
postvote = PostVote(user_id=current_user.id, author_id=current_user.id, post=post, effect=1.0)
|
if current_user.reputation > 100:
|
||||||
post.up_votes = 1
|
postvote = PostVote(user_id=1, author_id=current_user.id, post=post, effect=1.0)
|
||||||
post.score = 1
|
post.up_votes = 1
|
||||||
db.session.add(postvote)
|
post.score = 1
|
||||||
|
post.ranking = 1
|
||||||
|
db.session.add(postvote)
|
||||||
|
if current_user.reputation < -100:
|
||||||
|
postvote = PostVote(user_id=1, author_id=current_user.id, post=post, effect=-1.0)
|
||||||
|
post.score = -1
|
||||||
|
post.ranking = -1
|
||||||
|
db.session.add(postvote)
|
||||||
db.session.add(post)
|
db.session.add(post)
|
||||||
g.site.last_active = utcnow()
|
g.site.last_active = utcnow()
|
||||||
|
|
||||||
|
|
|
@ -175,6 +175,7 @@ class Community(db.Model):
|
||||||
else:
|
else:
|
||||||
return self.ap_id
|
return self.ap_id
|
||||||
|
|
||||||
|
@cache.memoize(timeout=30)
|
||||||
def moderators(self):
|
def moderators(self):
|
||||||
return CommunityMember.query.filter((CommunityMember.community_id == self.id) &
|
return CommunityMember.query.filter((CommunityMember.community_id == self.id) &
|
||||||
(or_(
|
(or_(
|
||||||
|
@ -210,7 +211,7 @@ class Community(db.Model):
|
||||||
# returns a list of tuples (instance.id, instance.inbox)
|
# returns a list of tuples (instance.id, instance.inbox)
|
||||||
def following_instances(self):
|
def following_instances(self):
|
||||||
sql = 'select distinct i.id, i.inbox from "instance" as i inner join "user" as u on u.instance_id = i.id inner join "community_member" as cm on cm.user_id = u.id '
|
sql = 'select distinct i.id, i.inbox from "instance" as i inner join "user" as u on u.instance_id = i.id inner join "community_member" as cm on cm.user_id = u.id '
|
||||||
sql += 'where cm.community_id = :community_id and cm.is_banned = false and i.id <> 1'
|
sql += 'where cm.community_id = :community_id and cm.is_banned = false and i.id <> 1 and i.dormant = false and i.gone_forever = false'
|
||||||
return db.session.execute(text(sql), {'community_id': self.id})
|
return db.session.execute(text(sql), {'community_id': self.id})
|
||||||
|
|
||||||
def delete_dependencies(self):
|
def delete_dependencies(self):
|
||||||
|
@ -898,6 +899,17 @@ class Report(db.Model):
|
||||||
created_at = db.Column(db.DateTime, default=utcnow)
|
created_at = db.Column(db.DateTime, default=utcnow)
|
||||||
updated = db.Column(db.DateTime, default=utcnow)
|
updated = db.Column(db.DateTime, default=utcnow)
|
||||||
|
|
||||||
|
# textual representation of self.type
|
||||||
|
def type_text(self):
|
||||||
|
types = ('User', 'Post', 'Comment', 'Community')
|
||||||
|
if self.type is None:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return types[self.type]
|
||||||
|
|
||||||
|
def is_local(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class IpBan(db.Model):
|
class IpBan(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
|
@ -18,7 +18,7 @@ from app.models import Post, PostReply, \
|
||||||
from app.post import bp
|
from app.post import bp
|
||||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
||||||
shorten_string, markdown_to_text, domain_from_url, validate_image, gibberish, ap_datetime, return_304, \
|
shorten_string, markdown_to_text, domain_from_url, validate_image, gibberish, ap_datetime, return_304, \
|
||||||
request_etag_matches, ip_address, user_ip_banned
|
request_etag_matches, ip_address, user_ip_banned, instance_banned
|
||||||
|
|
||||||
|
|
||||||
def show_post(post_id: int):
|
def show_post(post_id: int):
|
||||||
|
@ -68,9 +68,19 @@ def show_post(post_id: int):
|
||||||
db.session.add(reply)
|
db.session.add(reply)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
reply.ap_id = reply.profile_id()
|
reply.ap_id = reply.profile_id()
|
||||||
reply_vote = PostReplyVote(user_id=current_user.id, author_id=current_user.id, post_reply_id=reply.id,
|
if current_user.reputation > 100:
|
||||||
effect=1.0)
|
reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id,
|
||||||
db.session.add(reply_vote)
|
effect=1.0)
|
||||||
|
reply.up_votes += 1
|
||||||
|
reply.score += 1
|
||||||
|
reply.ranking += 1
|
||||||
|
db.session.add(reply_vote)
|
||||||
|
elif current_user.reputation < -100:
|
||||||
|
reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id,
|
||||||
|
effect=-1.0)
|
||||||
|
reply.score -= 1
|
||||||
|
reply.ranking -= 1
|
||||||
|
db.session.add(reply_vote)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
form.body.data = ''
|
form.body.data = ''
|
||||||
flash('Your comment has been added.')
|
flash('Your comment has been added.')
|
||||||
|
@ -133,7 +143,7 @@ def show_post(post_id: int):
|
||||||
}
|
}
|
||||||
|
|
||||||
for instance in post.community.following_instances():
|
for instance in post.community.following_instances():
|
||||||
if instance[1] and not current_user.has_blocked_instance(instance[0]):
|
if instance[1] and not current_user.has_blocked_instance(instance[0]) and not instance_banned(instance[1]):
|
||||||
send_to_remote_instance(instance[1], post.community.id, announce)
|
send_to_remote_instance(instance[1], post.community.id, announce)
|
||||||
|
|
||||||
return redirect(url_for('activitypub.post_ap', post_id=post_id)) # redirect to current page to avoid refresh resubmitting the form
|
return redirect(url_for('activitypub.post_ap', post_id=post_id)) # redirect to current page to avoid refresh resubmitting the form
|
||||||
|
@ -225,7 +235,7 @@ def post_vote(post_id: int, vote_direction):
|
||||||
'object': action_json
|
'object': action_json
|
||||||
}
|
}
|
||||||
for instance in post.community.following_instances():
|
for instance in post.community.following_instances():
|
||||||
if instance[1] and not current_user.has_blocked_instance(instance[0]):
|
if instance[1] and not current_user.has_blocked_instance(instance[0]) and not instance_banned(instance[1]):
|
||||||
send_to_remote_instance(instance[1], post.community.id, announce)
|
send_to_remote_instance(instance[1], post.community.id, announce)
|
||||||
else:
|
else:
|
||||||
success = post_request(post.community.ap_inbox_url, action_json, current_user.private_key,
|
success = post_request(post.community.ap_inbox_url, action_json, current_user.private_key,
|
||||||
|
@ -368,9 +378,19 @@ def add_reply(post_id: int, comment_id: int):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
reply.ap_id = reply.profile_id()
|
reply.ap_id = reply.profile_id()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
reply_vote = PostReplyVote(user_id=current_user.id, author_id=current_user.id, post_reply_id=reply.id,
|
if current_user.reputation > 100:
|
||||||
effect=1.0)
|
reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id,
|
||||||
db.session.add(reply_vote)
|
effect=1.0)
|
||||||
|
reply.up_votes += 1
|
||||||
|
reply.score += 1
|
||||||
|
reply.ranking += 1
|
||||||
|
db.session.add(reply_vote)
|
||||||
|
elif current_user.reputation < -100:
|
||||||
|
reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id,
|
||||||
|
effect=-1.0)
|
||||||
|
reply.score -= 1
|
||||||
|
reply.ranking -= 1
|
||||||
|
db.session.add(reply_vote)
|
||||||
post.reply_count = post_reply_count(post.id)
|
post.reply_count = post_reply_count(post.id)
|
||||||
post.last_active = post.community.last_active = utcnow()
|
post.last_active = post.community.last_active = utcnow()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -452,7 +472,7 @@ def add_reply(post_id: int, comment_id: int):
|
||||||
}
|
}
|
||||||
|
|
||||||
for instance in post.community.following_instances():
|
for instance in post.community.following_instances():
|
||||||
if instance[1] and not current_user.has_blocked_instance(instance[0]):
|
if instance[1] and not current_user.has_blocked_instance(instance[0]) and not instance_banned(instance[1]):
|
||||||
send_to_remote_instance(instance[1], post.community.id, announce)
|
send_to_remote_instance(instance[1], post.community.id, announce)
|
||||||
|
|
||||||
if reply.depth <= constants.THREAD_CUTOFF_DEPTH:
|
if reply.depth <= constants.THREAD_CUTOFF_DEPTH:
|
||||||
|
|
|
@ -570,6 +570,10 @@ nav.navbar {
|
||||||
width: 96%;
|
width: 96%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reported {
|
||||||
|
background-color: antiquewhite;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
background-color: #777;
|
background-color: #777;
|
||||||
|
|
|
@ -254,6 +254,10 @@ nav.navbar {
|
||||||
width: 96%;
|
width: 96%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reported {
|
||||||
|
background-color: antiquewhite;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
background-color: $dark-grey;
|
background-color: $dark-grey;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<a href="{{ url_for('admin.admin_misc') }}">{{ _('Misc settings') }}</a> |
|
<a href="{{ url_for('admin.admin_misc') }}">{{ _('Misc settings') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a> |
|
<a href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_users') }}">{{ _('Users') }}</a> |
|
<a href="{{ url_for('admin.admin_users') }}">{{ _('Users') }}</a> |
|
||||||
|
<a href="{{ url_for('admin.admin_reports') }}">{{ _('Moderation') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_federation') }}">{{ _('Federation') }}</a> |
|
<a href="{{ url_for('admin.admin_federation') }}">{{ _('Federation') }}</a> |
|
||||||
<a href="{{ url_for('admin.admin_activities') }}">{{ _('Activities') }}</a>
|
<a href="{{ url_for('admin.admin_activities') }}">{{ _('Activities') }}</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
63
app/templates/admin/reports.html
Normal file
63
app/templates/admin/reports.html
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% from 'bootstrap/form.html' import render_form %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
{% include 'admin/_nav.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<form method="get">
|
||||||
|
<input type="search" name="search" value="{{ search }}">
|
||||||
|
<input type="radio" name="local_remote" value="local" id="local_remote_local" {{ 'checked' if local_remote == 'local' }}><label for="local_remote_local"> Local</label>
|
||||||
|
<input type="radio" name="local_remote" value="remote" id="local_remote_remote" {{ 'checked' if local_remote == 'remote' }}><label for="local_remote_remote"> Remote</label>
|
||||||
|
<input type="submit" name="submit" value="Search" class="btn btn-primary">
|
||||||
|
</form>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr>
|
||||||
|
<th>Local/Remote</th>
|
||||||
|
<th>Reasons</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
{% for report in reports %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ 'Local' if report.is_local() else 'Remote' }}</td>
|
||||||
|
<td>{{ report.reasons }}</td>
|
||||||
|
<td>{{ report.description }}</td>
|
||||||
|
<td>{{ report.type_text() }}</td>
|
||||||
|
<td>{{ report.created_at }}</td>
|
||||||
|
<td>
|
||||||
|
{% if report.suspect_post_reply_id %}
|
||||||
|
<a href="/post/{{ report.suspect_post_id }}#comment_{{ report.suspect_post_reply_id }}">View</a>
|
||||||
|
{% elif report.suspect_post_id %}
|
||||||
|
<a href="/post/{{ report.suspect_post_id }}">View</a>
|
||||||
|
{% elif report.suspect_user_id %}
|
||||||
|
<a href="/user/{{ report.suspect_user_id }}">View</a>
|
||||||
|
{% elif report.suspect_community_id %}
|
||||||
|
<a href="/user/{{ report.suspect_community_id }}">View</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<nav aria-label="Pagination" class="mt-4">
|
||||||
|
{% if prev_url %}
|
||||||
|
<a href="{{ prev_url }}" class="btn btn-primary">
|
||||||
|
<span aria-hidden="true">←</span> {{ _('Previous page') }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if next_url %}
|
||||||
|
<a href="{{ next_url }}" class="btn btn-primary">
|
||||||
|
{{ _('Next page') }} <span aria-hidden="true">→</span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -11,9 +11,9 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<form method="get">
|
<form method="get">
|
||||||
<input type="search" name="search">
|
<input type="search" name="search" value="{{ search }}">
|
||||||
<input type="radio" name="local_remote" value="local" id="local_remote_local"><label for="local_remote_local"> Local</label>
|
<input type="radio" name="local_remote" value="local" id="local_remote_local" {{ 'checked' if local_remote == 'local' }}><label for="local_remote_local"> Local</label>
|
||||||
<input type="radio" name="local_remote" value="remote" id="local_remote_remote"><label for="local_remote_remote"> Remote</label>
|
<input type="radio" name="local_remote" value="remote" id="local_remote_remote" {{ 'checked' if local_remote == 'remote' }}><label for="local_remote_remote"> Remote</label>
|
||||||
<input type="submit" name="submit" value="Search" class="btn btn-primary">
|
<input type="submit" name="submit" value="Search" class="btn btn-primary">
|
||||||
</form>
|
</form>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
|
|
|
@ -18,7 +18,9 @@
|
||||||
<img src="/static/images/external_link_black.svg" class="external_link_icon" alt="External link" />
|
<img src="/static/images/external_link_black.svg" class="external_link_icon" alt="External link" />
|
||||||
</a></small></p>
|
</a></small></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p><small>submitted {{ moment(post.posted_at).fromNow() }} by {{ render_username(post.author) }}
|
<p>{% if post.reports and current_user.is_authenticated and post.community.is_moderator(current_user) %}
|
||||||
|
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
|
||||||
|
{% endif %}<small>submitted {{ moment(post.posted_at).fromNow() }} by {{ render_username(post.author) }}
|
||||||
{% if post.edited_at %} edited {{ moment(post.edited_at).fromNow() }}{% endif %}
|
{% if post.edited_at %} edited {{ moment(post.edited_at).fromNow() }}{% endif %}
|
||||||
</small></p>
|
</small></p>
|
||||||
<div class="post_image">
|
<div class="post_image">
|
||||||
|
@ -46,9 +48,11 @@
|
||||||
width="{{ post.image.thumbnail_width }}" height="{{ post.image.thumbnail_height }}" /></a>
|
width="{{ post.image.thumbnail_width }}" height="{{ post.image.thumbnail_height }}" /></a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="small">submitted {{ moment(post.posted_at).fromNow() }} by
|
<p>{% if post.reports and current_user.is_authenticated and post.community.is_moderator(current_user) %}
|
||||||
|
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
|
||||||
|
{% endif %}<small>submitted {{ moment(post.posted_at).fromNow() }} by
|
||||||
{{ render_username(post.author) }}
|
{{ render_username(post.author) }}
|
||||||
{% if post.edited_at %} edited {{ moment(post.edited_at).fromNow() }}{% endif %}
|
{% if post.edited_at %} edited {{ moment(post.edited_at).fromNow() }}{% endif %}</small>
|
||||||
</p>
|
</p>
|
||||||
{% if post.type == POST_TYPE_LINK %}
|
{% if post.type == POST_TYPE_LINK %}
|
||||||
<p><small><a href="{{ post.url }}" rel="nofollow ugc">{{ post.url|shorten_url }}
|
<p><small><a href="{{ post.url }}" rel="nofollow ugc">{{ post.url|shorten_url }}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="post_teaser">
|
<div class="post_teaser {{ 'reported' if post.reports and current_user.is_authenticated and post.community.is_moderator() }}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="row main_row">
|
<div class="row main_row">
|
||||||
|
@ -29,6 +29,9 @@
|
||||||
</a>
|
</a>
|
||||||
<span class="domain_link">(<a href="/d/{{ post.domain_id }}">{{ post.domain.name }}</a>)</span>
|
<span class="domain_link">(<a href="/d/{{ post.domain_id }}">{{ post.domain.name }}</a>)</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if post.reports and current_user.is_authenticated and post.community.is_moderator(current_user) %}
|
||||||
|
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
|
||||||
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
<span class="small">{{ render_username(post.author) }} · {{ moment(post.posted_at).fromNow() }}</span>
|
<span class="small">{{ render_username(post.author) }} · {{ moment(post.posted_at).fromNow() }}</span>
|
||||||
|
|
||||||
|
|
|
@ -84,8 +84,11 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if comment['comment'].author.id == post.author.id %}<span title="Submitter of original post" aria-label="submitter" class="small">[OP]</span>{% endif %}
|
{% if comment['comment'].author.id == post.author.id %}<span title="Submitter of original post" aria-label="submitter" class="small">[OP]</span>{% endif %}
|
||||||
<span class="text-muted small">{{ moment(comment['comment'].posted_at).fromNow(refresh=True) }}{% if comment['comment'].edited_at %}, edited {{ moment(comment['comment'].edited_at).fromNow(refresh=True) }} {% endif %}</span>
|
<span class="text-muted small">{{ moment(comment['comment'].posted_at).fromNow(refresh=True) }}{% if comment['comment'].edited_at %}, edited {{ moment(comment['comment'].edited_at).fromNow(refresh=True) }} {% endif %}</span>
|
||||||
|
{% if comment['comment'].reports and current_user.is_authenticated and post.community.is_moderator(current_user)%}
|
||||||
|
<span class="red fe fe-report" title="{{ _('Reported. Check comment for issues.') }}"></span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="comment_body hidable">
|
<div class="comment_body hidable {% if comment['comment'].reports and current_user.is_authenticated and post.community.is_moderator(current_user) %}reported{% endif %}">
|
||||||
{{ comment['comment'].body_html | safe }}
|
{{ comment['comment'].body_html | safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -360,6 +360,11 @@ def user_ip_banned() -> bool:
|
||||||
return current_ip_address in banned_ip_addresses()
|
return current_ip_address in banned_ip_addresses()
|
||||||
|
|
||||||
|
|
||||||
|
def instance_banned(domain: str) -> bool:
|
||||||
|
banned = BannedInstances.query.filter_by(domain=domain).first()
|
||||||
|
return banned is not None
|
||||||
|
|
||||||
|
|
||||||
def user_cookie_banned() -> bool:
|
def user_cookie_banned() -> bool:
|
||||||
cookie = request.cookies.get('sesion', None)
|
cookie = request.cookies.get('sesion', None)
|
||||||
return cookie is not None
|
return cookie is not None
|
||||||
|
|
Loading…
Reference in a new issue