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") }}
+
+
+
+{% 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())