async def count_votes(message, emoji_list, is_exclusive, required_role_name): """Count the votes and compute the results of the poll.""" reactions = [ utils.try_get(message.reactions, error=exceptions.MissingEmoji(emoji), emoji=emoji) for emoji in emoji_list ] votes = { reaction.emoji: await reaction.users().flatten() for reaction in reactions } results = {} for emoji, voters in votes.items(): valid_votes_count = 0 for voter in voters: if voter.bot: continue # Exclude bot votes if is_exclusive and any([ voter in votes[other_emoji] for other_emoji in votes if other_emoji != emoji ]): continue # Only count vote of voters having voted once in exclusive mode if required_role_name and not checker.has_guild_role( message.guild, voter, required_role_name): continue # Only count vote of voters having the required role, if set valid_votes_count += 1 results[emoji] = valid_votes_count return reactions, results
async def check_authors_clan_contact_role(context, announces): """Check that all announce authors have the clan contact role.""" if missing_clan_contact_role_announces := list(filter( lambda a: not checker.has_guild_role(context.guild, a.author, Stats.CLAN_CONTACT_ROLE_NAME), announces )): for block in utils.make_message_blocks([ f"{announce.author.mention} ne possède pas le rôle @{Stats.CLAN_CONTACT_ROLE_NAME} nécessaire à la " f"publication d'une annonce : {announce.jump_url}" for announce in missing_clan_contact_role_announces ]): await context.send(block)
async def on_reaction_add(self, reaction, user): message = reaction.message message_id = message.id emoji = reaction.emoji if message_id in Poll.pending_polls: poll_emoji_codes = Poll.pending_polls[message_id]['emoji_codes'] same_emoji = any([ emoji == poll_emoji_code if isinstance(emoji, str) else emoji.id == poll_emoji_code for poll_emoji_code in poll_emoji_codes ]) if same_emoji and not user.bot: if required_role_name := Poll.pending_polls[message_id][ 'required_role_name']: if not checker.has_guild_role(self.guild, user, required_role_name): try: await utils.try_dm( user, f"Vous devez avoir le rôle @{required_role_name} pour " f"participer à ce sondage.") await message.remove_reaction(emoji, user) return # Don't enter other check blocks except (discord.errors.HTTPException, discord.errors.NotFound, discord.errors.Forbidden): pass if Poll.pending_polls[message_id]['is_exclusive']: for existing_reaction in list( filter( lambda r: r.emoji != emoji and (r.emoji if isinstance(r.emoji, str) else r.emoji.id ) in poll_emoji_codes, message.reactions)): existing_reaction_users = await existing_reaction.users( ).flatten() if discord.utils.get(existing_reaction_users, id=user.id): try: await utils.try_dm( user, f"Vous ne pouvez voter que pour une seule option." ) await message.remove_reaction(emoji, user) return # Don't enter other check blocks except (discord.errors.HTTPException, discord.errors.NotFound, discord.errors.Forbidden): pass break
async def validate_recruitments(self, context): if not checker.has_guild_role(context.guild, context.author, Stats.CLAN_CONTACT_ROLE_NAME): raise exceptions.MissingRoles([Stats.CLAN_CONTACT_ROLE_NAME]) recruitment_channel = self.guild.get_channel( Admin.RECRUITMENT_CHANNEL_ID) all_recruitment_announces = await recruitment_channel.history( ).flatten() recruitment_announces = list( filter(lambda a: a.author == context.author, all_recruitment_announces)) last_recruitment_announce = recruitment_announces and recruitment_announces[ 0] if not last_recruitment_announce: await context.send("Aucune annonce de recrutement n'a été trouvée." ) else: patched_context = copy(context) patched_context.send = self.mock_send validation_succeeded = True if await Admin.check_recruitment_announces_uniqueness( patched_context, recruitment_announces): validation_succeeded = False await context.send( f"Tu as publié {len(recruitment_announces)} annonces. Une seule est autorisée à la fois." ) if await Admin.check_recruitment_announces_length( patched_context, [last_recruitment_announce]): validation_succeeded = False apparent_length = Admin.compute_apparent_length( last_recruitment_announce) await context.send( f"L'annonce est d'une longueur apparente de **{apparent_length}** caractères (max " f"{Admin.MAX_RECRUITMENT_ANNOUNCE_LENGTH}). Réduit sa longueur en retirant du contenu ou en " f"réduisant le nombre de sauts de lignes.") if await Admin.check_recruitment_announces_embeds( patched_context, [last_recruitment_announce]): validation_succeeded = False await context.send( f"Ton annonce contient un embed, ce qui n'est pas autorisé. Utilise un raccourcisseur d'URLs comme " f"<https://tinyurl.com> pour héberger tes liens.") if await Admin.check_recruitment_announces_timespan( patched_context, recruitment_channel, [last_recruitment_announce]): validation_succeeded = False await context.send( f"Ton annonce a été postée avant le délai minimum de {Admin.MIN_RECRUITMENT_ANNOUNCE_TIMESPAN} " f"jours entre deux annonces.") if validation_succeeded: await context.send( f"L'annonce ne présente aucun problème. :ok_hand: ") last_announce_time_localized = converter.to_utc( zbot.db.load_recruitment_announces_data( query={'author': context.author.id}, order=[('time', -1)])[0]['time']) min_timespan = datetime.timedelta( # Apply a tolerance of 2 days for players interpreting the 30 days range as "one month". # This is a subtraction because the resulting value is the number of days to wait before posting again. days=Admin.MIN_RECRUITMENT_ANNOUNCE_TIMESPAN - Admin.RECRUITMENT_ANNOUNCE_TIMESPAN_TOLERANCE) next_announce_time_localized = last_announce_time_localized + min_timespan await context.send( f"Tu pourras à nouveau poster une annonce à partir du " f"{converter.to_human_format(next_announce_time_localized)}.")