diff --git a/app/blueprints/report/__init__.py b/app/blueprints/report/__init__.py new file mode 100644 index 0000000..5b5af47 --- /dev/null +++ b/app/blueprints/report/__init__.py @@ -0,0 +1,66 @@ +# 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 . + +from flask import Blueprint, request, render_template, url_for +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, BooleanField +from wtforms.fields.html5 import URLField +from wtforms.validators import InputRequired, Optional, Length + +from app.models import User, UserRank +from app.tasks.emails import send_user_email +from app.tasks.webhooktasks import post_discord_webhook +from app.utils import isYes, isNo + +bp = Blueprint("report", __name__) + + +class ReportForm(FlaskForm): + url = URLField(lazy_gettext("URL"), [Optional()]) + message = TextAreaField(lazy_gettext("Message"), [InputRequired(), Length(10, 10000)]) + submit = SubmitField(lazy_gettext("Report")) + + +@bp.route("/report/", methods=["GET", "POST"]) +def report(): + is_anon = not current_user.is_authenticated or not isNo(request.args.get("anon")) + + form = ReportForm(formdata=request.form) + if request.method == "GET": + if "url" in request.args: + form.url.data = request.args["url"] + + if form.validate_on_submit(): + if current_user.is_authenticated: + user_info = f"{current_user.username}" + else: + user_info = request.headers.get("X-Forwarded-For") or request.remote_addr + + url = request.args.get("url") or form.url.data or "?" + text = f"{url}\n\n{form.message.data}" + + task = None + for admin in User.query.filter_by(rank=UserRank.ADMIN).all(): + task = send_user_email.delay(admin.email, f"User report from {user_info}", text) + + post_discord_webhook.delay(None if is_anon else current_user.username, f"**New Report**\n`{url}`\n\n{form.message.data}", True) + + return redirect(url_for("tasks.check", id=task.id, r=url_for("homepage.home"))) + + return render_template("report/index.html", form=form, url=request.args.get("url"), is_anon=is_anon) diff --git a/app/flatpages/help.md b/app/flatpages/help.md index 66d07eb..2eb481f 100644 --- a/app/flatpages/help.md +++ b/app/flatpages/help.md @@ -9,7 +9,7 @@ toc: False * [Non-free Licenses](non_free) * [Why WTFPL is a terrible license](wtfpl) * [Ranks and Permissions](ranks_permissions) -* [Reporting Content](reporting) +* [Contact Us](contact_us) * [Top Packages Algorithm](top_packages) * [Featured Packages](featured) diff --git a/app/flatpages/help/contact_us.md b/app/flatpages/help/contact_us.md new file mode 100644 index 0000000..c41970e --- /dev/null +++ b/app/flatpages/help/contact_us.md @@ -0,0 +1,14 @@ +title: Contact Us + +## Reports + +Please let us know if anything on the ContentDB violates our rules or any applicable +laws. + +We take copyright violation and other offenses very seriously. + +Report + +## Other + +Contact the admin diff --git a/app/flatpages/help/reporting.md b/app/flatpages/help/reporting.md deleted file mode 100644 index 1b1beef..0000000 --- a/app/flatpages/help/reporting.md +++ /dev/null @@ -1,8 +0,0 @@ -title: Reporting Content - -Please let us know if anything on the ContentDB violates our rules or any applicable -laws. - -We take copyright violation and other offenses very seriously. - -Contact diff --git a/app/flatpages/policy_and_guidance.md b/app/flatpages/policy_and_guidance.md index 35a0555..05b54ff 100644 --- a/app/flatpages/policy_and_guidance.md +++ b/app/flatpages/policy_and_guidance.md @@ -27,7 +27,7 @@ including ones not covered by this document, and to ban users who abuse this ser ### 2.1. Acceptable Content Sexually-orientated content is not permitted. -If in doubt at what this means, [contact us by raising a report](/help/reporting/). +If in doubt at what this means, [contact us by raising a report](/report/). Mature content is permitted providing that it is labelled correctly. See [Content Flags](/help/content_flags/). @@ -153,4 +153,4 @@ Doing so may result in temporary or permanent suspension from ContentDB. ## 7. Reporting Violations -See the [Reporting Content](/help/reporting/) page. +Please click "Report" on the package page. diff --git a/app/flatpages/privacy_policy.md b/app/flatpages/privacy_policy.md index 5ad194c..20f9b59 100644 --- a/app/flatpages/privacy_policy.md +++ b/app/flatpages/privacy_policy.md @@ -72,7 +72,7 @@ requested. See below. ## Removal Requests -Please [raise a report](https://content.minetest.net/help/reporting/) if you +Please [raise a report](https://content.minetest.net/report/?anon=0) if you wish to remove your personal information. ContentDB keeps a record of each username and forum topic on the forums, diff --git a/app/models/threads.py b/app/models/threads.py index c48eaee..5e987b2 100644 --- a/app/models/threads.py +++ b/app/models/threads.py @@ -124,6 +124,9 @@ class ThreadReply(db.Model): created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow) + def get_url(self): + return url_for('threads.view', id=self.thread.id) + "#reply-" + str(self.id) + def checkPerm(self, user, perm): if not user.is_authenticated: return False diff --git a/app/tasks/emails.py b/app/tasks/emails.py index a6a8bda..c69d243 100644 --- a/app/tasks/emails.py +++ b/app/tasks/emails.py @@ -15,7 +15,7 @@ # along with this program. If not, see . -from flask import render_template +from flask import render_template, escape from flask_mail import Message from app import mail from app.models import Notification, db, EmailSubscription, User @@ -86,7 +86,7 @@ def send_email_with_reason(email, subject, text, html, reason): msg = Message(subject, recipients=[email]) msg.body = text - html = html or text + html = html or f"
{escape(text)}
" msg.html = render_template("emails/base.html", subject=subject, content=html, reason=reason, sub=sub) mail.send(msg) diff --git a/app/template_filters.py b/app/template_filters.py index b274f88..333eeb2 100644 --- a/app/template_filters.py +++ b/app/template_filters.py @@ -1,6 +1,6 @@ from . import app, utils from .models import Permission, Package, PackageState, PackageRelease -from .utils import abs_url_for, url_set_query, url_set_anchor +from .utils import abs_url_for, url_set_query, url_set_anchor, url_current from flask_login import current_user from flask_babel import format_timedelta, gettext from urllib.parse import urlparse @@ -17,7 +17,7 @@ def inject_debug(): def inject_functions(): check_global_perm = Permission.checkPerm return dict(abs_url_for=abs_url_for, url_set_query=url_set_query, url_set_anchor=url_set_anchor, - check_global_perm=check_global_perm, get_headings=get_headings) + check_global_perm=check_global_perm, get_headings=get_headings, url_current=url_current) @app.context_processor def inject_todo(): diff --git a/app/templates/base.html b/app/templates/base.html index 30f4eee..d0b7687 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -234,7 +234,7 @@
  • {{ _("Policy and Guidance") }}
  • {{ _("API") }}
  • {{ _("Privacy Policy") }}
  • -
  • {{ _("Report / DMCA") }}
  • +
  • {{ _("Report") }}
  • {{ _("Stats / Monitoring") }}
  • {{ _("User List") }}
  • {{ _("Source Code") }}
  • diff --git a/app/templates/macros/threads.html b/app/templates/macros/threads.html index 9bfc64a..0f8b29a 100644 --- a/app/templates/macros/threads.html +++ b/app/templates/macros/threads.html @@ -36,7 +36,7 @@ {% endif %} + href="{{ r.get_url() }}"> {{ r.created_at | datetime }} @@ -48,10 +48,17 @@ {% endif %} + {% if current_user != r.author %} + + + + {% endif %} {% if current_user == thread.author and thread.review and thread.replies[0] == r %} + href="{{ thread.review.package.getURL('packages.review') }}"> {% elif r.checkPerm(current_user, "EDIT_REPLY") %} diff --git a/app/templates/packages/view.html b/app/templates/packages/view.html index 59c5228..1533d80 100644 --- a/app/templates/packages/view.html +++ b/app/templates/packages/view.html @@ -473,13 +473,16 @@

    {% if package.approved and current_user != package.author %} - - {{ _("Report a problem with this listing") }} + + + {{ _("Report") }} {% endif %} {% if package.checkPerm(current_user, "EDIT_PACKAGE") or package.checkPerm(current_user, "APPROVE_NEW") %} - + {% if package.approved and current_user != package.author %} + | + {% endif %} + {{ _("See audit log") }} {% endif %} diff --git a/app/templates/report/index.html b/app/templates/report/index.html new file mode 100644 index 0000000..8b728fc --- /dev/null +++ b/app/templates/report/index.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block title %} + {{ _("Report") }} +{% endblock %} + +{% block content %} + +{% from "macros/forms.html" import render_field, render_submit_field, render_checkbox_field %} +

    {{ _("Report") }}

    + +
    + {{ form.hidden_tag() }} + {% if url %} +

    + URL: {{ url }} +

    + {% else %} + {{ render_field(form.url, hint=_("URL to the thing you're reporting")) }} + {% endif %} + {{ render_field(form.message, hint=_("What are you reporting? Why are you reporting it?")) }} + {{ render_submit_field(form.submit) }} + +

    + {{ _("Reports will be shared with ContentDB stuff.") }} + {% if is_anon %} + {{ _("Only the admin will be able to see who made the report.") }} + {% endif %} +

    +
    + +{% endblock %} diff --git a/app/templates/users/profile.html b/app/templates/users/profile.html index dc2f430..4081b80 100644 --- a/app/templates/users/profile.html +++ b/app/templates/users/profile.html @@ -21,7 +21,12 @@ {{ _("To Do List") }} - {% endif %} + {% endif %} + + + + {{ _("Report") }} + {% if current_user.is_authenticated and current_user.rank.atLeast(current_user.rank.MODERATOR) and user.email %} diff --git a/app/utils/flask.py b/app/utils/flask.py index 82fbbac..3e4b7e6 100644 --- a/app/utils/flask.py +++ b/app/utils/flask.py @@ -40,6 +40,15 @@ def abs_url_for(path, **kwargs): def abs_url(path): return urljoin(app.config["BASE_URL"], path) +def url_current(abs=False): + args = MultiDict(request.args) + dargs = dict(args.lists()) + dargs.update(request.view_args) + if abs: + return abs_url_for(request.endpoint, **dargs) + else: + return url_for(request.endpoint, **dargs) + def url_set_anchor(anchor): args = MultiDict(request.args) dargs = dict(args.lists())