make it possible to position poll options

pull/136/head
Aevann 2023-03-01 00:09:16 +02:00
parent 0ff2a35736
commit 55c8a51a1d
11 changed files with 122 additions and 91 deletions

View File

@ -276,20 +276,29 @@ class Comment(Base):
for o in self.options:
input_type = 'radio' if o.exclusive else 'checkbox'
body += f'<div class="custom-control"><input type="{input_type}" class="custom-control-input" id="comment-{o.id}" name="option-{self.id}"'
if o.voted(v): body += " checked"
option_body = f'<div class="custom-control"><input type="{input_type}" class="custom-control-input" id="comment-{o.id}" name="option-{self.id}"'
if o.voted(v): option_body += " checked"
if v:
if self.parent_submission:
sub = self.post.sub
if sub in {'furry','vampire','racist','femboy'} and not v.house.lower().startswith(sub): body += ' disabled '
body += f''' data-nonce="{g.nonce}" data-onclick="poll_vote_{o.exclusive}('{o.id}', '{self.id}', 'comment')"'''
if sub in {'furry','vampire','racist','femboy'} and not v.house.lower().startswith(sub): option_body += ' disabled '
option_body += f''' data-nonce="{g.nonce}" data-onclick="poll_vote_{o.exclusive}('{o.id}', '{self.id}', 'comment')"'''
else:
body += f''' data-nonce="{g.nonce}" data-onclick="poll_vote_no_v()"'''
option_body += f''' data-nonce="{g.nonce}" data-onclick="poll_vote_no_v()"'''
body += f'''><label class="custom-control-label" for="comment-{o.id}">{o.body_html}<span class="presult-{self.id}'''
if not self.total_poll_voted(v): body += ' d-none'
body += f'"> - <a href="/votes/comment/option/{o.id}"><span id="score-comment-{o.id}">{o.upvotes}</span> votes</a></label></div>'''
option_body += f'''><label class="custom-control-label" for="comment-{o.id}">{o.body_html}<span class="presult-{self.id}'''
if not self.total_poll_voted(v): option_body += ' d-none'
option_body += f'"> - <a href="/votes/comment/option/{o.id}"><span id="score-comment-{o.id}">{o.upvotes}</span> votes</a></label></div>'''
if o.exclusive > 1: s = '!!'
elif o.exclusive: s = '&amp;&amp;'
else: s = '$$'
if f'{s}{o.body}{s}' in body:
body = body.replace(f'{s}{o.body}{s}', option_body)
else:
body += option_body
if not self.ghost and self.author.show_sig(v):
body += f'<section id="signature-{self.author.id}" class="user-signature"><hr>{self.author.sig_html}</section>'

View File

@ -11,7 +11,8 @@ class SubmissionOption(Base):
__tablename__ = "submission_options"
id = Column(Integer, primary_key=True)
submission_id = Column(Integer, ForeignKey("submissions.id"))
parent_id = Column(Integer, ForeignKey("submissions.id"))
body = Column(Text)
body_html = Column(Text)
exclusive = Column(Integer)
created_utc = Column(Integer)
@ -66,7 +67,8 @@ class CommentOption(Base):
__tablename__ = "comment_options"
id = Column(Integer, primary_key=True)
comment_id = Column(Integer, ForeignKey("comments.id"))
parent_id = Column(Integer, ForeignKey("comments.id"))
body = Column(Text)
body_html = Column(Text)
exclusive = Column(Integer)
created_utc = Column(Integer)

View File

@ -285,39 +285,49 @@ class Submission(Base):
winner = [x for x in self.options if x.exclusive == 3]
for o in self.options:
if o.exclusive > 1:
body += f'''<div class="custom-control mt-2"><input name="option-{self.id}" autocomplete="off" class="custom-control-input bet" type="radio" id="{o.id}" data-nonce="{g.nonce}" data-onclick="bet_vote(this,'{o.id}')"'''
if o.voted(v): body += " checked "
if not (v and v.coins >= POLL_BET_COINS) or self.total_bet_voted(v): body += " disabled "
option_body = ''
body += f'''><label class="custom-control-label" for="{o.id}">{o.body_html}<span class="presult-{self.id}'''
body += f'"> - <a href="/votes/post/option/{o.id}"><span id="option-{o.id}">{o.upvotes}</span> bets</a>'
if o.exclusive > 1:
option_body += f'''<div class="custom-control mt-2"><input name="option-{self.id}" autocomplete="off" class="custom-control-input bet" type="radio" id="{o.id}" data-nonce="{g.nonce}" data-onclick="bet_vote(this,'{o.id}')"'''
if o.voted(v): option_body += " checked "
if not (v and v.coins >= POLL_BET_COINS) or self.total_bet_voted(v): option_body += " disabled "
option_body += f'''><label class="custom-control-label" for="{o.id}">{o.body_html}<span class="presult-{self.id}'''
option_body += f'"> - <a href="/votes/post/option/{o.id}"><span id="option-{o.id}">{o.upvotes}</span> bets</a>'
if not self.total_bet_voted(v):
body += f'''<span class="cost"> (cost of entry: {POLL_BET_COINS} coins)</span>'''
body += "</label>"
option_body += f'''<span class="cost"> (cost of entry: {POLL_BET_COINS} coins)</span>'''
option_body += "</label>"
if o.exclusive == 3:
body += " - <b>WINNER!</b>"
option_body += " - <b>WINNER!</b>"
if not winner and v and v.admin_level >= PERMS['POST_BETS_DISTRIBUTE']:
body += f'''<button class="btn btn-primary distribute" data-areyousure="postToastReload(this,'/distribute/{o.id}')" data-nonce="{g.nonce}" data-onclick="areyousure(this)">Declare winner</button>'''
body += "</div>"
option_body += f'''<button class="btn btn-primary distribute" data-areyousure="postToastReload(this,'/distribute/{o.id}')" data-nonce="{g.nonce}" data-onclick="areyousure(this)">Declare winner</button>'''
option_body += "</div>"
else:
input_type = 'radio' if o.exclusive else 'checkbox'
body += f'<div class="custom-control mt-2"><input type="{input_type}" class="custom-control-input" id="post-{o.id}" name="option-{self.id}"'
if o.voted(v): body += " checked"
option_body += f'<div class="custom-control mt-2"><input type="{input_type}" class="custom-control-input" id="post-{o.id}" name="option-{self.id}"'
if o.voted(v): option_body += " checked"
if v:
sub = self.sub
if sub in {'furry','vampire','racist','femboy'} and not v.house.lower().startswith(sub): body += ' disabled '
body += f''' data-nonce="{g.nonce}" data-onclick="poll_vote_{o.exclusive}('{o.id}', '{self.id}', 'post')"'''
if sub in {'furry','vampire','racist','femboy'} and not v.house.lower().startswith(sub): option_body += ' disabled '
option_body += f''' data-nonce="{g.nonce}" data-onclick="poll_vote_{o.exclusive}('{o.id}', '{self.id}', 'post')"'''
else:
body += f''' data-nonce="{g.nonce}" data-onclick="poll_vote_no_v()"'''
option_body += f''' data-nonce="{g.nonce}" data-onclick="poll_vote_no_v()"'''
body += f'''><label class="custom-control-label" for="post-{o.id}">{o.body_html}<span class="presult-{self.id}'''
if not self.total_poll_voted(v): body += ' d-none'
body += f'"> - <a href="/votes/post/option/{o.id}"><span id="score-post-{o.id}">{o.upvotes}</span> votes</a></label></div>'''
option_body += f'''><label class="custom-control-label" for="post-{o.id}">{o.body_html}<span class="presult-{self.id}'''
if not self.total_poll_voted(v): option_body += ' d-none'
option_body += f'"> - <a href="/votes/post/option/{o.id}"><span id="score-post-{o.id}">{o.upvotes}</span> votes</a></label></div>'''
if o.exclusive > 1: s = '!!'
elif o.exclusive: s = '&amp;&amp;'
else: s = '$$'
if f'{s}{o.body}{s}' in body:
body = body.replace(f'{s}{o.body}{s}', option_body)
else:
body += option_body
if not listing and not self.ghost and self.author.show_sig(v):
body += f'<section id="signature-{self.author.id}" class="user-signature"><hr>{self.author.sig_html}</section>'

View File

@ -466,28 +466,52 @@ def execute_lawlz_actions(v:User, p:Submission):
g.db.add(ma_2)
g.db.add(ma_3)
def process_poll_options(target:Union[Submission, Comment],
cls:Union[Type[SubmissionOption], Type[CommentOption]],
options:Iterable[str], exclusive:int, friendly_name:str,
db:scoped_session) -> None:
for option in options:
if len(option) > 500: abort(400, f"{friendly_name} option too long!")
if cls is SubmissionOption:
option = cls(
submission_id=target.id,
body_html=option,
exclusive=exclusive,
)
else:
option = cls(
comment_id=target.id,
body_html=option,
exclusive=exclusive,
)
db.add(option)
def execute_wordle(c:Comment, body:str):
if not FEATURES['WORDLE']: return
if not "!wordle" in body: return
answer = random.choice(WORDLE_LIST)
c.wordle_result = f'_active_{answer}'
def process_poll_options(v:User, target:Union[Submission, Comment]):
patterns = [(poll_regex, 0), (choice_regex, 1)]
if isinstance(target, Submission) and v and v.admin_level >= PERMS['POST_BETS']:
patterns.append((bet_regex, 2))
option_count = 0
for pattern, exclusive in patterns:
for i in pattern.finditer(target.body):
option_count += 1
if option_count > POLL_MAX_OPTIONS:
abort(400, f"Max number of poll options is {POLL_MAX_OPTIONS}")
body = i.group(1)
if len(body) > 500:
abort(400, f"Poll option body too long! (Max 500 characters)")
if isinstance(target, Submission):
cls = SubmissionOption
else:
cls = CommentOption
g.db.flush()
existing = g.db.query(cls).filter_by(
parent_id=target.id,
body=body,
exclusive=exclusive,
).one_or_none()
if not existing:
option = cls(
parent_id=target.id,
body=body,
body_html=filter_emojis_only(body),
exclusive=exclusive,
)
g.db.add(option)

