From 30b65f1b9aa51b2258dc6ee6b8cb0583a5fad16c Mon Sep 17 00:00:00 2001 From: Matthew Flegg Date: Thu, 28 Apr 2022 19:31:59 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=9F=20added=20polls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .setup.toml | 3 +- ext/commands/misc.py | 48 ++++++++++++++++++++++++++-- ext/handlers/__init__.py | 4 +++ ext/handlers/events.py | 67 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 ext/handlers/__init__.py create mode 100644 ext/handlers/events.py diff --git a/.setup.toml b/.setup.toml index e934072..664a28d 100644 --- a/.setup.toml +++ b/.setup.toml @@ -11,5 +11,6 @@ status = "/" extensions = [ "ext.commands.mod", "ext.commands.info", - "ext.commands.misc" + "ext.commands.misc", + "ext.handlers.events" ] \ No newline at end of file diff --git a/ext/commands/misc.py b/ext/commands/misc.py index 73a969e..90bd2cb 100644 --- a/ext/commands/misc.py +++ b/ext/commands/misc.py @@ -1,3 +1,7 @@ +""" +Module `misc` contains the `misc` cog/group, which implements +information commands for BB.Bot. +""" import datetime import discord import humanize @@ -13,7 +17,6 @@ class Misc(commands.Cog, name="Miscellaneous"): """ 🎲 Contains miscellaneous commands. """ - def __init__(self, client: core.DiscordClient): self.client = client super().__init__() @@ -25,13 +28,14 @@ async def twitch(self, interaction: discord.Interaction, *, broadcaster: str) -> 🎲 Shows information about a Twitch stream. """ streams = await self.client.twitch_client.fetch_streams(user_logins=[broadcaster]) - stream: twitchio.Stream = streams[0] or None - if not stream: + if not streams: error_embed = utils.create_error_embed(f"The streamer **`{broadcaster}`** is not currently live.") return await interaction.response.send_message(embed=error_embed) + stream: twitchio.Stream = streams[0] current_time = datetime.datetime.utcnow() + stream_time = humanize.precisedelta( current_time-stream.started_at.replace(tzinfo=None), format="%0.0f" @@ -51,6 +55,44 @@ async def twitch(self, interaction: discord.Interaction, *, broadcaster: str) -> .add_field(name="❓ Category", value=stream.game_name, inline=False) await interaction.response.send_message(embed=stream_embed) + + @app_commands.command() + async def meme(self, interaction: discord.Interaction): + """ + 🎲 Sends a random meme from Reddit. + """ + response = await self.client.session.get("https://meme-api.herokuapp.com/gimme") + data = await response.json() + + meme_embed = discord.Embed( + title="🎲 Found a Meme", + description=f"**`{data['title']}`**", + timestamp=datetime.datetime.utcnow(), + color=self.client.theme, + ) \ + .set_image(url=f"{data['url']}") \ + .set_footer(text="❓ Try again? Use /meme.") + + await interaction.response.send_message(embed=meme_embed) + + @app_commands.command() + @app_commands.describe(question="❓ The yes/no question to ask for the poll.") + async def poll(self, interaction: discord.Interaction, *, question: str): + """ + 🎲 Creates a simple yes or no poll for users to vote on. + """ + poll_embed = discord.Embed( + title="🎲 Poll", + description=f"**`{question}`**", + timestamp=datetime.datetime.utcnow(), + color=self.client.theme, + ) \ + .set_author(name=interaction.user.name, url=interaction.user.avatar.url) \ + .set_footer(text="Vote ✔️ Yes or ❌ No.") + + message = await interaction.channel.send(embed=poll_embed) + await message.add_reaction("✔️") + await message.add_reaction("❌") diff --git a/ext/handlers/__init__.py b/ext/handlers/__init__.py new file mode 100644 index 0000000..fbef232 --- /dev/null +++ b/ext/handlers/__init__.py @@ -0,0 +1,4 @@ +""" +Module `commands` contains discord.py extensions that +implement event, task, and error handlers. +""" \ No newline at end of file diff --git a/ext/handlers/events.py b/ext/handlers/events.py new file mode 100644 index 0000000..812003f --- /dev/null +++ b/ext/handlers/events.py @@ -0,0 +1,67 @@ +""" +Module `events` contains the `EventHandler` extension, +which is used to respond to discord events. +""" +import discord +import asyncio +import core + +from discord.ext import commands + + +class EventHandler(commands.Cog): + """ + Class `EventHandler` is responsible for executing code + in response to various client-side events. + """ + def __init__(self, client: core.DiscordClient): + self.client = client + super().__init__() + + @commands.Cog.listener() + async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User) -> None: + """ + This event is triggered when users react to a message + that the bot has sent. + + This method is responsible for ensuring that users can + only vote once on a poll. + + Params: + - reaction (discord.Reaction): The reaction that was added. + - user (discord.User): The user who added the reaction. + """ + if user.bot: + return + + message: discord.Message = discord.utils.find( + lambda x: x == reaction.message, self.client.cached_messages + ) + + for existing_reaction in message.reactions: + users = {user async for user in existing_reaction.users()} + + if user in users and str(existing_reaction) != str(reaction): + await message.remove_reaction(existing_reaction.emoji, user) + + +async def setup(client: core.DiscordClient) -> None: + """ + Registers the command group/cog with the discord client. + All extensions must have a setup function. + + Params: + - client: (DiscordClient): The client to register the cog with. + """ + await client.add_cog(EventHandler(client)) + + +async def teardown(client: core.DiscordClient) -> None: + """ + De-registers the command group/cog with the discord client. + This is not usually needed, but is useful to have. + + Params: + - client: (DiscordClient): The client to de-register the cog with. + """ + await client.remove_cog(EventHandler(client)) \ No newline at end of file