avoid duplicate comments and gif reactions

This commit is contained in:
rimu 2024-01-06 14:54:10 +13:00
parent fb75f9901f
commit 396a5bae4c
6 changed files with 69 additions and 8 deletions

View file

@ -300,6 +300,7 @@ def shared_inbox():
if 'id' in request_json:
if activity_already_ingested(request_json['id']): # Lemmy has an extremely short POST timeout and tends to retry unnecessarily. Ignore their retries.
activity_log.result = 'ignored'
activity_log.exception_message = 'Unnecessary retry attempt'
db.session.add(activity_log)
db.session.commit()
return ''
@ -458,7 +459,6 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
activity_log.exception_message = 'Could not detect type of like'
if activity_log.result == 'success':
...
# todo: recalculate 'hotness' of liked post/reply
# todo: if vote was on content in local community, federate the vote out to followers
else:
activity_log.exception_message = 'Cannot upvote this'
@ -723,7 +723,6 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
activity_log.exception_message = 'Could not detect type of like'
if activity_log.result == 'success':
...
# todo: recalculate 'hotness' of liked post/reply
# todo: if vote was on content in local community, federate the vote out to followers
else:
activity_log.exception_message = 'Cannot upvote this'
@ -754,7 +753,6 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
elif isinstance(disliked, PostReply):
downvote_post_reply(disliked, user)
activity_log.result = 'success'
# todo: recalculate 'hotness' of liked post/reply
# todo: if vote was on content in the local community, federate the vote out to followers
else:
activity_log.exception_message = 'Could not detect type of like'

View file

@ -21,7 +21,7 @@ from io import BytesIO
from app.utils import get_request, allowlist_html, html_to_markdown, get_setting, ap_datetime, markdown_to_html, \
is_image_url, domain_from_url, gibberish, ensure_directory_exists, markdown_to_text, head_request, post_ranking, \
shorten_string
shorten_string, reply_already_exists, reply_is_just_link_to_gif_reaction
def public_key():
@ -924,6 +924,17 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep
activity_log.result = 'ignored'
return None
if reply_already_exists(user_id=user.id, post_id=post.id, parent_id=post_reply.parent_id, body=post_reply.body):
activity_log.exception_message = 'Duplicate reply'
activity_log.result = 'ignored'
return None
if reply_is_just_link_to_gif_reaction(post_reply.body):
user.reputation -= 1
activity_log.exception_message = 'gif comment ignored'
activity_log.result = 'ignored'
return None
db.session.add(post_reply)
post.reply_count += 1
community.post_reply_count += 1

View file

@ -25,6 +25,7 @@ from datetime import timezone, timedelta
@bp.route('/add_local', methods=['GET', 'POST'])
@login_required
def add_local():
flash('PieFed is still being tested so hosting communities on piefed.social is not advised except for testing purposes.', 'warning')
form = AddLocalCommunity()
if g.site.enable_nsfw is False:
form.nsfw.render_kw = {'disabled': True}

View file

@ -14,7 +14,7 @@ from flask_babel import _, get_locale
from sqlalchemy import select, desc
from sqlalchemy_searchable import search
from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \
ap_datetime, ip_address, retrieve_block_list
ap_datetime, ip_address, retrieve_block_list, shorten_string, markdown_to_text
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic
@ -35,6 +35,7 @@ def index():
page = request.args.get('page', 1, type=int)
if current_user.is_anonymous:
flash(_('Create an account to tailor this feed to your interests.'))
posts = Post.query.filter(Post.from_bot == False, Post.nsfw == False, Post.nsfl == False)
else:
posts = Post.query.join(CommunityMember, Post.community_id == CommunityMember.community_id).filter(CommunityMember.is_banned == False)
@ -54,7 +55,8 @@ def index():
POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK,
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
etag=f"home_{hash(str(g.site.last_active))}", next_url=next_url, prev_url=prev_url,
rss_feed=f"https://{current_app.config['SERVER_NAME']}/feed", rss_feed_name=f"Posts on " + g.site.name)
rss_feed=f"https://{current_app.config['SERVER_NAME']}/feed", rss_feed_name=f"Posts on " + g.site.name,
title=f"{g.site.name} - {g.site.description}", description=shorten_string(markdown_to_text(g.site.sidebar), 150))
@bp.route('/new', methods=['HEAD', 'GET', 'POST'])

View file

