rDrama/drama/classes/comment.py

510 lines
14 KiB
Python

from flask import *
from sqlalchemy import *
from sqlalchemy.orm import relationship, deferred
from sqlalchemy.ext.associationproxy import association_proxy
from .mix_ins import *
from drama.helpers.base36 import *
from drama.helpers.lazy import lazy
from drama.__main__ import Base
from .votes import CommentVote
class CommentAux(Base):
__tablename__ = "comments_aux"
key_id = Column(Integer, primary_key=True)
id = Column(Integer, ForeignKey("comments.id"))
body = Column(String(10000), default=None)
body_html = Column(String(20000))
ban_reason = Column(String(256), default='')
class Comment(Base, Age_times, Scores, Stndrd, Fuzzing):
__tablename__ = "comments"
id = Column(Integer, primary_key=True)
comment_aux = relationship(
"CommentAux",
lazy="joined",
uselist=False,
innerjoin=True,
primaryjoin="Comment.id==CommentAux.id")
author_id = Column(Integer, ForeignKey("users.id"))
parent_submission = Column(Integer, ForeignKey("submissions.id"))
# this column is foreignkeyed to comment(id) but we can't do that yet as
# "comment" class isn't yet defined
parent_fullname = Column(Integer)
created_utc = Column(Integer, default=0)
edited_utc = Column(Integer, default=0)
is_banned = Column(Boolean, default=False)
shadowbanned = Column(Boolean, default=False)
distinguish_level = Column(Integer, default=0)
gm_distinguish = Column(Integer, ForeignKey("boards.id"), default=0)
distinguished_board = relationship("Board", lazy="joined", primaryjoin="Comment.gm_distinguish==Board.id")
deleted_utc = Column(Integer, default=0)
purged_utc = Column(Integer, default=0)
is_approved = Column(Integer, default=0)
approved_utc = Column(Integer, default=0)
creation_ip = Column(String(64), default='')
score_hot = Column(Float, default=0)
score_top = Column(Integer, default=1)
level = Column(Integer, default=0)
parent_comment_id = Column(Integer, ForeignKey("comments.id"))
original_board_id = Column(Integer, ForeignKey("boards.id"))
over_18 = Column(Boolean, default=False)
is_offensive = Column(Boolean, default=False)
is_nsfl = Column(Boolean, default=False)
is_bot = Column(Boolean, default=False)
banaward = Column(String, default=None)
is_pinned = Column(Boolean, default=False)
creation_region=Column(String(2), default=None)
sentto=Column(Integer, default=None)
app_id = Column(Integer, ForeignKey("oauth_apps.id"), default=None)
oauth_app=relationship("OauthApp")
post = relationship("Submission")
flags = relationship("CommentFlag", backref="comment")
author = relationship(
"User",
lazy="joined",
innerjoin=True,
primaryjoin="User.id==Comment.author_id")
board = association_proxy("post", "board")
original_board = relationship(
"Board", primaryjoin="Board.id==Comment.original_board_id")
upvotes = Column(Integer, default=1)
downvotes = Column(Integer, default=0)
parent_comment = relationship("Comment", remote_side=[id])
child_comments = relationship("Comment", remote_side=[parent_comment_id])
awards = relationship("AwardRelationship", lazy="joined")
# These are virtual properties handled as postgres functions server-side
# There is no difference to SQLAlchemy, but they cannot be written to
ups = deferred(Column(Integer, server_default=FetchedValue()))
downs = deferred(Column(Integer, server_default=FetchedValue()))
is_public = deferred(Column(Boolean, server_default=FetchedValue()))
score = deferred(Column(Integer, server_default=FetchedValue()))
rank_fiery = deferred(Column(Float, server_default=FetchedValue()))
rank_hot = deferred(Column(Float, server_default=FetchedValue()))
board_id = deferred(Column(Integer, server_default=FetchedValue()))
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs:
kwargs["created_utc"] = int(time.time())
kwargs["creation_ip"] = request.remote_addr
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Comment(id={self.id})>"
@property
@lazy
def score_disputed(self):
return (self.upvotes+1) * (self.downvotes+1)
@property
@lazy
def fullname(self):
return f"t3_{self.base36id}"
def children(self, v):
return sorted([x for x in self.child_comments if not x.author.shadowbanned or (v and v.id == x.author_id)], key=lambda x: x.score, reverse=True)
@property
@lazy
def is_deleted(self):
return bool(self.deleted_utc)
@property
@lazy
def is_top_level(self):
return self.parent_fullname and self.parent_fullname.startswith("t2_")
@property
def is_archived(self):
return self.post.is_archived
@property
@lazy
def parent(self):
if not self.parent_submission:
return None
if self.is_top_level:
return self.post
else:
return g.db.query(Comment).get(self.parent_comment_id)
@property
def replies(self):
r = self.__dict__.get("replies", None)
if r is None:
r = self.child_comments
return r
@replies.setter
def replies(self, value):
self.__dict__["replies"] = value
@property
def replies2(self):
return self.__dict__.get("replies2", [])
@replies2.setter
def replies2(self, value):
self.__dict__["replies2"] = value
@property
@lazy
def permalink(self):
if self.post: return f"{self.post.permalink}/{self.id}/"
else: return f"/comment/{self.id}/"
@property
def any_descendants_live(self):
if self.replies == []:
return False
if any([not x.is_banned and x.deleted_utc == 0 for x in self.replies]):
return True
else:
return any([x.any_descendants_live for x in self.replies])
def rendered_comment(self, v=None, render_replies=True,
standalone=False, level=1, **kwargs):
kwargs["post_base36id"] = kwargs.get(
"post_base36id", self.post.base36id if self.post else None)
if self.is_banned or self.deleted_utc > 0:
if v and v.admin_level > 1:
return render_template("single_comment.html",
v=v,
c=self,
render_replies=render_replies,
standalone=standalone,
level=level,
**kwargs)
elif self.any_descendants_live:
return render_template("single_comment_removed.html",
c=self,
render_replies=render_replies,
standalone=standalone,
level=level,
**kwargs)
else:
return ""
return render_template("single_comment.html",
v=v,
c=self,
render_replies=render_replies,
standalone=standalone,
level=level,
**kwargs)
@property
def active_flags(self):
if self.is_approved:
return 0
else:
return self.flag_count
def visibility_reason(self, v):
if not v or self.author_id == v.id:
return "this is your content."
elif not self.board:
return None
elif self.board.has_mod(v):
return f"you are a guildmaster of +{self.board.name}."
elif self.board.has_contributor(v):
return f"you are an approved contributor in +{self.board.name}."
elif self.parent.author_id == v.id:
return "this is a reply to your content."
elif v.admin_level >= 4:
return "you are a Drama admin."
@property
def json_raw(self):
data= {
'id': self.base36id,
'fullname': self.fullname,
'level': self.level,
'author_name': self.author.username if not self.author.is_deleted else None,
'body': self.body,
'body_html': self.body_html,
'is_archived': self.is_archived,
'is_bot': self.is_bot,
'created_utc': self.created_utc,
'edited_utc': self.edited_utc or 0,
'is_banned': bool(self.is_banned),
'is_deleted': self.is_deleted,
'is_nsfw': self.over_18,
'is_offensive': self.is_offensive,
'is_nsfl': self.is_nsfl,
'permalink': self.permalink,
'post_id': self.post.base36id,
'score': self.score_fuzzed,
'upvotes': self.upvotes_fuzzed,
'downvotes': self.downvotes_fuzzed,
'award_count': self.award_count,
'is_bot': self.is_bot
}
if self.ban_reason:
data["ban_reason"]=self.ban_reason
return data
@property
def json_core(self):
if self.is_banned:
data= {'is_banned': True,
'ban_reason': self.ban_reason,
'id': self.base36id,
'post': self.post.base36id,
'level': self.level,
'parent': self.parent_fullname
}
elif self.deleted_utc > 0:
data= {'deleted_utc': self.deleted_utc,
'id': self.base36id,
'post': self.post.base36id,
'level': self.level,
'parent': self.parent_fullname
}
else:
data=self.json_raw
if self.level>=2:
data['parent_comment_id']= base36encode(self.parent_comment_id),
if "replies" in self.__dict__:
data['replies']=[x.json_core for x in self.replies]
return data
@property
def json(self):
data=self.json_core
if self.deleted_utc > 0 or self.is_banned:
return data
data["author"]=self.author.json_core
data["post"]=self.post.json_core
data["guild"]=self.post.board.json_core
if self.level >= 2:
data["parent"]=self.parent.json_core
return data
@property
def voted(self):
x = self.__dict__.get("_voted")
if x is not None:
return x
if g.v:
x = g.db.query(CommentVote).filter_by(
comment_id=self.id,
user_id=g.v.id
).first()
if x:
x = x.vote_type
else:
x = 0
else:
x = 0
return x
@property
def title(self):
return self.__dict__.get("_title", self.author.title)
@property
def is_blocking(self):
return self.__dict__.get('_is_blocking', 0)
@property
def is_blocked(self):
return self.__dict__.get('_is_blocked', 0)
@property
def body(self):
if self.comment_aux: return self.comment_aux.body
else: return ""
@body.setter
def body(self, x):
self.comment_aux.body = x
g.db.add(self.comment_aux)
@property
def body_html(self):
return self.comment_aux.body_html
@body_html.setter
def body_html(self, x):
self.comment_aux.body_html = x
g.db.add(self.comment_aux)
def realbody(self, v):
body = self.comment_aux.body_html
if not v or v.slurreplacer: body = body.replace(" nigger"," 🏀").replace(" Nigger"," 🏀").replace(" NIGGER"," 🏀").replace(" pedo"," libertarian").replace(" Pedo"," Libertarian ").replace(" PEDO"," LIBERTARIAN ").replace(" tranny"," 🚄").replace(" Tranny"," 🚄").replace(" TRANNY"," 🚄").replace(" fag"," cute twink").replace(" Fag"," Cute twink").replace(" FAG"," CUTE TWINK").replace(" faggot"," cute twink").replace(" Faggot"," Cute twink").replace(" FAGGOT"," CUTE TWINK").replace(" trump"," DDR").replace(" Trump"," DDR").replace(" TRUMP"," DDR").replace(" biden"," DDD").replace(" Biden"," DDD").replace(" BIDEN"," DDD").replace(" steve akins"," penny verity oaken").replace(" Steve Akins"," Penny Verity Oaken").replace(" STEVE AKINS"," PENNY VERITY OAKEN").replace(" RETARD"," RSLUR").replace(" rapist"," male feminist").replace(" Rapist"," Male feminist").replace(" RAPIST"," MALE FEMINIST").replace(" RETARD"," RSLUR").replace(" rapist"," male feminist").replace(" Rapist"," Male feminist").replace(" RAPIST"," MALE FEMINIST").replace(" RETARD"," RSLUR").replace(" rapist"," male feminist").replace(" Rapist"," Male feminist").replace(" RAPIST"," MALE FEMINIST").replace(" kill yourself"," keep yourself safe").replace(" KILL YOURSELF"," KEEP YOURSELF SAFE")
if v and not v.oldreddit: body = body.replace("old.reddit.com", "reddit.com")
return body
@property
def ban_reason(self):
return self.comment_aux.ban_reason
@ban_reason.setter
def ban_reason(self, x):
self.comment_aux.ban_reason = x
g.db.add(self.comment_aux)
@property
def flag_count(self):
return len(self.flags)
@property
def award_count(self):
return len(self.awards)
def collapse_for_user(self, v):
if self.over_18 and not (v and v.over_18) and not self.post.over_18:
return True
if not v:
return False
if self.is_offensive and v.hide_offensive:
return True
if self.is_bot and v.hide_bot:
return True
if any([x in self.body for x in v.filter_words]):
return True
if self.is_banned: return True
return False
@property
def flagged_by(self):
return [x.user for x in self.flags]
@property
def self_download_json(self):
#This property should never be served to anyone but author and admin
if not self.is_banned and not self.is_banned:
return self.json_core
data= {
"author": self.author.name,
"body": self.body,
"body_html": self.body_html,
"is_banned": bool(self.is_banned),
"deleted_utc": self.deleted_utc,
'created_utc': self.created_utc,
'id': self.base36id,
'fullname': self.fullname,
'permalink': self.permalink,
'post_id': self.post.base36id,
'level': self.level
}
if self.level>=2:
data['parent_comment_id']= base36encode(self.parent_comment_id)
return data
@property
def json_admin(self):
data= self.json_raw
data["creation_ip"] = self.creation_ip
data["creation_region"] = self.creation_region
return data
def is_guildmaster(self, perm=None):
mod=self.__dict__.get('_is_guildmaster', False)
if not mod:
return False
elif not perm:
return True
else:
return mod.perm_full or mod.__dict__[f"perm_{perm}"]
return output
@property
def is_exiled_for(self):
return self.__dict__.get('_is_exiled_for', None)
@property
@lazy
def is_op(self):
return self.author_id==self.post.author_id and not self.author.is_deleted and not self.post.author.is_deleted and not self.post.is_deleted
class Notification(Base):
__tablename__ = "notifications"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
comment_id = Column(Integer, ForeignKey("comments.id"))
read = Column(Boolean, default=False)
followsender = Column(Integer, default=None)
unfollowsender = Column(Integer, default=None)
blocksender = Column(Integer, default=None)
unblocksender = Column(Integer, default=None)
comment = relationship("Comment", lazy="joined", innerjoin=True)
user=relationship("User", innerjoin=True)
# Server side computed values (copied from corresponding comment)
created_utc = Column(Integer, server_default=FetchedValue())
def __repr__(self):
return f"<Notification(id={self.id})>"
@property
def voted(self):
return 0