View File

@ -30,7 +30,7 @@ valid_sub_regex = re.compile("^[a-zA-Z0-9_\-]{3,25}$", flags=re.A)
query_regex = re.compile("(\w+):(\S+)", flags=re.A)
poll_regex = re.compile("\s*\$\$([^\$\n]+)\$\$\s*(?!([^<]*<\/(code|pre|a)>|[^`]*`))", flags=re.A)
bet_regex = re.compile("\s*\$\$\$([^\$\n]+)\$\$\$\s*(?!([^<]*<\/(code|pre|a)>|[^`]*`))", flags=re.A)
bet_regex = re.compile("\s*!!([^\$\n]+)!!\s*(?!([^<]*<\/(code|pre|a)>|[^`]*`))", flags=re.A)
choice_regex = re.compile("\s*&&([^\$\n]+)&&\s*(?!([^<]*<\/(code|pre|a)>|[^`]*`))", flags=re.A)
html_comment_regex = re.compile("<!--.*-->", flags=re.A)

View File

@ -647,18 +647,3 @@ def validate_css(css):
return False, f"The domain '{domain}' is not allowed, please use one of these domains\n\n{approved_embed_hosts}."
return True, ""
def sanitize_poll_options(v:User, body:str, allow_bets:bool) -> tuple[str, List[Any], List[Any], List[Any]]:
def sanitize_poll_type(body:str, re:re.Pattern) -> tuple[str, List[str]]:
opts = []
for i in list(re.finditer(body))[:POLL_MAX_OPTIONS] if POLL_MAX_OPTIONS else list(re.finditer(body)):
opts.append(filter_emojis_only(i.group(1)))
body = body.replace(i.group(0), "")
return (body, opts)
bets = []
if allow_bets and v and v.admin_level >= PERMS['POST_BETS']:
body, bets = sanitize_poll_type(body, bet_regex)
body, options = sanitize_poll_type(body, poll_regex)
body, choices = sanitize_poll_type(body, choice_regex)
return (body, bets, options, choices)

View File

@ -150,8 +150,6 @@ def comment(v:User):
if not v.admin_level >= PERMS['POST_COMMENT_MODERATION'] and parent_user.any_block_exists(v):
abort(403, "You can't reply to users who have blocked you or users that you have blocked!")
body, _, options, choices = sanitize_poll_options(v, body, False)
if request.files.get("file") and not g.is_tor:
files = request.files.getlist('file')[:20]
@ -264,15 +262,14 @@ def comment(v:User):
g.db.add(c)
g.db.flush()
process_poll_options(v, c)
execute_blackjack(v, c, c.body, "comment")
execute_under_siege(v, c, c.body, "comment")
if c.level == 1: c.top_comment_id = c.id
else: c.top_comment_id = parent.top_comment_id
process_poll_options(c, CommentOption, options, 0, "Poll", g.db)
process_poll_options(c, CommentOption, choices, 1, "Poll", g.db)
if post_target.id not in ADMIGGER_THREADS and v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in c.body.lower() and not (posting_to_submission and post_target.sub == 'chudrama'):
c.is_banned = True
c.ban_reason = "AutoJanny"
@ -403,10 +400,6 @@ def edit_comment(cid, v):
elif v.bird and len(body) > 140:
abort(403, "You have to type less than 140 characters!")
body, _, options, choices = sanitize_poll_options(v, body, False)
process_poll_options(c, CommentOption, options, 0, "Poll", g.db)
process_poll_options(c, CommentOption, choices, 1, "Poll", g.db)
execute_antispam_comment_check(body, v)
body = process_files(request.files, v, body)
@ -428,6 +421,9 @@ def edit_comment(cid, v):
abort(403, "You can only type marseys!")
c.body = body
process_poll_options(v, c)
c.body_html = body_html
execute_blackjack(v, c, c.body, "comment")

View File