@ -18,7 +18,8 @@ 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, instance_banned, can_downvote, can_upvote, post_ranking
request_etag_matches, ip_address, user_ip_banned, instance_banned, can_downvote, can_upvote, post_ranking, \
reply_already_exists, reply_is_just_link_to_gif_reaction
def show_post(post_id: int):
@ -58,6 +59,16 @@ def show_post(post_id: int):
flash(_('You cannot reply to %(name)s', name=post.author.display_name()))
return redirect(url_for('activitypub.post_ap', post_id=post_id))
# avoid duplicate replies
if reply_already_exists(user_id=current_user.id, post_id=post.id, parent_id=None, body=form.body.data):
return redirect(url_for('activitypub.post_ap', post_id=post_id))
# disallow low-effort gif reaction posts
if reply_is_just_link_to_gif_reaction(form.body.data):
current_user.reputation -= 1
flash(_('This type of comment is not accepted, sorry.'), 'error')
return redirect(url_for('activitypub.post_ap', post_id=post_id))
reply = PostReply(user_id=current_user.id, post_id=post.id, community_id=community.id, body=form.body.data,
body_html=markdown_to_html(form.body.data), body_html_safe=True,
from_bot=current_user.bot, up_votes=1, nsfw=post.nsfw, nsfl=post.nsfl,
@ -375,6 +386,20 @@ def add_reply(post_id: int, comment_id: int):
form = NewReplyForm()
if form.validate_on_submit():
if reply_already_exists(user_id=current_user.id, post_id=post.id, parent_id=in_reply_to.id, body=form.body.data):
if in_reply_to.depth <= constants.THREAD_CUTOFF_DEPTH:
return redirect(url_for('activitypub.post_ap', post_id=post_id, _anchor=f'comment_{in_reply_to.id}'))
else:
return redirect(url_for('post.continue_discussion', post_id=post_id, comment_id=in_reply_to.parent_id))
if reply_is_just_link_to_gif_reaction(form.body.data):
current_user.reputation -= 1
flash(_('This type of comment is not accepted, sorry.'), 'error')
if in_reply_to.depth <= constants.THREAD_CUTOFF_DEPTH:
return redirect(url_for('activitypub.post_ap', post_id=post_id, _anchor=f'comment_{in_reply_to.id}'))
else:
return redirect(url_for('post.continue_discussion', post_id=post_id, comment_id=in_reply_to.parent_id))
current_user.last_seen = utcnow()
current_user.ip_address = ip_address()
reply = PostReply(user_id=current_user.id, post_id=post.id, parent_id=in_reply_to.id, depth=in_reply_to.depth + 1,
@ -487,7 +512,7 @@ def add_reply(post_id: int, comment_id: int):
send_to_remote_instance(instance.id, post.community.id, announce)
if reply.depth <= constants.THREAD_CUTOFF_DEPTH:
return redirect(url_for('activitypub.post_ap', post_id=post_id, _anchor=f'comment_{reply.parent_id}'))
return redirect(url_for('activitypub.post_ap', post_id=post_id, _anchor=f'comment_{reply.id}'))
else:
return redirect(url_for('post.continue_discussion', post_id=post_id, comment_id=reply.parent_id))
else:

View file

@ -436,6 +436,30 @@ def can_create(user, content: Union[Community, Post, PostReply]) -> bool:
return True
def reply_already_exists(user_id, post_id, parent_id, body) -> bool:
if parent_id is None:
num_matching_replies = db.session.execute(text(
'SELECT COUNT(id) as c FROM "post_reply" WHERE user_id = :user_id AND post_id = :post_id AND parent_id is null AND body = :body'),
{'user_id': user_id, 'post_id': post_id, 'body': body}).scalar()
else:
num_matching_replies = db.session.execute(text(
'SELECT COUNT(id) as c FROM "post_reply" WHERE user_id = :user_id AND post_id = :post_id AND parent_id = :parent_id AND body = :body'),
{'user_id': user_id, 'post_id': post_id, 'parent_id': parent_id, 'body': body}).scalar()
return num_matching_replies != 0
def reply_is_just_link_to_gif_reaction(body) -> bool:
tmp_body = body.strip()
if tmp_body.startswith('https://media.tenor.com/') or \
tmp_body.startswith('https://media1.giphy.com/') or \
tmp_body.startswith('https://media2.giphy.com/') or \
tmp_body.startswith('https://media3.giphy.com/') or \
tmp_body.startswith('https://media4.giphy.com/'):
return True
else:
return False
def inbox_domain(inbox: str) -> str:
inbox = inbox.lower()
if 'https://' in inbox or 'http://' in inbox: