From f5f2c008ad598fc6dce9f20505c7a3ecc60e90b8 Mon Sep 17 00:00:00 2001 From: TLSM Date: Thu, 8 Sep 2022 16:37:34 -0400 Subject: [PATCH] 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. --- files/routes/admin.py | 17 +++++- files/routes/comments.py | 109 ++++++++++++++++++++++++--------------- files/routes/front.py | 16 +----- 3 files changed, 84 insertions(+), 58 deletions(-) diff --git a/files/routes/admin.py b/files/routes/admin.py index 1b35b9486..b8cf14297 100644 --- a/files/routes/admin.py +++ b/files/routes/admin.py @@ -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: diff --git a/files/routes/comments.py b/files/routes/comments.py index 903e7cf76..da39edc80 100644 --- a/files/routes/comments.py +++ b/files/routes/comments.py @@ -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,37 +316,14 @@ 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 @@ -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/") @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) - - 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) + 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) 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!"} diff --git a/files/routes/front.py b/files/routes/front.py index 081263d97..de73bf860 100644 --- a/files/routes/front.py +++ b/files/routes/front.py @@ -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) \