Fix approved filtered comments not notifying.

Moves behavior in api_comment that updates stateful counters and
generates notifications into a function which can also be called
if a filtered comment is approved. Fixes #272.

Incidentally, also fixes #278 by adding another filter to the post
subscribers query during general clean-up/refactoring.

Originally was going to move this function into the Comments model,
since assurances about state (even with side effects) should probably
be made there, but I couldn't find a sane way to untangle the imports.
This commit is contained in:
TLSM 2022-09-08 16:37:34 -04:00 committed by Ben Rog-Wilhelm
parent 0ec522d897
commit f5f2c008ad
3 changed files with 84 additions and 58 deletions

View file

@ -13,6 +13,7 @@ from files.classes import *
from flask import *
from files.__main__ import app, cache, limiter
from .front import frontlist
from files.routes.comments import comment_on_publish
from files.helpers.discord import add_role
from datetime import datetime
import requests
@ -426,13 +427,25 @@ def update_filter_status(v):
return { 'result': f'Status of {new_status} is not permitted' }
if post_id:
rows_updated = g.db.query(Submission).where(Submission.id == post_id).update({Submission.filter_state: new_status})
p = g.db.query(Submission).get(post_id)
old_status = p.filter_state
rows_updated = g.db.query(Submission).where(Submission.id == post_id) \
.update({Submission.filter_state: new_status})
elif comment_id:
rows_updated = g.db.query(Comment).where(Comment.id == comment_id).update({Comment.filter_state: new_status})
c = g.db.query(Comment).get(comment_id)
old_status = c.filter_state
rows_updated = g.db.query(Comment).where(Comment.id == comment_id) \
.update({Comment.filter_state: new_status})
else:
return { 'result': f'No valid item ID provided' }
if rows_updated == 1:
# If comment now visible, update state to reflect publication.
if (comment_id
and old_status in ['filtered', 'removed']
and new_status in ['normal', 'ignored']):
comment_on_publish(c)
g.db.commit()
return { 'result': 'Update successful' }
else:

View file