@ -35,7 +35,7 @@ def vote_option(option_id, v):
if option.exclusive:
vote = g.db.query(SubmissionOptionVote).join(SubmissionOption).filter(
SubmissionOptionVote.user_id==v.id,
SubmissionOptionVote.submission_id==option.submission_id,
SubmissionOptionVote.submission_id==option.parent_id,
SubmissionOption.exclusive==option.exclusive).all()
if vote:
if option.exclusive == 2: abort(400, "You already voted on this bet!")
@ -47,7 +47,7 @@ def vote_option(option_id, v):
vote = SubmissionOptionVote(
option_id=option_id,
user_id=v.id,
submission_id=option.submission_id,
submission_id=option.parent_id,
)
g.db.add(vote)
elif existing and not option.exclusive:
@ -107,7 +107,7 @@ def vote_option_comment(option_id, v):
if option.exclusive:
vote = g.db.query(CommentOptionVote).join(CommentOption).filter(
CommentOptionVote.user_id==v.id,
CommentOptionVote.comment_id==option.comment_id,
CommentOptionVote.comment_id==option.parent_id,
CommentOption.exclusive==1).one_or_none()
if vote:
g.db.delete(vote)
@ -117,7 +117,7 @@ def vote_option_comment(option_id, v):
vote = CommentOptionVote(
option_id=option_id,
user_id=v.id,
comment_id=option.comment_id,
comment_id=option.parent_id,
)
g.db.add(vote)
elif existing:

View File

@ -294,11 +294,6 @@ def edit_post(pid, v):
body = body.strip()[:POST_BODY_LENGTH_LIMIT(v)] # process_files() may be adding stuff to the body
if body != p.body:
body, bets, options, choices = sanitize_poll_options(v, body, False)
process_poll_options(p, SubmissionOption, bets, 2, "Bet", g.db)
process_poll_options(p, SubmissionOption, options, 0, "Poll", g.db)
process_poll_options(p, SubmissionOption, choices, 1, "Poll", g.db)
torture = (v.agendaposter and not v.marseyawarded and p.sub != 'chudrama' and v.id == p.author_id)
body_html = sanitize(body, golden=False, limit_pings=100, showmore=False, torture=torture)
@ -309,6 +304,8 @@ def edit_post(pid, v):
p.body = body
process_poll_options(v, p)
execute_under_siege(v, p, p.body, 'submission')
for text in [p.body, p.title, p.url]:
@ -649,8 +646,6 @@ def submit_post(v:User, sub=None):
if len(url) > 2048:
abort(400, "There's a 2048 character limit for URLs!")
body, bets, options, choices = sanitize_poll_options(v, body, True)
body = process_files(request.files, v, body)
body = body.strip()[:POST_BODY_LENGTH_LIMIT(v)] # process_files() adds content to the body, so we need to re-strip
@ -699,13 +694,11 @@ def submit_post(v:User, sub=None):
g.db.add(post)
g.db.flush()
process_poll_options(v, post)
for text in {post.body, post.title, post.url}:
if execute_blackjack(v, post, text, 'submission'): break
process_poll_options(post, SubmissionOption, bets, 2, "Bet", g.db)
process_poll_options(post, SubmissionOption, options, 0, "Poll", g.db)
process_poll_options(post, SubmissionOption, choices, 1, "Poll", g.db)
vote = Vote(user_id=v.id,
vote_type=1,
submission_id=post.id

View File

@ -0,0 +1,11 @@
alter table submission_options add column body character varying(500);
update submission_options set body=body_html;
alter table submission_options alter column body set not null;
alter table comment_options add column body character varying(500);
update comment_options set body=body_html;
alter table comment_options alter column body set not null;
alter table submission_options rename column submission_id to parent_id;
alter table comment_options rename column comment_id to parent_id;

View File

@ -327,7 +327,8 @@ CREATE TABLE public.comment_option_votes (
CREATE TABLE public.comment_options (
id integer DEFAULT nextval('public.comment_option_id_seq'::regclass) NOT NULL,
comment_id integer NOT NULL,
parent_id integer NOT NULL,
body character varying(500) NOT NULL,
body_html character varying(500) NOT NULL,
exclusive integer NOT NULL,
created_utc integer
@ -870,7 +871,8 @@ CREATE TABLE public.submission_option_votes (
CREATE TABLE public.submission_options (
id integer DEFAULT nextval('public.submission_option_id_seq'::regclass) NOT NULL,
submission_id integer NOT NULL,
parent_id integer NOT NULL,
body character varying(500) NOT NULL,
body_html character varying(500) NOT NULL,
exclusive integer NOT NULL,
created_utc integer
@ -2872,4 +2874,3 @@ ALTER TABLE ONLY public.comments
--
-- PostgreSQL database dump complete
--