Files
contentdb/app/blueprints/report/__init__.py
2025-08-26 15:04:01 +01:00

118 lines
4.2 KiB
Python

# ContentDB
# Copyright (C) 2022 rubenwardy
#
# 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 by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import Blueprint, request, render_template, url_for, abort
from flask_babel import lazy_gettext
from flask_login import current_user
from flask_wtf import FlaskForm
from werkzeug.utils import redirect
from wtforms import TextAreaField, SubmitField, URLField, StringField, RadioField
from wtforms.validators import InputRequired, Length, Optional
from app.models import User, UserRank, Report, db, AuditSeverity, Thread
from app.tasks.webhooktasks import post_discord_webhook
from app.utils import is_no, abs_url_samesite, normalize_line_endings, rank_required, add_audit_log, abs_url_for, \
add_replies
bp = Blueprint("report", __name__)
class ReportForm(FlaskForm):
url = URLField(lazy_gettext("URL"), [Optional()])
title = StringField(lazy_gettext("Subject / Title"), [InputRequired(), Length(10, 300)])
message = TextAreaField(lazy_gettext("Message"), [Optional(), Length(0, 10000)], filters=[normalize_line_endings])
submit = SubmitField(lazy_gettext("Report"))
@bp.route("/report/", methods=["GET", "POST"])
def report():
is_anon = not current_user.is_authenticated or not is_no(request.args.get("anon"))
url = request.args.get("url")
if url:
if url.startswith("/report/"):
abort(404)
url = abs_url_samesite(url)
form = ReportForm(formdata=request.form) if current_user.is_authenticated else None
if form and request.method == "GET":
form.url.data = url
form.message.data = request.args.get("message", "")
if form and form.validate_on_submit():
report = Report()
report.user = current_user if current_user.is_authenticated else None
form.populate_obj(report)
if not current_user.is_authenticated:
ip_addr = request.headers.get("X-Forwarded-For") or request.remote_addr
report.message = ip_addr + "\n\n" + report.message
db.session.add(report)
db.session.flush()
if current_user.is_authenticated:
add_audit_log(AuditSeverity.USER, current_user, f"New report: {report.title}",
url_for("report.view", rid=report.id))
db.session.commit()
abs_url = abs_url_for("report.view", rid=report.id)
msg = f"**New Report**\nReport on `{report.url}`\n\n{report.title}\n\nView: {abs_url}"
post_discord_webhook.delay(None if is_anon else current_user.username, msg, True)
return redirect(url_for("report.view", rid=report.id))
return render_template("report/report.html", form=form, url=url, is_anon=is_anon, noindex=url is not None)
@bp.route("/admin/reports/")
@rank_required(UserRank.MODERATOR)
def list_all():
reports = Report.query.order_by(db.asc(Report.is_resolved), db.asc(Report.created_at)).all()
return render_template("report/list.html", reports=reports)
class ResolveForm(FlaskForm):
completed = SubmitField(lazy_gettext("Completed / resolved"))
removed = SubmitField(lazy_gettext("Content removed"))
invalid = SubmitField(lazy_gettext("Invalid / No action taken"))
@bp.route("/admin/reports/<int:rid>/", methods=["GET", "POST"])
@rank_required(UserRank.MODERATOR)
def view(rid: int):
report = Report.query.get_or_404(rid)
resolve_form = ResolveForm(request.form)
if resolve_form.validate_on_submit():
if resolve_form.completed.data:
outcome = "completed"
elif resolve_form.removed.data:
outcome = "removed"
elif resolve_form.invalid.data:
outcome = "invalid"
else:
abort(400)
report.is_resolved = True
url = url_for("report.view", rid=report.id)
add_audit_log(AuditSeverity.MODERATION, current_user, f"Resolved report as {outcome} \"{report.title}\"", url)
db.session.commit()
return render_template("report/view.html", report=report, resolve_form=resolve_form)