async def emojis(self, context: commands.Context, poll_id: int, emoji_list: converter.to_emoji_list, *, options=""): message, channel, previous_emoji_list, is_exclusive, required_role_name, time, organizer = \ await self.get_message_env(poll_id, raise_if_not_found=True) if context.author != organizer: checker.has_any_mod_role(context, print_error=True) if not context.author.permissions_in(channel).send_messages: raise exceptions.ForbiddenChannel(channel) if not emoji_list: raise commands.MissingRequiredArgument( context.command.params['emoji_list']) if len(emoji_list) > 20: raise exceptions.OversizedArgument(f"{len(emoji_list)} emojis", "20 emojis") required_role_name = utils.get_option_value(options, 'role') if required_role_name: utils.try_get( # Raise if role does not exist self.guild.roles, error=exceptions.UnknownRole(required_role_name), name=required_role_name) is_exclusive = utils.is_option_enabled(options, 'exclusive') previous_reactions = [ utils.try_get(message.reactions, error=exceptions.MissingEmoji(previous_emoji), emoji=previous_emoji) for previous_emoji in previous_emoji_list ] for previous_reaction in previous_reactions: await previous_reaction.remove(zbot.bot.user) for emoji in emoji_list: await message.add_reaction(emoji) embed = self.build_announce_embed(message.embeds[0].description, is_exclusive, required_role_name, organizer, time, self.guild.roles) await message.edit(embed=embed) job_id = self.pending_polls[message.id]['_id'] poll_data = { 'emoji_codes': list(map(lambda e: e if isinstance(e, str) else e.id, emoji_list)), 'is_exclusive': is_exclusive, 'required_role_name': required_role_name } zbot.db.update_poll_data(job_id, poll_data) self.pending_polls[message.id].update(poll_data) await context.send( f"Émojis du sondage d'identifiant `{poll_id}` mis à jour : <{message.jump_url}>" )
async def emoji(self, context: commands.Context, lottery_id: int, emoji: typing.Union[discord.Emoji, str]): message, channel, previous_emoji, nb_winners, time, organizer = \ await self.get_message_env(lottery_id, raise_if_not_found=True) if context.author != organizer: checker.has_any_mod_role(context, print_error=True) if isinstance(emoji, str) and emojis.emojis.count(emoji) != 1: raise exceptions.ForbiddenEmoji(emoji) previous_reaction = utils.try_get( message.reactions, error=exceptions.MissingEmoji(previous_emoji), emoji=previous_emoji) await previous_reaction.remove(zbot.bot.user) await message.add_reaction(emoji) embed = self.build_announce_embed(emoji, nb_winners, organizer, time, self.guild.roles) await message.edit(embed=embed) job_id = self.pending_lotteries[message.id]['_id'] lottery_data = { 'emoji_code': emoji if isinstance(emoji, str) else emoji.id } zbot.db.update_job_data(self.JOBSTORE, job_id, lottery_data) self.pending_lotteries[message.id].update(lottery_data) await context.send( f"Émoji du tirage au sort d'identifiant `{lottery_id}` remplacé par \"{emoji}\" : " f"<{message.jump_url}>")
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
def build_announce_embed( description, is_exclusive, required_role_name, organizer, time, guild_roles ): embed = discord.Embed( title=f"Clôture du sondage le {converter.humanize_datetime(time)} :alarm_clock:", description=description, color=Poll.EMBED_COLOR, ) embed.add_field( name="Participation", value="Réagissez avec un émoji ci-dessous." if is_exclusive else "Réagissez avec les émojis ci-dessous." ) embed.add_field( name="Choix multiple", value="✅" if not is_exclusive else "❌" ) embed.add_field( name="Rôle requis", value=utils.try_get( guild_roles, error=exceptions.UnknownRole(required_role_name), name=required_role_name ).mention if required_role_name else "Aucun" ) embed.set_author( name=f"Organisateur : @{organizer.display_name}", icon_url=organizer.avatar_url ) return embed
async def draw(message, emoji, nb_winners, seed=None): await Lottery.prepare_seed(seed) reaction = utils.try_get(message.reactions, error=exceptions.MissingEmoji(emoji), emoji=emoji) players, winners = await Lottery.pick_winners(message.guild, reaction, nb_winners) return players, reaction, winners
async def simulate(self, context: commands.Context, src_channel: discord.TextChannel, message_id: int, emoji_list: converter.to_emoji_list = (), dest_channel: discord.TextChannel = None, *, options=""): if dest_channel and not context.author.permissions_in( dest_channel).send_messages: raise exceptions.ForbiddenChannel(dest_channel) is_exclusive = utils.is_option_enabled(options, 'exclusive') required_role_name = utils.get_option_value(options, 'role') if required_role_name: utils.try_get( # Raise if role does not exist self.guild.roles, error=exceptions.UnknownRole(required_role_name), name=required_role_name) message = await utils.try_get_message( src_channel, message_id, error=exceptions.MissingMessage(message_id)) if not emoji_list: if len(message.reactions) == 0: raise exceptions.MissingConditionalArgument( "Une liste d'émojis doit être fournie si le message ciblé n'a pas de réaction." ) else: emoji_list = [reaction.emoji for reaction in message.reactions] elif len(emoji_list) > 20: raise exceptions.OversizedArgument(f"{len(emoji_list)} emojis", "20 emojis") reactions, results = await Poll.count_votes(message, emoji_list, is_exclusive, required_role_name) announcement = await ( dest_channel or context).send("Évaluation des votes sur base des réactions.") await Poll.announce_results(results, message, announcement.channel, is_exclusive, required_role_name, dest_message=announcement)
async def announce_results( results: dict, src_message: discord.Message, channel: discord.TextChannel, is_exclusive, required_role_name, organizer: discord.User = None, dest_message: discord.Message = None ): announcement_embed = discord.Embed( title="Résultats du sondage", description=f"[Cliquez ici pour accéder au sondage 🗳️]({src_message.jump_url})", color=Poll.EMBED_COLOR ) # Compute ranking of votes count: {votes_count: position, votes_count: position, ...} votes_ranking = {k: v for v, k in enumerate(sorted(set(results.values()), reverse=True), start=1)} # Compute ranking of emojis: {emoji: position, emoji: position, ...} emoji_ranking = {emoji: votes_ranking[vote_count] for emoji, vote_count in results.items()} ranking_medals = {1: "🥇", 2: "🥈", 3: "🥉"} for emoji, vote_count in results.items(): rank = emoji_ranking[emoji] medal_message = f" {ranking_medals.get(rank, '')}" if vote_count > 0 else "" announcement_embed.add_field( name=f"{emoji}\u2000**# {rank}**", value=f"Votes: **{vote_count}**{medal_message}" ) if organizer: announcement_embed.set_author( name=f"Organisateur : @{organizer.display_name}", icon_url=organizer.avatar_url) if dest_message: # Poll simulation await dest_message.edit(embed=announcement_embed) else: # Assessment of a poll dest_message = await channel.send(embed=announcement_embed) embed = discord.Embed( title="Sondage clôturé", description=f"[Cliquez ici pour accéder aux résultats 📊]({dest_message.jump_url})" + (f"\n\n{src_message.embeds[0].description}" if src_message.embeds[0].description else ""), color=Poll.EMBED_COLOR ) embed.add_field(name="Choix multiple", value="✅" if not is_exclusive else "❌") embed.add_field( name="Rôle requis", value=utils.try_get( src_message.guild.roles, error=exceptions.UnknownRole(required_role_name), name=required_role_name ).mention if required_role_name else "Aucun" ) if organizer: embed.set_author( name=f"Organisateur : @{organizer.display_name}", icon_url=organizer.avatar_url) await src_message.edit(embed=embed) logger.debug(f"Poll results: {results}")
async def record_message_count(self, time): message_counts = [] for channel_name in self.DISCUSSION_CHANNELS: channel = utils.try_get(self.guild.channels, name=channel_name) channel_message_count = len(await channel.history( after=converter.to_utc(time - datetime.timedelta(hours=1)).replace( tzinfo=None), limit=999).flatten()) message_counts.append({ 'count': channel_message_count, 'channel_id': channel.id }) zbot.db.insert_timed_message_counts(time, message_counts)
def build_announce_embed(emoji, nb_winners, organizer, time, guild_roles): embed = discord.Embed( title= f"Tirage au sort le {converter.humanize_datetime(time)} :alarm_clock:", color=Lottery.EMBED_COLOR) embed.add_field( name="Nombre de gagnants", value=f"**{nb_winners}** joueur{('s' if nb_winners > 1 else '')}") embed.add_field(name="Inscription", value=f"Réagissez avec {emoji}") embed.add_field(name="Rôle requis", value=utils.try_get( guild_roles, error=exceptions.UnknownRole( Lottery.USER_ROLE_NAMES[0]), name=Lottery.USER_ROLE_NAMES[0]).mention) embed.set_author(name=f"Organisateur : @{organizer.display_name}", icon_url=organizer.avatar_url) return embed
async def members(self, context: commands.Context): role_sizes = {} for primary_role_name in self.PRIMARY_ROLES: guild_role = utils.try_get(context.guild.roles, name=primary_role_name) role_sizes[primary_role_name] = len(guild_role.members) embed = discord.Embed( title=f"Décompte des membres du serveur", description=f"Total : **{len(context.guild.members)}** membres pour " f"**{len(self.PRIMARY_ROLES)}** roles principaux", color=self.EMBED_COLOR) for role_name in self.PRIMARY_ROLES: embed.add_field(name=role_name, value=f"**{role_sizes[role_name]}** membres", inline=True) embed.add_field(name="Banni", value=f"**{len(await context.guild.bans())}** boulets", inline=True) await context.send(embed=embed)
async def cancel(self, context: commands.Context, poll_id: int): message, _, emoji_list, _, _, _, organizer = await self.get_message_env( poll_id, raise_if_not_found=False) if context.author != organizer: checker.has_any_mod_role(context, print_error=True) if message: for emoji in emoji_list: reaction = utils.try_get( message.reactions, error=exceptions.MissingEmoji(emoji), emoji=emoji ) await reaction.remove(zbot.bot.user) embed = discord.Embed( title=f"Sondage __annulé__ par {context.author.display_name}", description=message.embeds[0].description if message.embeds[0].description else "", color=Poll.EMBED_COLOR ) embed.set_author(name=f"Organisateur : @{organizer.display_name}", icon_url=organizer.avatar_url) await message.edit(embed=embed) await message.unpin() self.remove_pending_poll(message.id, cancel_job=True) await context.send(f"Sondage d'identifiant `{poll_id}` annulé : <{message.jump_url}>")
async def cancel(self, context: commands.Context, lottery_id: int): message, _, emoji, _, _, organizer = await self.get_message_env( lottery_id, raise_if_not_found=False) if context.author != organizer: checker.has_any_mod_role(context, print_error=True) if message: reaction = utils.try_get(message.reactions, error=exceptions.MissingEmoji(emoji), emoji=emoji) await reaction.remove(zbot.bot.user) embed = discord.Embed( title= f"Tirage au sort __annulé__ par {context.author.display_name}", color=Lottery.EMBED_COLOR) embed.set_author(name=f"Organisateur : @{organizer.display_name}", icon_url=organizer.avatar_url) await message.edit(embed=embed) self.remove_pending_lottery(message.id, cancel_job=True) await context.send( f"Tirage au sort d'identifiant `{lottery_id}` annulé : <{message.jump_url}>" )
async def start(self, context: commands.Context, announce: str, description: str, dest_channel: discord.TextChannel, emoji_list: converter.to_emoji_list, time: converter.to_future_datetime, *, options=""): # Check arguments if not context.author.permissions_in(dest_channel).send_messages: raise exceptions.ForbiddenChannel(dest_channel) if not emoji_list: raise commands.MissingRequiredArgument( context.command.params['emoji_list']) if len(emoji_list) > 20: raise exceptions.OversizedArgument(f"{len(emoji_list)} emojis", "20 emojis") do_announce = utils.is_option_enabled(options, 'do-announce') do_pin = utils.is_option_enabled(options, 'pin') if do_announce or do_pin: checker.has_any_mod_role(context, print_error=True) required_role_name = utils.get_option_value(options, 'role') if required_role_name: utils.try_get( # Raise if role does not exist self.guild.roles, error=exceptions.UnknownRole(required_role_name), name=required_role_name) # Run command is_exclusive = utils.is_option_enabled(options, 'exclusive') organizer = context.author prefixed_announce = utils.make_announce( context.guild, announce, do_announce and self.ANNOUNCE_ROLE_NAME) embed = self.build_announce_embed(description, is_exclusive, required_role_name, organizer, time, self.guild.roles) message = await dest_channel.send(prefixed_announce, embed=embed) for emoji in emoji_list: await message.add_reaction(emoji) if do_pin: await message.pin() # Register data job_id = scheduler.schedule_stored_job( zbot.db.PENDING_POLLS_COLLECTION, time, self.close_poll, message.id).id poll_data = { 'poll_id': self.get_next_poll_id(), 'message_id': message.id, 'channel_id': dest_channel.id, 'emoji_codes': list(map(lambda e: e if isinstance(e, str) else e.id, emoji_list)), 'organizer_id': organizer.id, 'is_exclusive': is_exclusive, 'required_role_name': required_role_name, } zbot.db.update_poll_data(job_id, poll_data) # Add data managed by scheduler later to avoid updating the database with them poll_data.update({ '_id': job_id, 'next_run_time': converter.to_timestamp(time) }) self.pending_polls[message.id] = poll_data # Confirm command await context.send( f"Sondage d'identifiant `{poll_data['poll_id']}` programmé : <{message.jump_url}>." )