diff options
Diffstat (limited to 'bot/seasons/evergreen')
| -rw-r--r-- | bot/seasons/evergreen/__init__.py | 6 | ||||
| -rw-r--r-- | bot/seasons/evergreen/error_handler.py | 2 | ||||
| -rw-r--r-- | bot/seasons/evergreen/fun.py | 2 | ||||
| -rw-r--r-- | bot/seasons/evergreen/issues.py | 41 | ||||
| -rw-r--r-- | bot/seasons/evergreen/magic_8ball.py | 7 | ||||
| -rw-r--r-- | bot/seasons/evergreen/showprojects.py | 33 | ||||
| -rw-r--r-- | bot/seasons/evergreen/snakes/__init__.py | 1 | ||||
| -rw-r--r-- | bot/seasons/evergreen/snakes/converter.py | 5 | ||||
| -rw-r--r-- | bot/seasons/evergreen/snakes/snakes_cog.py | 53 | ||||
| -rw-r--r-- | bot/seasons/evergreen/snakes/utils.py | 36 | ||||
| -rw-r--r-- | bot/seasons/evergreen/uptime.py | 2 |
11 files changed, 99 insertions, 89 deletions
diff --git a/bot/seasons/evergreen/__init__.py b/bot/seasons/evergreen/__init__.py index ac32c199..b95f3528 100644 --- a/bot/seasons/evergreen/__init__.py +++ b/bot/seasons/evergreen/__init__.py @@ -5,3 +5,9 @@ class Evergreen(SeasonBase): """Evergreen Seasonal event attributes.""" bot_icon = "/logos/logo_seasonal/evergreen/logo_evergreen.png" + icon = ( + "/logos/logo_animated/heartbeat/heartbeat.gif", + "/logos/logo_animated/spinner/spinner.gif", + "/logos/logo_animated/tongues/tongues.gif", + "/logos/logo_animated/winky/winky.gif", + ) diff --git a/bot/seasons/evergreen/error_handler.py b/bot/seasons/evergreen/error_handler.py index 26afe814..f4457f8f 100644 --- a/bot/seasons/evergreen/error_handler.py +++ b/bot/seasons/evergreen/error_handler.py @@ -27,7 +27,6 @@ class CommandErrorHandler(commands.Cog): @commands.Cog.listener()
async def on_command_error(self, ctx, error):
"""Activates when a command opens an error."""
-
if hasattr(ctx.command, 'on_error'):
return logging.debug(
"A command error occured but the command had it's own error handler."
@@ -101,6 +100,5 @@ class CommandErrorHandler(commands.Cog): def setup(bot):
"""Error handler Cog load."""
-
bot.add_cog(CommandErrorHandler(bot))
log.info("CommandErrorHandler cog loaded")
diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 05cf504e..ce3484f7 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -17,7 +17,6 @@ class Fun(commands.Cog): @commands.command() async def roll(self, ctx, num_rolls: int = 1): """Outputs a number of random dice emotes (up to 6).""" - output = "" if num_rolls > 6: num_rolls = 6 @@ -31,6 +30,5 @@ class Fun(commands.Cog): def setup(bot): """Fun Cog load.""" - bot.add_cog(Fun(bot)) log.info("Fun cog loaded") diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py new file mode 100644 index 00000000..840d9ead --- /dev/null +++ b/bot/seasons/evergreen/issues.py @@ -0,0 +1,41 @@ +import logging + +import discord +from discord.ext import commands + +log = logging.getLogger(__name__) + + +class Issues(commands.Cog): + """Cog that allows users to retrieve issues from GitHub.""" + + def __init__(self, bot): + self.bot = bot + + @commands.command(aliases=("issues",)) + async def issue(self, ctx, number: int, repository: str = "seasonalbot", user: str = "python-discord"): + """Command to retrieve issues from a GitHub repository.""" + url = f"https://api.github.com/repos/{user}/{repository}/issues/{str(number)}" + status = {"404": f"Issue #{str(number)} doesn't exist in the repository {user}/{repository}.", + "403": f"Rate limit exceeded. Please wait a while before trying again!"} + + async with self.bot.http_session.get(url) as r: + json_data = await r.json() + + if str(r.status) in status: + return await ctx.send(status.get(str(r.status))) + + valid = discord.Embed(colour=0x00ff37) + valid.add_field(name="Repository", value=f"{user}/{repository}", inline=False) + valid.add_field(name="Issue Number", value=f"#{str(number)}", inline=False) + valid.add_field(name="Status", value=json_data.get("state").title()) + valid.add_field(name="Link", value=url, inline=False) + if len(json_data.get("body")) < 1024: + valid.add_field(name="Description", value=json_data.get("body"), inline=False) + await ctx.send(embed=valid) + + +def setup(bot): + """Github Issues Cog Load.""" + bot.add_cog(Issues(bot)) + log.info("Issues cog loaded") diff --git a/bot/seasons/evergreen/magic_8ball.py b/bot/seasons/evergreen/magic_8ball.py index 0b4eeb62..55652af7 100644 --- a/bot/seasons/evergreen/magic_8ball.py +++ b/bot/seasons/evergreen/magic_8ball.py @@ -13,12 +13,12 @@ class Magic8ball(commands.Cog): def __init__(self, bot): self.bot = bot - with open(Path("bot", "resources", "evergreen", "magic8ball.json"), "r") as file: + with open(Path("bot/resources/evergreen/magic8ball.json"), "r") as file: self.answers = json.load(file) @commands.command(name="8ball") async def output_answer(self, ctx, *, question): - """Return a magic 8 ball answer from answers list.""" + """Return a Magic 8ball answer from answers list.""" if len(question.split()) >= 3: answer = random.choice(self.answers) await ctx.send(answer) @@ -27,7 +27,6 @@ class Magic8ball(commands.Cog): def setup(bot): - """Magic 8ball cog load.""" - + """Magic 8ball Cog load.""" bot.add_cog(Magic8ball(bot)) log.info("Magic8ball cog loaded") diff --git a/bot/seasons/evergreen/showprojects.py b/bot/seasons/evergreen/showprojects.py new file mode 100644 index 00000000..37809b33 --- /dev/null +++ b/bot/seasons/evergreen/showprojects.py @@ -0,0 +1,33 @@ +import logging + +from discord.ext import commands + +from bot.constants import Channels + +log = logging.getLogger(__name__) + + +class ShowProjects(commands.Cog): + """Cog that reacts to posts in the #show-your-projects""" + + def __init__(self, bot): + self.bot = bot + self.lastPoster = 0 # Given 0 as the default last poster ID as no user can actually have 0 assigned to them + + @commands.Cog.listener() + async def on_message(self, message): + """Adds reactions to posts in #show-your-projects""" + reactions = ["\U0001f44d", "\U00002764", "\U0001f440", "\U0001f389", "\U0001f680", "\U00002b50", "\U0001f6a9"] + if (message.channel.id == Channels.show_your_projects + and message.author.bot is False + and message.author.id != self.lastPoster): + for reaction in reactions: + await message.add_reaction(reaction) + + self.lastPoster = message.author.id + + +def setup(bot): + """Show Projects Reaction Cog""" + bot.add_cog(ShowProjects(bot)) + log.info("ShowProjects cog loaded") diff --git a/bot/seasons/evergreen/snakes/__init__.py b/bot/seasons/evergreen/snakes/__init__.py index 5188200e..d0e57dae 100644 --- a/bot/seasons/evergreen/snakes/__init__.py +++ b/bot/seasons/evergreen/snakes/__init__.py @@ -7,6 +7,5 @@ log = logging.getLogger(__name__) def setup(bot): """Snakes Cog load.""" - bot.add_cog(Snakes(bot)) log.info("Snakes cog loaded") diff --git a/bot/seasons/evergreen/snakes/converter.py b/bot/seasons/evergreen/snakes/converter.py index ec9c9870..f2637530 100644 --- a/bot/seasons/evergreen/snakes/converter.py +++ b/bot/seasons/evergreen/snakes/converter.py @@ -20,7 +20,6 @@ class Snake(Converter): async def convert(self, ctx, name): """Convert the input snake name to the closest matching Snake object.""" - await self.build_list() name = name.lower() @@ -61,7 +60,6 @@ class Snake(Converter): @classmethod async def build_list(cls): """Build list of snakes from the static snake resources.""" - # Get all the snakes if cls.snakes is None: with (SNAKE_RESOURCES / "snake_names.json").open() as snakefile: @@ -80,10 +78,7 @@ class Snake(Converter): This is stupid. We should find a way to somehow get the global session into a global context, so I can get it from here. - - :return: """ - await cls.build_list() names = [snake['scientific'] for snake in cls.snakes] return random.choice(names) diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py index 3ffdf1bf..1d138aff 100644 --- a/bot/seasons/evergreen/snakes/snakes_cog.py +++ b/bot/seasons/evergreen/snakes/snakes_cog.py @@ -156,7 +156,6 @@ class Snakes(Cog): @staticmethod def _beautiful_pastel(hue): """Returns random bright pastels.""" - light = random.uniform(0.7, 0.85) saturation = 1 @@ -176,7 +175,6 @@ class Snakes(Cog): Written by juan and Someone during the first code jam. """ - snake = Image.open(buffer) # Get the size of the snake icon, configure the height of the image box (yes, it changes) @@ -254,7 +252,6 @@ class Snakes(Cog): @staticmethod def _snakify(message): """Sssnakifffiesss a sstring.""" - # Replace fricatives with exaggerated snake fricatives. simple_fricatives = [ "f", "s", "z", "h", @@ -277,7 +274,6 @@ class Snakes(Cog): async def _fetch(self, session, url, params=None): """Asynchronous web request helper method.""" - if params is None: params = {} @@ -291,7 +287,6 @@ class Snakes(Cog): Else, just return whatever the last message is. """ - long_message = random.choice(messages) if len(long_message.split()) < 3 and retries > 0: return self._get_random_long_message( @@ -312,7 +307,6 @@ class Snakes(Cog): :param name: The name of the snake to get information for - omit for a random snake :return: A dict containing information on a snake """ - snake_info = {} async with aiohttp.ClientSession() as session: @@ -327,7 +321,7 @@ class Snakes(Cog): json = await self._fetch(session, URL, params=params) - # wikipedia does have a error page + # Wikipedia does have a error page try: pageid = json["query"]["search"][0]["pageid"] except KeyError: @@ -348,7 +342,7 @@ class Snakes(Cog): json = await self._fetch(session, URL, params=params) - # constructing dict - handle exceptions later + # Constructing dict - handle exceptions later try: snake_info["title"] = json["query"]["pages"][f"{pageid}"]["title"] snake_info["extract"] = json["query"]["pages"][f"{pageid}"]["extract"] @@ -380,7 +374,7 @@ class Snakes(Cog): ] for image in snake_info["images"]: - # images come in the format of `File:filename.extension` + # Images come in the format of `File:filename.extension` file, sep, filename = image["title"].partition(':') filename = filename.replace(" ", "%20") # Wikipedia returns good data! @@ -417,15 +411,9 @@ class Snakes(Cog): return random.choice(self.snake_names) async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list): - """ - Validate the answer using a reaction event loop. - - :return: - """ - + """Validate the answer using a reaction event loop.""" def predicate(reaction, user): """Test if the the answer is valid and can be evaluated.""" - return ( reaction.message.id == message.id # The reaction is attached to the question we asked. and user == ctx.author # It's the user who triggered the quiz. @@ -457,8 +445,7 @@ class Snakes(Cog): @group(name='snakes', aliases=('snake',), invoke_without_command=True) async def snakes_group(self, ctx: Context): """Commands from our first code jam.""" - - await ctx.invoke(self.bot.get_command("help"), "snake") + await ctx.send_help(ctx.command) @bot_has_permissions(manage_messages=True) @snakes_group.command(name='antidote') @@ -478,10 +465,8 @@ class Snakes(Cog): This game was created by Lord Bisk and Runew0lf. """ - def predicate(reaction_: Reaction, user_: Member): """Make sure that this reaction is what we want to operate on.""" - return ( all(( # Reaction is on this message @@ -613,7 +598,6 @@ class Snakes(Cog): Written by Momo and kel. Modified by juan and lemon. """ - with ctx.typing(): # Generate random snake attributes @@ -640,8 +624,8 @@ class Snakes(Cog): text_color=text_color, bg_color=bg_color ) - png_bytes = utils.frame_to_png_bytes(image_frame) - file = File(png_bytes, filename='snek.png') + png_bytesIO = utils.frame_to_png_bytes(image_frame) + file = File(png_bytesIO, filename='snek.png') await ctx.send(file=file) @snakes_group.command(name='get') @@ -657,7 +641,6 @@ class Snakes(Cog): Created by Ava and eivl. """ - with ctx.typing(): if name is None: name = await Snake.random() @@ -707,7 +690,6 @@ class Snakes(Cog): Made by Ava and eivl. Modified by lemon. """ - with ctx.typing(): image = None @@ -741,7 +723,6 @@ class Snakes(Cog): Written by Momo and kel. """ - # Pick a random snake to hatch. snake_name = random.choice(list(utils.snakes.keys())) snake_image = utils.snakes[snake_name] @@ -774,7 +755,6 @@ class Snakes(Cog): Written by Samuel. Modified by gdude. """ - url = "http://www.omdbapi.com/" page = random.randint(1, 27) @@ -845,7 +825,6 @@ class Snakes(Cog): This was created by Mushy and Cardium, and modified by Urthas and lemon. """ - # Prepare a question. question = random.choice(self.snake_quizzes) answer = question["answerkey"] @@ -886,7 +865,6 @@ class Snakes(Cog): This was written by Iceman, and modified for inclusion into the bot by lemon. """ - snake_name = await self._get_snake_name() snake_name = snake_name['name'] snake_prefix = "" @@ -944,8 +922,7 @@ class Snakes(Cog): Written by Momo and kel. Modified by lemon. """ - - # check if there is already a game in this channel + # Check if there is already a game in this channel if ctx.channel in self.active_sal: await ctx.send(f"{ctx.author.mention} A game is already in progress in this channel.") return @@ -958,7 +935,6 @@ class Snakes(Cog): @snakes_group.command(name='about') async def about_command(self, ctx: Context): """Show an embed with information about the event, its participants, and its winners.""" - contributors = [ "<@!245270749919576066>", "<@!396290259907903491>", @@ -1006,7 +982,6 @@ class Snakes(Cog): Created by juan and Someone during the first code jam. """ - # Get the snake data we need if not name: name_obj = await self._get_snake_name() @@ -1046,7 +1021,6 @@ class Snakes(Cog): Written by Andrew and Prithaj. Modified by lemon. """ - question = random.choice(self.snake_facts)["fact"] embed = Embed( title="Snake fact", @@ -1055,13 +1029,6 @@ class Snakes(Cog): ) await ctx.channel.send(embed=embed) - @snakes_group.command(name='help') - async def help_command(self, ctx: Context): - """Invokes the help command for the Snakes Cog.""" - - log.debug(f"{ctx.author} requested info about the snakes cog") - return await ctx.invoke(self.bot.get_command("help"), "Snakes") - @snakes_group.command(name='snakify') async def snakify_command(self, ctx: Context, *, message: str = None): """ @@ -1075,7 +1042,6 @@ class Snakes(Cog): Written by Momo and kel. Modified by lemon. """ - with ctx.typing(): embed = Embed() user = ctx.message.author @@ -1116,7 +1082,6 @@ class Snakes(Cog): Written by Andrew and Prithaj. """ - # Are we searching for anything specific? if search: query = search + ' snake' @@ -1156,7 +1121,6 @@ class Snakes(Cog): Written by Prithaj and Andrew. Modified by lemon. """ - embed = Embed( title="Zzzen of Pythhon", color=SNAKE_COLOR @@ -1179,7 +1143,6 @@ class Snakes(Cog): @video_command.error async def command_error(self, ctx, error): """Local error handler for the Snake Cog.""" - embed = Embed() embed.colour = Colour.red() diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index e2ed60bd..88fb2032 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -8,13 +8,12 @@ from itertools import product from pathlib import Path from typing import List, Tuple -import aiohttp from PIL import Image from PIL.ImageDraw import ImageDraw from discord import File, Member, Reaction from discord.ext.commands import Context -SNAKE_RESOURCES = Path('bot', 'resources', 'snakes').absolute() +SNAKE_RESOURCES = Path("bot/resources/snakes").absolute() h1 = r'''``` ---- @@ -113,20 +112,17 @@ ANGLE_RANGE = math.pi * 2 def get_resource(file: str) -> List[dict]: """Load Snake resources JSON.""" - with (SNAKE_RESOURCES / f"{file}.json").open(encoding="utf-8") as snakefile: return json.load(snakefile) def smoothstep(t): """Smooth curve with a zero derivative at 0 and 1, making it useful for interpolating.""" - return t * t * (3. - 2. * t) def lerp(t, a, b): """Linear interpolation between a and b, given a fraction t.""" - return a + t * (b - a) @@ -159,7 +155,6 @@ class PerlinNoiseFactory(object): If ``unbias`` is true, the smoothstep function will be applied to the output before returning it, to counteract some of Perlin noise's significant bias towards the center of its output range. """ - self.dimension = dimension self.octaves = octaves self.tile = tile + (0,) * dimension @@ -177,7 +172,6 @@ class PerlinNoiseFactory(object): This is the "gradient" vector, in that the grid tile slopes towards it """ - # 1 dimension is special, since the only unit vector is trivial; # instead, use a slope between -1 and 1 if self.dimension == 1: @@ -194,7 +188,6 @@ class PerlinNoiseFactory(object): def get_plain_noise(self, *point): """Get plain noise for a single point, without taking into account either octaves or tiling.""" - if len(point) != self.dimension: raise ValueError("Expected {0} values, got {1}".format( self.dimension, len(point))) @@ -247,7 +240,6 @@ class PerlinNoiseFactory(object): The number of values given should match the number of dimensions. """ - ret = 0 for o in range(self.octaves): o2 = 1 << o @@ -308,7 +300,6 @@ def create_snek_frame( :param text_color: the color of the text. :return: a PIL image, representing a single frame. """ - start_x = random.randint(image_margins[X], image_dimensions[X] - image_margins[X]) start_y = random.randint(image_margins[Y], image_dimensions[Y] - image_margins[Y]) points = [(start_x, start_y)] @@ -361,12 +352,12 @@ def create_snek_frame( return image -def frame_to_png_bytes(image: Image): +def frame_to_png_bytes(image: Image) -> io.BytesIO: """Convert image to byte stream.""" - stream = io.BytesIO() image.save(stream, format='PNG') - return stream.getvalue() + stream.seek(0) + return stream log = logging.getLogger(__name__) @@ -410,10 +401,8 @@ class SnakeAndLaddersGame: Listen for reactions until players have joined, and the game has been started. """ - def startup_event_check(reaction_: Reaction, user_: Member): """Make sure that this reaction is what we want to operate on.""" - return ( all(( reaction_.message.id == startup.id, # Reaction is on startup message @@ -480,12 +469,10 @@ class SnakeAndLaddersGame: async def _add_player(self, user: Member): self.players.append(user) self.player_tiles[user.id] = 1 - avatar_url = user.avatar_url_as(format='jpeg', size=PLAYER_ICON_IMAGE_SIZE) - async with aiohttp.ClientSession() as session: - async with session.get(avatar_url) as res: - avatar_bytes = await res.read() - im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE)) - self.avatar_images[user.id] = im + + avatar_bytes = await user.avatar_url_as(format='jpeg', size=PLAYER_ICON_IMAGE_SIZE).read() + im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE)) + self.avatar_images[user.id] = im async def player_join(self, user: Member): """ @@ -494,7 +481,6 @@ class SnakeAndLaddersGame: Prevent player joining if they have already joined, if the game is full, or if the game is in a waiting state. """ - for p in self.players: if user == p: await self.channel.send(user.mention + " You are already in the game.", delete_after=10) @@ -521,7 +507,6 @@ class SnakeAndLaddersGame: Leaving is prevented if the user initiated the game or if they weren't part of it in the first place. """ - if user == self.author: await self.channel.send( user.mention + " You are the author, and cannot leave the game. Execute " @@ -547,7 +532,6 @@ class SnakeAndLaddersGame: async def cancel_game(self, user: Member): """Allow the game author to cancel the running game.""" - if not user == self.author: await self.channel.send(user.mention + " Only the author of the game can cancel it.", delete_after=10) return @@ -561,7 +545,6 @@ class SnakeAndLaddersGame: The game cannot be started if there aren't enough players joined or if the game is in a waiting state. """ - if not user == self.author: await self.channel.send(user.mention + " Only the author of the game can start it.", delete_after=10) return @@ -581,10 +564,8 @@ class SnakeAndLaddersGame: async def start_round(self): """Begin the round.""" - def game_event_check(reaction_: Reaction, user_: Member): """Make sure that this reaction is what we want to operate on.""" - return ( all(( reaction_.message.id == self.positions.id, # Reaction is on positions message @@ -676,7 +657,6 @@ class SnakeAndLaddersGame: async def player_roll(self, user: Member): """Handle the player's roll.""" - if user.id not in self.player_tiles: await self.channel.send(user.mention + " You are not in the match.", delete_after=10) return diff --git a/bot/seasons/evergreen/uptime.py b/bot/seasons/evergreen/uptime.py index 32c2b59d..92066e0a 100644 --- a/bot/seasons/evergreen/uptime.py +++ b/bot/seasons/evergreen/uptime.py @@ -18,7 +18,6 @@ class Uptime(commands.Cog): @commands.command(name="uptime") async def uptime(self, ctx): """Responds with the uptime of the bot.""" - difference = relativedelta(start_time - arrow.utcnow()) uptime_string = start_time.shift( seconds=-difference.seconds, @@ -31,6 +30,5 @@ class Uptime(commands.Cog): def setup(bot): """Uptime Cog load.""" - bot.add_cog(Uptime(bot)) log.info("Uptime cog loaded") |