@ -3,7 +3,6 @@ from files.helpers.alerts import *
from files.helpers.images import *
from files.helpers.const import *
from files.classes import *
from files.routes.front import comment_idlist
from pusher_push_notifications import PushNotifications
from flask import *
from files.__main__ import app, limiter
@ -317,38 +316,15 @@ def api_comment(v):
if c.level == 1: c.top_comment_id = c.id
else: c.top_comment_id = parent.top_comment_id
if parent_post.id not in ADMINISTRATORS:
if not v.shadowbanned and not is_filtered:
notify_users = NOTIFY_USERS(body, v)
for x in g.db.query(Subscription.user_id).filter_by(submission_id=c.parent_submission).all(): notify_users.add(x[0])
if parent.author.id not in (v.id, BASEDBOT_ID, AUTOJANNY_ID, SNAPPY_ID, LONGPOSTBOT_ID, ZOZBOT_ID):
notify_users.add(parent.author.id)
for x in notify_users:
n = Notification(comment_id=c.id, user_id=x)
g.db.add(n)
if parent.author.id != v.id and PUSHER_ID != 'blahblahblah' and not v.shadowbanned:
try: gevent.spawn(pusher_thread, f'{request.host}{parent.author.id}', c, c.author_name)
except: pass
if not v.shadowbanned and not is_filtered:
comment_on_publish(c)
vote = CommentVote(user_id=v.id,
comment_id=c.id,
vote_type=1,
)
g.db.add(vote)
cache.delete_memoized(comment_idlist)
v.comment_count = g.db.query(Comment.id).filter(Comment.author_id == v.id, Comment.parent_submission != None).filter_by(is_banned=False, deleted_utc=0).count()
g.db.add(v)
c.voted = 1
if v.id == PIZZASHILL_ID:
@ -364,15 +340,68 @@ def api_comment(v):
if v.marseyawarded and parent_post.id not in ADMINISTRATORS and marseyaward_body_regex.search(body_html):
return {"error":"You can only type marseys!"}, 403
parent_post.comment_count += 1
g.db.add(parent_post)
g.db.commit()
if request.headers.get("Authorization"): return c.json
return {"comment": render_template("comments.html", v=v, comments=[c], ajax=True)}
def comment_on_publish(comment):
"""
Run when comment becomes visible: immediately for non-filtered comments,
or on approval for previously filtered comments.
Should be used to update stateful counters, notifications, etc. that
reflect the comments users will actually see.
"""
# TODO: Get this out of the routes and into a model eventually...
# Shadowbanned users are invisible. This may lead to inconsistencies if
# a user comments while shadowed and is later unshadowed. (TODO?)
if comment.author.shadowbanned:
return
# Comment instances used for purposes other than actual comments (notifs,
# DMs) shouldn't be considered published.
if not comment.parent_submission:
return
# Generate notifs for: mentions, post subscribers, parent post/comment
to_notify = NOTIFY_USERS(comment.body, comment.author)
post_subscribers = g.db.query(Subscription.user_id).filter(
Subscription.submission_id == comment.parent_submission,
Subscription.user_id != comment.author_id,
).all()
to_notify.update([x[0] for x in post_subscribers])
parent = comment.parent
if parent and parent.author_id != comment.author_id:
to_notify.add(parent.author_id)
for uid in to_notify:
notif = Notification(comment_id=comment.id, user_id=uid)
g.db.add(notif)
# Comment counter for parent submission
comment.post.comment_count += 1
g.db.add(comment.post)
# Comment counter for author's profile
comment.author.comment_count = g.db.query(Comment).filter(
Comment.author_id == comment.author_id,
Comment.parent_submission != None,
Comment.is_banned == False,
Comment.deleted_utc == 0,
).count()
g.db.add(comment.author)
# Generate push notifications if enabled.
if PUSHER_ID != 'blahblahblah' and comment.author_id != parent.author_id:
try:
gevent.spawn(pusher_thread, f'{request.host}{parent.author.id}',
comment, comment.author_name)
except: pass
@app.post("/edit_comment/<cid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@ -467,13 +496,15 @@ def edit_comment(cid, v):
g.db.add(c)
notify_users = NOTIFY_USERS(body, v)
if c.filter_state != 'filtered':
notify_users = NOTIFY_USERS(body, v)
for x in notify_users:
notif = g.db.query(Notification).filter_by(comment_id=c.id, user_id=x).one_or_none()
if not notif:
n = Notification(comment_id=c.id, user_id=x)
g.db.add(n)
for x in notify_users:
notif = g.db.query(Notification) \
.filter_by(comment_id=c.id, user_id=x).one_or_none()
if not notif:
n = Notification(comment_id=c.id, user_id=x)
g.db.add(n)
g.db.commit()
@ -494,9 +525,6 @@ def delete_comment(cid, v):
c.deleted_utc = int(time.time())
g.db.add(c)
cache.delete_memoized(comment_idlist)
g.db.commit()
return {"message": "Comment deleted!"}
@ -514,9 +542,6 @@ def undelete_comment(cid, v):
c.deleted_utc = 0
g.db.add(c)
cache.delete_memoized(comment_idlist)
g.db.commit()
return {"message": "Comment undeleted!"}

View file

@ -490,8 +490,6 @@ def random_user(v):
@app.get("/comments")
@auth_required
def all_comments(v):
try: page = max(int(request.values.get("page", 1)), 1)
except: page = 1
@ -504,15 +502,7 @@ def all_comments(v):
try: lt=int(request.values.get("before", 0))
except: lt=0
idlist = comment_idlist(v=v,
page=page,
sort=sort,
t=t,
gt=gt,
lt=lt,
site=SITE
)
idlist = get_comments_idlist(v=v, page=page, sort=sort, t=t, gt=gt, lt=lt)
comments = get_comments(idlist, v=v)
next_exists = len(idlist) > 25
@ -523,9 +513,7 @@ def all_comments(v):
return render_template("home_comments.html", v=v, sort=sort, t=t, page=page, comments=comments, standalone=True, next_exists=next_exists)
@cache.memoize(timeout=86400)
def comment_idlist(page=1, v=None, nsfw=False, sort="new", t="all", gt=0, lt=0, site=None):
def get_comments_idlist(page=1, v=None, sort="new", t="all", gt=0, lt=0):
comments = g.db.query(Comment.id) \
.join(Comment.post) \
.join(Comment.author) \