API: support /comment/report

This commit is contained in:
freamon 2024-10-27 10:20:38 +00:00
parent d738850fc7
commit 308f29ba38
5 changed files with 148 additions and 7 deletions

View file

@ -2,7 +2,7 @@ from app.api.alpha import bp
from app.api.alpha.utils import get_site, post_site_block, \ from app.api.alpha.utils import get_site, post_site_block, \
get_search, \ get_search, \
get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, \ get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, \
get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, \ get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report, \
get_community_list, get_community, post_community_follow, post_community_block, \ get_community_list, get_community, post_community_follow, post_community_block, \
get_user, post_user_block get_user, post_user_block
from app.shared.auth import log_user_in from app.shared.auth import log_user_in
@ -242,6 +242,18 @@ def post_alpha_comment_delete():
return jsonify({"error": str(ex)}), 400 return jsonify({"error": str(ex)}), 400
@bp.route('/api/alpha/comment/report', methods=['POST'])
def post_alpha_comment_report():
if not current_app.debug:
return jsonify({'error': 'alpha api routes only available in debug mode'})
try:
auth = request.headers.get('Authorization')
data = request.get_json(force=True) or {}
return jsonify(post_reply_report(auth, data))
except Exception as ex:
return jsonify({"error": str(ex)}), 400
# User # User
@bp.route('/api/alpha/user', methods=['GET']) @bp.route('/api/alpha/user', methods=['GET'])
def get_alpha_user(): def get_alpha_user():
@ -325,7 +337,6 @@ def alpha_post():
@bp.route('/api/alpha/comment/remove', methods=['POST']) @bp.route('/api/alpha/comment/remove', methods=['POST'])
@bp.route('/api/alpha/comment/mark_as_read', methods=['POST']) @bp.route('/api/alpha/comment/mark_as_read', methods=['POST'])
@bp.route('/api/alpha/comment/distinguish', methods=['POST']) @bp.route('/api/alpha/comment/distinguish', methods=['POST'])
@bp.route('/api/alpha/comment/report', methods=['POST'])
@bp.route('/api/alpha/comment/report/resolve', methods=['PUT']) @bp.route('/api/alpha/comment/report/resolve', methods=['PUT'])
@bp.route('/api/alpha/comment/report/list', methods=['GET']) @bp.route('/api/alpha/comment/report/list', methods=['GET'])
def alpha_reply(): def alpha_reply():

View file

@ -1,7 +1,7 @@
from app.api.alpha.utils.site import get_site, post_site_block from app.api.alpha.utils.site import get_site, post_site_block
from app.api.alpha.utils.misc import get_search from app.api.alpha.utils.misc import get_search
from app.api.alpha.utils.post import get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe from app.api.alpha.utils.post import get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe
from app.api.alpha.utils.reply import get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete from app.api.alpha.utils.reply import get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report
from app.api.alpha.utils.community import get_community, get_community_list, post_community_follow, post_community_block from app.api.alpha.utils.community import get_community, get_community_list, post_community_follow, post_community_block
from app.api.alpha.utils.user import get_user, post_user_block from app.api.alpha.utils.user import get_user, post_user_block

View file

@ -1,9 +1,9 @@
from app import cache from app import cache
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected, string_expected from app.api.alpha.utils.validators import required, integer_expected, boolean_expected, string_expected
from app.api.alpha.views import reply_view from app.api.alpha.views import reply_view, reply_report_view
from app.models import PostReply, Post from app.models import PostReply, Post
from app.shared.reply import vote_for_reply, bookmark_the_post_reply, remove_the_bookmark_from_post_reply, toggle_post_reply_notification, make_reply, edit_reply, \ from app.shared.reply import vote_for_reply, bookmark_the_post_reply, remove_the_bookmark_from_post_reply, toggle_post_reply_notification, make_reply, edit_reply, \
delete_reply, restore_reply delete_reply, restore_reply, report_reply
from app.utils import authorise_api_user, blocked_users, blocked_instances from app.utils import authorise_api_user, blocked_users, blocked_instances
from sqlalchemy import desc from sqlalchemy import desc
@ -182,4 +182,18 @@ def post_reply_delete(auth, data):
reply_json = reply_view(reply=reply, variant=4, user_id=user_id) reply_json = reply_view(reply=reply, variant=4, user_id=user_id)
return reply_json return reply_json
return {}
def post_reply_report(auth, data):
required(['comment_id', 'reason'], data)
integer_expected(['comment_id'], data)
string_expected(['reason'], data)
reply_id = data['comment_id']
reason = data['reason']
input = {'reason': reason, 'description': '', 'report_remote': True}
user_id, report = report_reply(reply_id, input, SRC_API, auth)
reply_json = reply_report_view(report=report, reply_id=reply_id, user_id=user_id)
return reply_json

View file

