diff --git a/bot/cogs/poll.py b/bot/cogs/poll.py deleted file mode 100644 index 95feb86c..00000000 --- a/bot/cogs/poll.py +++ /dev/null @@ -1,151 +0,0 @@ -import discord -from discord.ext import commands - - -class Polls(commands.Cog): - def __init__(self, bot: commands.AutoShardedBot): - self.__bot = bot - - @property - def reactions(self): - return { - 1: "1️⃣", - 2: "2️⃣", - 3: "3️⃣", - 4: "4️⃣", - 5: "5️⃣", - 6: "6️⃣", - 7: "7️⃣", - 8: "8️⃣", - 9: "9️⃣", - 10: "🔟", - } - - def poll_check(self, message: discord.Message): - try: - embed = message.embeds[0] - except Exception: - return False - if str(embed.footer.text).count("Poll by") == 1: - return message.author == self.__bot.user - return False - - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - channel: discord.TextChannel = self.__bot.get_channel(payload.channel_id) - message: discord.Message = await channel.fetch_message(payload.message_id) - - if payload.user_id == self.__bot.user.id: - return - - if not self.poll_check(message): - return - - emojis = list(self.reactions.values()) - if str(payload.emoji) not in emojis: - return - - for reaction in message.reactions: - if str(reaction) not in emojis: - return - - if str(reaction.emoji) != str(payload.emoji): - user = self.__bot.get_user(payload.user_id) - await message.remove_reaction(reaction.emoji, user) - - @commands.group() - async def poll(self, ctx): - """Polls""" - if ctx.invoked_subcommand is None: - return await ctx.send_help(self.__bot.get_command("poll")) - - @poll.command() - @commands.cooldown(1, 10, commands.BucketType.channel) - async def new(self, ctx, desc: str, *choices): - """Create a new poll""" - await ctx.message.delete() - - if len(choices) < 2: - ctx.command.reset_cooldown(ctx) - if len(choices) == 1: - return await ctx.send("Can't make a poll with only one choice") - return await ctx.send("You have to enter two or more choices to make a poll") - - if len(choices) > 10: - ctx.command.reset_cooldown(ctx) - return await ctx.send("You can't make a poll with more than 10 choices") - - embed = discord.Embed( - description=f"**{desc}**\n\n" - + "\n\n".join(f"{str(self.reactions[i])} {choice}" for i, choice in enumerate(choices, 1)), - timestamp=discord.utils.utcnow(), - color=discord.colour.Color.gold(), - ) - embed.set_footer(text=f"Poll by {str(ctx.author)}") - msg = await ctx.send(embed=embed) - for i in range(1, len(choices) + 1): - await msg.add_reaction(self.reactions[i]) - - @poll.command() - async def show(self, ctx, message: str): - """Show a poll result""" - await ctx.message.delete() - - try: - *_, channel_id, msg_id = message.split("/") - - try: - channel = self.__bot.get_channel(int(channel_id)) - message = await channel.fetch_message(int(msg_id)) - except Exception: - return await ctx.send("Please provide the message ID/link for a valid poll") - except Exception: - try: - message = await ctx.channel.fetch_message(message) - except Exception: - return await ctx.send("Please provide the message ID/link for a valid poll") - - if self.poll_check(message): - poll_embed = message.embeds[0] - reactions = message.reactions - reactions_total = sum( - [reaction.count - 1 if str(reaction.emoji) in self.reactions.values() else 0 for reaction in reactions] - ) - - options = list( - map( - lambda o: " ".join(o.split()[1:]), - poll_embed.description.split("1️")[1].split("\n\n"), - ) - ) - desc = poll_embed.description.split("1️")[0] - - embed = discord.Embed( - description=desc, - timestamp=poll_embed.timestamp, - color=discord.Color.gold(), - ) - - for i, option in enumerate(options): - reaction_count = reactions[i].count - 1 - indicator = "░" * 20 - if reactions_total != 0: - indicator = "█" * int(((reaction_count / reactions_total) * 100) / 5) + "░" * int( - (((reactions_total - reaction_count) / reactions_total) * 100) / 5 - ) - - embed.add_field( - name=option, - value=f"{indicator} {int((reaction_count / (reactions_total or 1)*100))}%" - f" (**{reaction_count} votes**)", - inline=False, - ) - - embed.set_footer(text="Poll Result") - return await ctx.send(embed=embed) - - return await ctx.send("Please provide the message ID/link for a valid poll") - - -async def setup(bot): - await bot.add_cog(Polls(bot)) diff --git a/bot/extensions/levelling/commands.py b/bot/extensions/levelling/commands.py index eae67cb3..60d0e0be 100644 --- a/bot/extensions/levelling/commands.py +++ b/bot/extensions/levelling/commands.py @@ -230,14 +230,13 @@ def convert_int(integer): xp_offset_x -= xp_text_size[2] - xp_text_size[0] draw.text((xp_offset_x, xp_offset_y), text, font=self.small_font, fill="#fff") - if len(username) >= 15: + if len(username) >= 18: # Truncating the name username = username[:15] + "..." text_bbox = draw.textbbox((0, 0), username, font=self.medium_font) - text_offset_x = bar_offset_x - 10 text_offset_y = bar_offset_y - (text_bbox[3] - text_bbox[1]) - 20 - draw.text((text_offset_x, text_offset_y), username, font=self.medium_font, fill="#fff") + draw.text((bar_offset_x, text_offset_y), username, font=self.medium_font, fill="#fff") # create copy of background background = self.background.copy() @@ -274,8 +273,6 @@ async def rank(self, interaction: core.InteractionType, member: discord.Member = record = await LevellingUser.pool.fetchrow(query, interaction.guild.id, member.id) - log.info(record) - if record.total_xp is None: if member.id == interaction.user.id: return await interaction.response.send_message( diff --git a/bot/extensions/poll/__init__.py b/bot/extensions/poll/__init__.py new file mode 100644 index 00000000..998921e0 --- /dev/null +++ b/bot/extensions/poll/__init__.py @@ -0,0 +1,9 @@ +from bot.core import DiscordBot + +from .commands import Polls +from .events import PollsEvents + + +async def setup(bot: DiscordBot) -> None: + await bot.add_cog(Polls(bot=bot)) + await bot.add_cog(PollsEvents(bot=bot)) diff --git a/bot/extensions/poll/commands.py b/bot/extensions/poll/commands.py new file mode 100644 index 00000000..6dc21729 --- /dev/null +++ b/bot/extensions/poll/commands.py @@ -0,0 +1,203 @@ +import discord +from discord import app_commands, ui +from discord.ext import commands + +from bot import core + + +class PollModal(ui.Modal): + name = ui.TextInput(label="Option name", placeholder="Enter poll option name", max_length=50, required=True) + + def __init__(self, var: discord.Interaction): + self.var = var + super().__init__(title="Poll options") + + @property + def reactions(self): + return { + 0: "1️⃣", + 1: "2️⃣", + 2: "3️⃣", + 3: "4️⃣", + 4: "5️⃣", + 5: "6️⃣", + 6: "7️⃣", + 7: "8️⃣", + 8: "9️⃣", + 9: "🔟", + } + + async def on_submit(self, interaction: discord.Interaction) -> None: + message = await self.var.followup.fetch_message(self.var.message.id) + + # Determine the emoji to use based on the number of options + num = str(message.embeds[0].description).count("\n\n") + message.embeds[0].description += f"\n\n{str(self.reactions[num])} {self.name}" + + await message.edit(embed=message.embeds[0]) + await interaction.response.defer() + + +class Buttons(ui.View): + def __init__(self, *, timeout=180): + super().__init__(timeout=timeout) + + @property + def reactions(self): + return { + 0: "1️⃣", + 1: "2️⃣", + 2: "3️⃣", + 3: "4️⃣", + 4: "5️⃣", + 5: "6️⃣", + 6: "7️⃣", + 7: "8️⃣", + 8: "9️⃣", + 9: "🔟", + } + + @discord.ui.button(label="Add Choice", style=discord.ButtonStyle.gray, emoji="➕") + async def add_choice(self, interaction: discord.Interaction, _button: ui.Button): + # Count the number of options + num = str(interaction.message.embeds[0].description).count("\n\n") + # If there are more than 10 options, return + if num >= 10: + return await interaction.response.send_message( + "You can't make a poll with more than 10 choices", ephemeral=True + ) + + modal = PollModal(var=interaction) + await interaction.response.send_modal(modal) + + @discord.ui.button(label="Remove Choice", style=discord.ButtonStyle.gray, emoji="➖") + async def remove_choice(self, interaction: discord.Interaction, _button: ui.Button): + embed = interaction.message.embeds[0] + + # If there are no options, return + if str(embed.description).count("\n\n") == 0: + return await interaction.response.send_message( + "You can't remove a choice from a poll with no choices", ephemeral=True + ) + + # Remove the last option + embed.description = "\n\n".join(embed.description.split("\n\n")[:-1]) + await interaction.response.edit_message(embed=embed) + + @discord.ui.button(label="Create Poll", style=discord.ButtonStyle.gray, emoji="📝") + async def create_poll(self, interaction: discord.Interaction, _button: ui.Button): + embed = interaction.message.embeds[0] + + # If there are less than 2 options, return + if str(embed.description).count("\n\n") < 2: + return await interaction.response.send_message("You can't create a poll with no choices", ephemeral=True) + + message = await interaction.channel.send(embed=embed) + + # Add reactions + for i in range(0, str(embed.description).count("\n\n")): + await message.add_reaction(self.reactions[i]) + + +class Polls(commands.GroupCog, group_name="poll"): + def __init__(self, bot: commands.AutoShardedBot): + self.__bot = bot + + @property + def reactions(self): + return { + 1: "1️⃣", + 2: "2️⃣", + 3: "3️⃣", + 4: "4️⃣", + 5: "5️⃣", + 6: "6️⃣", + 7: "7️⃣", + 8: "8️⃣", + 9: "9️⃣", + 10: "🔟", + } + + def poll_check(self, message: discord.Message): + try: + embed = message.embeds[0] + except Exception: + return False + if str(embed.footer.text).count("Poll by") == 1: + return message.author == self.__bot.user + return False + + @app_commands.command() + @app_commands.checks.cooldown(1, 10) + async def new(self, interaction: core.InteractionType, desc: str): + """Create a new poll""" + + embed = discord.Embed( + description=f"**{desc}**\n\n", + timestamp=discord.utils.utcnow(), + color=discord.colour.Color.gold(), + ) + embed.set_footer(text=f"Poll by {str(interaction.user.display_name)}") + await interaction.response.send_message(embed=embed, ephemeral=True, view=Buttons()) + + @app_commands.command() + async def show(self, interaction: core.InteractionType, message: str, ephemeral: bool = True): + """Show a poll result""" + try: + *_, channel_id, msg_id = message.split("/") + + try: + channel = self.__bot.get_channel(int(channel_id)) + message = await channel.fetch_message(int(msg_id)) + except Exception: + return await interaction.response.send_message("Please provide the message ID/link for a valid poll") + except Exception: + try: + message = await interaction.channel.fetch_message(int(message)) + except Exception: + return await interaction.response.send_message("Please provide the message ID/link for a valid poll") + + if self.poll_check(message): + poll_embed = message.embeds[0] + reactions = message.reactions + reactions_total = sum( + [reaction.count - 1 if str(reaction.emoji) in self.reactions.values() else 0 for reaction in reactions] + ) + + options = list( + map( + lambda o: " ".join(o.split()[1:]), + poll_embed.description.split("1️")[1].split("\n\n"), + ) + ) + desc = poll_embed.description.split("1️")[0] + + embed = discord.Embed( + description=desc, + timestamp=poll_embed.timestamp, + color=discord.Color.gold(), + ) + + for i, option in enumerate(options): + reaction_count = reactions[i].count - 1 + indicator = "░" * 20 + if reactions_total != 0: + indicator = "█" * int(((reaction_count / reactions_total) * 100) / 5) + "░" * int( + (((reactions_total - reaction_count) / reactions_total) * 100) / 5 + ) + + embed.add_field( + name=option, + value=f"{indicator} {int((reaction_count / (reactions_total or 1)*100))}%" + f" (**{reaction_count} votes**)", + inline=False, + ) + + embed.set_footer(text="Poll Result") + return await interaction.response.send_message(embed=embed, ephemeral=ephemeral) + + return await interaction.response.send_message("Please provide the message ID/link for a valid poll") + + +async def setup(bot: commands.Bot): + await bot.add_cog(Polls(bot=bot)) diff --git a/bot/extensions/poll/events.py b/bot/extensions/poll/events.py new file mode 100644 index 00000000..9ecc98c0 --- /dev/null +++ b/bot/extensions/poll/events.py @@ -0,0 +1,34 @@ +import discord +from discord.ext import commands + +from bot import core + + +class PollsEvents(commands.Cog): + """Events for polls in discord.""" + + def __init__(self, bot: core.DiscordBot): + self.bot = bot + + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): + channel: discord.TextChannel = self.bot.get_channel(payload.channel_id) + message: discord.Message = await channel.fetch_message(payload.message_id) + + if payload.user_id == self.bot.user.id: + return + + if not self.poll_check(message): + return + + emojis = list(self.reactions.values()) + if str(payload.emoji) not in emojis: + return + + for reaction in message.reactions: + if str(reaction) not in emojis: + return + + if str(reaction.emoji) != str(payload.emoji): + user = self.bot.get_user(payload.user_id) + await message.remove_reaction(reaction.emoji, user) diff --git a/cli.py b/cli.py index f90ae121..6878c734 100644 --- a/cli.py +++ b/cli.py @@ -137,10 +137,10 @@ async def main(ctx): "bot.extensions.tags", "bot.extensions.levelling", "bot.extensions.persistent_roles", + "bot.extensions.poll", "bot.cogs._help", "bot.cogs.clashofcode", "bot.cogs.roles", - "bot.cogs.poll", ) intents = discord.Intents.all()