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
|
||||
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.>
|
||||
Copyright (C) <year> <name of author>
|
||||
PieFed, a federated forum
|
||||
Copyright (C) 2024 Rimu Atkinson
|
||||
|
||||
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
|
||||
|
|
|
@ -893,10 +893,15 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep
|
|||
community.last_active = post.last_active = utcnow()
|
||||
activity_log.result = 'success'
|
||||
db.session.commit()
|
||||
vote = PostReplyVote(user_id=user.id, author_id=post_reply.user_id,
|
||||
post_reply_id=post_reply.id,
|
||||
effect=instance_weight(user.ap_domain))
|
||||
db.session.add(vote)
|
||||
if user.reputation > 100:
|
||||
vote = PostReplyVote(user_id=1, author_id=post_reply.user_id,
|
||||
post_reply_id=post_reply.id,
|
||||
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:
|
||||
activity_log.exception_message = 'Comments disabled, reply discarded'
|
||||
activity_log.result = 'ignored'
|
||||
|
@ -920,7 +925,8 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
|||
ap_announce_id=announce_id,
|
||||
type=constants.POST_TYPE_ARTICLE,
|
||||
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':
|
||||
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:
|
||||
make_image_sizes(post.image_id, 266, None, 'posts')
|
||||
|
||||
vote = PostVote(user_id=user.id, author_id=post.user_id,
|
||||
post_id=post.id,
|
||||
effect=instance_weight(user.ap_domain))
|
||||
db.session.add(vote)
|
||||
if user.reputation > 100:
|
||||
vote = PostVote(user_id=1, author_id=post.user_id,
|
||||
post_id=post.id,
|
||||
effect=instance_weight(user.ap_domain))
|
||||
db.session.add(vote)
|
||||
post.up_votes += 1
|
||||
post.score += 1
|
||||
post.ranking += 1
|
||||
db.session.commit()
|
||||
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.util import default_context
|
||||
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.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.admin import bp
|
||||
|
||||
|
@ -277,13 +278,21 @@ def unsubscribe_everyone_then_delete_task(community_id):
|
|||
def admin_users():
|
||||
|
||||
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
|
||||
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'])
|
||||
|
@ -370,68 +379,24 @@ def admin_user_delete(user_id):
|
|||
return redirect(url_for('admin.admin_users'))
|
||||
|
||||
|
||||
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)
|
||||
@bp.route('/reports', methods=['GET'])
|
||||
@login_required
|
||||
@permission_required('administer all users')
|
||||
def admin_reports():
|
||||
|
||||
page = request.args.get('page', 1, type=int)
|
||||
search = request.args.get('search', '')
|
||||
local_remote = request.args.get('local_remote', '')
|
||||
|
||||
@celery.task
|
||||
def unsubscribe_from_everything_then_delete_task(user_id):
|
||||
user = User.query.get(user_id)
|
||||
if user:
|
||||
reports = Report.query.filter_by(status=0)
|
||||
if local_remote == 'local':
|
||||
reports = reports.filter_by(ap_id=None)
|
||||
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
|
||||
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)
|
||||
next_url = url_for('admin.admin_reports', page=reports.next_num) if reports.has_next else None
|
||||
prev_url = url_for('admin.admin_reports', page=reports.prev_num) if reports.has_prev and page != 1 else None
|
||||
|
||||
# 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()
|
||||
return render_template('admin/reports.html', title=_('Reports'), next_url=next_url, prev_url=prev_url, reports=reports,
|
||||
local_remote=local_remote, search=search)
|
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.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, \
|
||||
request_etag_matches, return_304
|
||||
request_etag_matches, return_304, instance_banned
|
||||
from feedgen.feed import FeedGenerator
|
||||
from datetime import timezone
|
||||
|
||||
|
@ -360,6 +360,10 @@ def add_post(actor):
|
|||
"object": page,
|
||||
'@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
|
||||
success = post_request(community.ap_inbox_url, create, current_user.private_key,
|
||||
current_user.ap_profile_id + '#main-key')
|
||||
|
@ -384,7 +388,7 @@ def add_post(actor):
|
|||
|
||||
sent_to = 0
|
||||
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)
|
||||
sent_to += 1
|
||||
if sent_to:
|
||||
|
|
|
@ -255,10 +255,17 @@ def save_post(form, post: Post):
|
|||
else:
|
||||
raise Exception('invalid post type')
|
||||
if post.id is None:
|
||||
postvote = PostVote(user_id=current_user.id, author_id=current_user.id, post=post, effect=1.0)
|
||||
post.up_votes = 1
|
||||
post.score = 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.up_votes = 1
|
||||
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)
|
||||
g.site.last_active = utcnow()
|
||||
|
||||
|
|
|
@ -175,6 +175,7 @@ class Community(db.Model):
|
|||
else:
|
||||
return self.ap_id
|
||||
|
||||
@cache.memoize(timeout=30)
|
||||
def moderators(self):
|
||||
return CommunityMember.query.filter((CommunityMember.community_id == self.id) &
|
||||
(or_(
|
||||
|
@ -210,7 +211,7 @@ class Community(db.Model):
|
|||
# returns a list of tuples (instance.id, instance.inbox)
|
||||
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 += '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})
|
||||
|
||||
def delete_dependencies(self):
|
||||
|
@ -898,6 +899,17 @@ class Report(db.Model):
|
|||
created_at = 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):
|
||||
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.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, \
|
||||
request_etag_matches, ip_address, user_ip_banned
|
||||
request_etag_matches, ip_address, user_ip_banned, instance_banned
|
||||
|
||||
|
||||
def show_post(post_id: int):
|
||||
|
@ -68,9 +68,19 @@ def show_post(post_id: int):
|
|||
db.session.add(reply)
|
||||
db.session.commit()
|
||||
reply.ap_id = reply.profile_id()
|
||||
reply_vote = PostReplyVote(user_id=current_user.id, author_id=current_user.id, post_reply_id=reply.id,
|
||||
effect=1.0)
|
||||
db.session.add(reply_vote)
|
||||
if current_user.reputation > 100:
|
||||
reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id,
|
||||
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()
|
||||
form.body.data = ''
|
||||
flash('Your comment has been added.')
|
||||
|
@ -133,7 +143,7 @@ def show_post(post_id: int):
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
else:
|
||||
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()
|
||||
reply.ap_id = reply.profile_id()
|
||||
db.session.commit()
|
||||
reply_vote = PostReplyVote(user_id=current_user.id, author_id=current_user.id, post_reply_id=reply.id,
|
||||
effect=1.0)
|
||||
db.session.add(reply_vote)
|
||||
if current_user.reputation > 100:
|
||||
reply_vote = PostReplyVote(user_id=1, author_id=current_user.id, post_reply_id=reply.id,
|
||||
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.last_active = post.community.last_active = utcnow()
|
||||
db.session.commit()
|
||||
|
@ -452,7 +472,7 @@ def add_reply(post_id: int, comment_id: int):
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
if reply.depth <= constants.THREAD_CUTOFF_DEPTH:
|
||||
|
|
|
@ -570,6 +570,10 @@ nav.navbar {
|
|||
width: 96%;
|
||||
}
|
||||
|
||||
.reported {
|
||||
background-color: antiquewhite;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #777;
|
||||
|
|
|
@ -254,6 +254,10 @@ nav.navbar {
|
|||
width: 96%;
|
||||
}
|
||||
|
||||
.reported {
|
||||
background-color: antiquewhite;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: $dark-grey;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<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_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_activities') }}">{{ _('Activities') }}</a>
|
||||
</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="col">
|
||||
<form method="get">
|
||||
<input type="search" name="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="remote" id="local_remote_remote"><label for="local_remote_remote"> Remote</label>
|
||||
<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">
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
<img src="/static/images/external_link_black.svg" class="external_link_icon" alt="External link" />
|
||||
</a></small></p>
|
||||
{% 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 %}
|
||||
</small></p>
|
||||
<div class="post_image">
|
||||
|
@ -46,9 +48,11 @@
|
|||
width="{{ post.image.thumbnail_width }}" height="{{ post.image.thumbnail_height }}" /></a>
|
||||
</div>
|
||||
{% 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) }}
|
||||
{% if post.edited_at %} edited {{ moment(post.edited_at).fromNow() }}{% endif %}
|
||||
{% if post.edited_at %} edited {{ moment(post.edited_at).fromNow() }}{% endif %}</small>
|
||||
</p>
|
||||
{% if post.type == POST_TYPE_LINK %}
|
||||
<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="col-12">
|
||||
<div class="row main_row">
|
||||
|
@ -29,6 +29,9 @@
|
|||
</a>
|
||||
<span class="domain_link">(<a href="/d/{{ post.domain_id }}">{{ post.domain.name }}</a>)</span>
|
||||
{% 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>
|
||||
<span class="small">{{ render_username(post.author) }} · {{ moment(post.posted_at).fromNow() }}</span>
|
||||
|
||||
|
|
|
@ -84,8 +84,11 @@
|
|||
{% 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>
|
||||
{% 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 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 }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -360,6 +360,11 @@ def user_ip_banned() -> bool:
|
|||
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:
|
||||
cookie = request.cookies.get('sesion', None)
|
||||
return cookie is not None
|
||||
|
|
Loading…
Reference in a new issue