From 2178a3ec99972051fc7a44ce4e62ec216af8de46 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 14 Feb 2021 01:48:56 -0600 Subject: [PATCH] Move Submission votes count logic into model, add improved verification system, add verification that the bot is always reacting to the message (on every reaction add/removal) --- bot/cogs/contest.py | 22 +++++++++++++--------- bot/models.py | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/bot/cogs/contest.py b/bot/cogs/contest.py index 5643605..e97ec0a 100644 --- a/bot/cogs/contest.py +++ b/bot/cogs/contest.py @@ -232,16 +232,20 @@ class ContestCog(commands.Cog): with self.bot.get_session() as session: guild: Guild = session.query(Guild).get(payload.guild_id) if payload.channel_id == guild.submission_channel: + channel: discord.TextChannel = self.bot.get_channel(payload.channel_id) + message: discord.PartialMessage = channel.get_partial_message(payload.message_id) if payload.emoji.id != constants.Emoji.UPVOTE: - channel: discord.TextChannel = self.bot.get_channel(payload.channel_id) - message: discord.PartialMessage = channel.get_partial_message(payload.message_id) await message.remove_reaction(payload.emoji, payload.member) else: submission: Submission = session.query(Submission).get(payload.message_id) if submission is None: logger.warning(f'Upvote reaction added to message {payload.message_id}, but no Submission found in database.') else: - submission.votes += 1 + submission.increment() + # Make sure our reaction exists, verify vote count + self_reacted = await submission.verify(await message.fetch(), self.bot.user) + if not self_reacted: + await message.add_reaction(self.bot.get_emoji(constants.Emoji.UPVOTE)) @commands.Cog.listener() async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent) -> None: @@ -262,16 +266,16 @@ class ContestCog(commands.Cog): if submission is None: logger.warning(f'Upvote reaction removed from message {payload.message_id}, but no Submission found in database.') else: - submission.votes -= 1 + submission.decrement() # Get the actual number of votes from the message channel: discord.TextChannel = self.bot.get_channel(payload.channel_id) message: discord.Message = await channel.fetch_message(payload.message_id) - reaction: discord.Reaction - for reaction in filter(lambda _reaction: isinstance(_reaction.emoji, (discord.Emoji, discord.PartialEmoji)) - and _reaction.emoji.id == constants.Emoji.UPVOTE, - message.reactions): - submission.set_votes(reaction.count) + self_reacted = await submission.verify(await message.fetch(), self.bot.user) + + # Make sure our reaction exists + if not self_reacted: + await message.add_reaction(self.bot.get_emoji(constants.Emoji.UPVOTE)) @commands.Cog.listener() async def on_raw_reaction_clear(self, payload: discord.RawReactionActionEvent) -> None: diff --git a/bot/models.py b/bot/models.py index b9fa8f6..de3ad1b 100644 --- a/bot/models.py +++ b/bot/models.py @@ -2,8 +2,9 @@ import datetime import enum import functools import logging -from typing import List +from typing import List, Optional, Union +import discord from sqlalchemy import Boolean, Column, DateTime, Enum, ForeignKey, Integer, Text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship @@ -80,11 +81,38 @@ class Submission(Base): period_id = Column(Integer, ForeignKey("period.id")) # The id of the period this Submission relates to. period = relationship("Period", back_populates="submissions") # The period this submission was made in. - def set_votes(self, n: int) -> None: + def increment(self) -> None: + """Increase the number of votes by one.""" + self.votes += 1 + + def decrement(self) -> None: + """Decrease the number of votes by one.""" + self.votes -= 1 + + async def verify(self, message: discord.Message, user: Optional[Union[discord.ClientUser, discord.User]] = None) -> bool: """Sets the number of votes for this Submission.""" - if self.votes != n: - logger.warning(f'True vote count was off for Submission {self.id} by {n - self.votes}.') - self.votes = n + saw_user = False + for reaction in message.reactions: + # Check that it's a custom Emoji and that the Emoji is the expected Upvote emoji + if isinstance(reaction.emoji, (discord.Emoji, discord.PartialEmoji)): + if reaction.emoji.id == constants.Emoji.UPVOTE: + # If a user was specified, look for him in the reactions + if user is not None: + reacting_user: Union[discord.Member, discord.User] + async for reacting_user in reaction.users(): + # Check if the user who reacted to this emoji is the user we are looking for + if reacting_user.id == user.id: + saw_user = True + break + + # Tally the number of votes + votes = reaction.count - 1 + if votes != self.votes: + # Make a racket if we counted wrong or somehow missed reactions + logger.warning(f'True vote count was off for Submission {self.id} by {votes - self.votes}.') + self.votes = votes + + return saw_user class Period(Base):