mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
avoid duplicate comments and gif reactions
This commit is contained in:
parent
fb75f9901f
commit
396a5bae4c
6 changed files with 69 additions and 8 deletions
|
@ -300,6 +300,7 @@ def shared_inbox():
|
||||||
if 'id' in request_json:
|
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.
|
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.result = 'ignored'
|
||||||
|
activity_log.exception_message = 'Unnecessary retry attempt'
|
||||||
db.session.add(activity_log)
|
db.session.add(activity_log)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return ''
|
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'
|
activity_log.exception_message = 'Could not detect type of like'
|
||||||
if activity_log.result == 'success':
|
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
|
# todo: if vote was on content in local community, federate the vote out to followers
|
||||||
else:
|
else:
|
||||||
activity_log.exception_message = 'Cannot upvote this'
|
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'
|
activity_log.exception_message = 'Could not detect type of like'
|
||||||
if activity_log.result == 'success':
|
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
|
# todo: if vote was on content in local community, federate the vote out to followers
|
||||||
else:
|
else:
|
||||||
activity_log.exception_message = 'Cannot upvote this'
|
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):
|
elif isinstance(disliked, PostReply):
|
||||||
downvote_post_reply(disliked, user)
|
downvote_post_reply(disliked, user)
|
||||||
activity_log.result = 'success'
|
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
|
# todo: if vote was on content in the local community, federate the vote out to followers
|
||||||
else:
|
else:
|
||||||
activity_log.exception_message = 'Could not detect type of like'
|
activity_log.exception_message = 'Could not detect type of like'
|
||||||
|
|
|
@ -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, \
|
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, \
|
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():
|
def public_key():
|
||||||
|
@ -924,6 +924,17 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep
|
||||||
activity_log.result = 'ignored'
|
activity_log.result = 'ignored'
|
||||||
return None
|
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)
|
db.session.add(post_reply)
|
||||||
post.reply_count += 1
|
post.reply_count += 1
|
||||||
community.post_reply_count += 1
|
community.post_reply_count += 1
|
||||||
|
|
|
@ -25,6 +25,7 @@ from datetime import timezone, timedelta
|
||||||
@bp.route('/add_local', methods=['GET', 'POST'])
|
@bp.route('/add_local', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def add_local():
|
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()
|
form = AddLocalCommunity()
|
||||||
if g.site.enable_nsfw is False:
|
if g.site.enable_nsfw is False:
|
||||||
form.nsfw.render_kw = {'disabled': True}
|
form.nsfw.render_kw = {'disabled': True}
|
||||||
|
|
|
@ -14,7 +14,7 @@ from flask_babel import _, get_locale
|
||||||
from sqlalchemy import select, desc
|
from sqlalchemy import select, desc
|
||||||
from sqlalchemy_searchable import search
|
from sqlalchemy_searchable import search
|
||||||
from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \
|
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
|
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)
|
page = request.args.get('page', 1, type=int)
|
||||||
|
|
||||||
if current_user.is_anonymous:
|
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)
|
posts = Post.query.filter(Post.from_bot == False, Post.nsfw == False, Post.nsfl == False)
|
||||||
else:
|
else:
|
||||||
posts = Post.query.join(CommunityMember, Post.community_id == CommunityMember.community_id).filter(CommunityMember.is_banned == False)
|
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,
|
POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK,
|
||||||
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
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,
|
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'])
|
@bp.route('/new', methods=['HEAD', 'GET', 'POST'])
|
||||||
|
|
|
@ -18,7 +18,8 @@ from app.models import Post, PostReply, \
|
||||||
from app.post import bp
|
from app.post import bp
|
||||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
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, \
|
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):
|
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()))
|
flash(_('You cannot reply to %(name)s', name=post.author.display_name()))
|
||||||
return redirect(url_for('activitypub.post_ap', post_id=post_id))
|
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,
|
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,
|
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,
|
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()
|
form = NewReplyForm()
|
||||||
if form.validate_on_submit():
|
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.last_seen = utcnow()
|
||||||
current_user.ip_address = ip_address()
|
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,
|
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)
|
send_to_remote_instance(instance.id, post.community.id, announce)
|
||||||
|
|
||||||
if reply.depth <= constants.THREAD_CUTOFF_DEPTH:
|
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:
|
else:
|
||||||
return redirect(url_for('post.continue_discussion', post_id=post_id, comment_id=reply.parent_id))
|
return redirect(url_for('post.continue_discussion', post_id=post_id, comment_id=reply.parent_id))
|
||||||
else:
|
else:
|
||||||
|
|
24
app/utils.py
24
app/utils.py
|
@ -436,6 +436,30 @@ def can_create(user, content: Union[Community, Post, PostReply]) -> bool:
|
||||||
return True
|
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:
|
def inbox_domain(inbox: str) -> str:
|
||||||
inbox = inbox.lower()
|
inbox = inbox.lower()
|
||||||
if 'https://' in inbox or 'http://' in inbox:
|
if 'https://' in inbox or 'http://' in inbox:
|
||||||
|
|
Loading…
Add table
Reference in a new issue