aboutsummaryrefslogtreecommitdiffstats
path: root/bot/seasons/evergreen
diff options
context:
space:
mode:
Diffstat (limited to 'bot/seasons/evergreen')
-rw-r--r--bot/seasons/evergreen/__init__.py6
-rw-r--r--bot/seasons/evergreen/error_handler.py2
-rw-r--r--bot/seasons/evergreen/fun.py2
-rw-r--r--bot/seasons/evergreen/issues.py41
-rw-r--r--bot/seasons/evergreen/magic_8ball.py7
-rw-r--r--bot/seasons/evergreen/showprojects.py33
-rw-r--r--bot/seasons/evergreen/snakes/__init__.py1
-rw-r--r--bot/seasons/evergreen/snakes/converter.py5
-rw-r--r--bot/seasons/evergreen/snakes/snakes_cog.py53
-rw-r--r--bot/seasons/evergreen/snakes/utils.py36
-rw-r--r--bot/seasons/evergreen/uptime.py2
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")