@ -338,6 +338,48 @@ def reply_view(reply: PostReply | int, variant, user_id=None, my_vote=0):
return v4 return v4
def reply_report_view(report, reply_id, user_id):
# views/comment_report_view.dart - /comment/report api endpoint
reply_json = reply_view(reply=reply_id, variant=2, user_id=user_id)
post_json = post_view(post=reply_json['comment']['post_id'], variant=1, stub=True)
community_json = community_view(community=post_json['community_id'], variant=1, stub=True)
banned = db.session.execute(text('SELECT user_id FROM "community_ban" WHERE user_id = :user_id and community_id = :community_id'), {'user_id': report.reporter_id, 'community_id': community_json['id']}).scalar()
moderator = db.session.execute(text('SELECT is_moderator FROM "community_member" WHERE user_id = :user_id and community_id = :community_id'), {'user_id': report.reporter_id, 'community_id': community_json['id']}).scalar()
admin = db.session.execute(text('SELECT user_id FROM "user_role" WHERE user_id = :user_id and role_id = 4'), {'user_id': report.reporter_id}).scalar()
creator_banned_from_community = True if banned else False
creator_is_moderator = True if moderator else False
creator_is_admin = True if admin else False
v1 = {
'comment_report_view': {
'comment_report': {
'id': report.id,
'creator_id': report.reporter_id,
'comment_id': report.suspect_post_reply_id,
'original_comment_text': reply_json['comment']['body'],
'reason': report.reasons,
'resolved': report.status == 3,
'published': report.created_at.isoformat() + 'Z'
},
'comment': reply_json['comment'],
'post': post_json,
'community': community_json,
'creator': user_view(user=user_id, variant=1, stub=True),
'comment_creator': user_view(user=report.suspect_user_id, variant=1, stub=True),
'counts': reply_json['counts'],
'creator_banned_from_community': creator_banned_from_community,
'creator_is_moderator': creator_is_moderator,
'creator_is_admin': creator_is_admin,
'creator_blocked': False,
'subscribed': reply_json['subscribed'],
'saved': reply_json['saved']
}
}
return v1
def search_view(type): def search_view(type):
v1 = { v1 = {
'type_': type, 'type_': type,

View file

@ -2,7 +2,7 @@ from app import cache, db
from app.activitypub.signature import default_context, post_request_in_background, post_request from app.activitypub.signature import default_context, post_request_in_background, post_request
from app.community.util import send_to_remote_instance from app.community.util import send_to_remote_instance
from app.constants import * from app.constants import *
from app.models import NotificationSubscription, Post, PostReply, PostReplyBookmark, User, utcnow from app.models import Instance, Notification, NotificationSubscription, Post, PostReply, PostReplyBookmark, Report, Site, User, utcnow
from app.utils import gibberish, instance_banned, render_template, authorise_api_user, recently_upvoted_post_replies, recently_downvoted_post_replies, shorten_string, \ from app.utils import gibberish, instance_banned, render_template, authorise_api_user, recently_upvoted_post_replies, recently_downvoted_post_replies, shorten_string, \
piefed_markdown_to_lemmy_markdown, markdown_to_html, ap_datetime piefed_markdown_to_lemmy_markdown, markdown_to_html, ap_datetime
@ -642,3 +642,77 @@ def restore_reply(reply_id, src, auth):
return user.id, reply return user.id, reply
else: else:
return return
def report_reply(reply_id, input, src, auth=None):
if src == SRC_API:
reply = PostReply.query.filter_by(id=reply_id).one()
user = authorise_api_user(auth, return_type='model')
reason = input['reason']
description = input['description']
report_remote = input['report_remote']
else:
reply = PostReply.query.get_or_404(reply_id)
user = current_user
reason = input.reasons_to_string(input.reasons.data)
description = input.description.data
report_remote = input.report_remote.data
if reply.reports == -1: # When a mod decides to ignore future reports, reply.reports is set to -1
if src == SRC_API:
raise Exception('already_reported')
else:
flash(_('Comment has already been reported, thank you!'))
return
report = Report(reasons=reason, description=description, type=2, reporter_id=user.id, suspect_post_id=reply.post.id, suspect_community_id=reply.community.id,
suspect_user_id=reply.author.id, suspect_post_reply_id=reply.id, in_community_id=reply.community.id, source_instance_id=1)
db.session.add(report)
# Notify moderators
already_notified = set()
for mod in reply.community.moderators():
moderator = User.query.get(mod.user_id)
if moderator and moderator.is_local():
notification = Notification(user_id=mod.user_id, title=_('A comment has been reported'),
url=f"https://{current_app.config['SERVER_NAME']}/comment/{reply.id}",
author_id=user.id)
db.session.add(notification)
already_notified.add(mod.user_id)
reply.reports += 1
# todo: only notify admins for certain types of report
for admin in Site.admins():
if admin.id not in already_notified:
notify = Notification(title='Suspicious content', url='/admin/reports', user_id=admin.id, author_id=user.id)
db.session.add(notify)
admin.unread_notifications += 1
db.session.commit()
# federate report to originating instance
if not reply.community.is_local() and report_remote:
summary = reason
if description:
summary += ' - ' + description
report_json = {
'actor': user.public_url(),
'audience': reply.community.public_url(),
'content': None,
'id': f"https://{current_app.config['SERVER_NAME']}/activities/flag/{gibberish(15)}",
'object': reply.ap_id,
'summary': summary,
'to': [
reply.community.public_url()
],
'type': 'Flag'
}
instance = Instance.query.get(reply.community.instance_id)
if reply.community.ap_inbox_url and not user.has_blocked_instance(instance.id) and not instance_banned(instance.domain):
success = post_request(reply.community.ap_inbox_url, report_json, user.private_key, user.public_url() + '#main-key')
if success is False or isinstance(success, str):
if src == SRC_WEB:
flash('Failed to send report to remote server', 'error')
if src == SRC_API:
return user.id, report
else:
return