From 0d0833849abcfe56fc0582cb1cf168e93cd4a588 Mon Sep 17 00:00:00 2001 From: Rohan Date: Tue, 9 Apr 2019 21:40:30 +0530 Subject: done with the easter egg fact cmd and background task --- bot/resources/easter/easter_egg_facts.json | 22 ++++++++++++++ bot/seasons/easter/egg_facts.py | 46 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 bot/resources/easter/easter_egg_facts.json create mode 100644 bot/seasons/easter/egg_facts.py (limited to 'bot') diff --git a/bot/resources/easter/easter_egg_facts.json b/bot/resources/easter/easter_egg_facts.json new file mode 100644 index 00000000..c8f72c8d --- /dev/null +++ b/bot/resources/easter/easter_egg_facts.json @@ -0,0 +1,22 @@ +{ + "facts": [ + "The first story of a rabbit (later named the \"Easter Bunny\") hiding eggs in a garden was published in 1680.", + "Rabbits are known to be prolific pro creators and are an ancient symbol of fertility and new life. The German immigrants brought the tale of Easter Bunny in the 1700s with the tradition of an egg-laying hare called ‘Osterhase\". The kids then would make nests in which the creature would lay coloured eggs. The tradition has been revolutionized in the form of candies and gifts instead of eggs.", + "In earlier days, a festival of egg throwing was held in church, when the priest would throw a hard-boiled egg to one of the choirboys. It was then tossed from one choirboy to the next and whoever held the egg when the clock struck 12 on Easter, was the winner and could keep it.", + "In medieval times, Easter eggs were boiled with onions to give them a golden sheen. Edward I went beyond this tradition in 1290 and ordered 450 eggs to be covered in gold leaf and given as Easter gifts.", + "Decorating Easter eggs is an ancient tradition that dates back to 13th century. One of the explanations for this custom is that eggs were considered as a forbidden food during the Lenten season (40 days before Easter). Therefore, people would paint and decorate them to mark an end of the period of penance and fasting and later eat them on Easter. The tradition of decorating eggs is called Pysanka which is creating a traditional Ukrainian folk design using wax-resist method.", + "Members of the Greek Orthodox faith often paint their Easter eggs red, which symbolizes Jesus\" blood and his victory over death. The color red, symbolizes renewal of life, such as, Jesus\" resurrection.", + "Eggs rolling take place in many parts of the world which symbolizes stone which was rolled away from the tomb where Jesus\" body was laid after his death.", + "Easter eggs have been considered as a symbol of fertility, rebirth and new life. The custom of giving eggs has been derived from Egyptians, Persians, Gauls, Greeks and Romans.", + "The first chocolate Easter egg was made by Fry\"s in 1873. Before this, people would give hollow cardboard eggs, filled with gifts.", + "The tallest chocolate Easter egg was made in Italy in 2011. Standing 10.39 metres tall and weighing 7,200 kg, it was taller than a giraffe and heavier than an elephant.", + "The largest ever Easter egg hunt was in Florida, where 9,753 children searched for 501,000 eggs.", + "In 2007, an Easter egg covered in diamonds sold for almost £9 million. Every hour, a cockerel made of jewels pops up from the top of the Faberge egg, flaps its wings four times, nods its head three times and makes a crowing noise. The gold-and-pink enamel egg was made by the Russian royal family as an engagement gift for French aristocrat Baron Edouard de Rothschild.", + "The White House held their first official egg roll in 1878 when Rutherford B. Hayes was the President. It is a race in which children push decorated, hard-boiled eggs across the White House lawn as an annual event held the Monday after Easter. In 2009, the Obamas hosted their first Easter egg roll with the theme, ‘Let\"s go play\" which was meant to encourage young people to lead healthy and active lives.", + "80 million chocolate Easter eggs are sold each year. This accounts for 10% of Britain\"s annual spending on chocolate!", + "The tradition of giving eggs at Easter has been traced back to Egyptians, Persians, Gauls, Greeks and Romans, who saw the egg as a symbol of life.", + "Medieval Easter eggs were boiled with onions to give them a golden sheen. Edward I, however, went one better and in 1290 he ordered 450 eggs to be covered in gold leaf and given as Easter gifts.", + "The first chocolate Easter egg was produced in 1873 by Fry\"s. Before this, people would give hollow cardboard eggs, filled with gifts.", + "John Cadbury soon followed suit and made his first Cadbury Easter egg in 1875. By 1892 the company was producing 19 different lines, all made from dark chocolate." + ] +} \ No newline at end of file diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py new file mode 100644 index 00000000..1080f6c6 --- /dev/null +++ b/bot/seasons/easter/egg_facts.py @@ -0,0 +1,46 @@ +from discord.ext import commands +import discord +import asyncio +from pathlib import Path +from json import load +import random +from bot.constants import Colours + + +class EasterFacts(commands.Cog): + + def __init__(self, bot): + self.bot = bot + self.facts = self.load_json() + + @staticmethod + def load_json(): + p = Path('bot', 'resources', 'easter', 'easter_egg_facts.json') + with p.open(encoding="utf8") as f: + facts = load(f) + return facts + + async def background(self): + channel = self.bot.get_channel(426566445124812815) + while True: + embed = self.make_embed() + await channel.send(embed=embed) + await asyncio.sleep(24*60*60) + + @commands.command(name='eggfact', aliases=['fact']) + async def easter_facts(self, ctx): + embed = self.make_embed() + await ctx.send(embed=embed) + + def make_embed(self): + embed = discord.Embed() + embed.colour = Colours.soft_red + embed.title = 'Easter Egg Fact' + random_fact = random.choice(self.facts['facts']) + embed.description = random_fact + return embed + + +def setup(bot): + bot.loop.create_task(EasterFacts(bot).background()) + bot.add_cog(EasterFacts(bot)) -- cgit v1.2.3 From 592cd2826b865e8abe1bea15fb48fd736a7c3f53 Mon Sep 17 00:00:00 2001 From: Rohan Date: Tue, 9 Apr 2019 22:17:14 +0530 Subject: fixed lint errors --- bot/seasons/easter/egg_facts.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 1080f6c6..dcc15078 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -1,26 +1,30 @@ -from discord.ext import commands -import discord import asyncio -from pathlib import Path -from json import load import random +from json import load +from pathlib import Path + +import discord +from discord.ext import commands + from bot.constants import Colours class EasterFacts(commands.Cog): - + """A cog for easter egg facts.""" def __init__(self, bot): self.bot = bot self.facts = self.load_json() @staticmethod def load_json(): + """Loading the json data""" p = Path('bot', 'resources', 'easter', 'easter_egg_facts.json') with p.open(encoding="utf8") as f: facts = load(f) return facts async def background(self): + """A background task that sends a easter egg fact.""" channel = self.bot.get_channel(426566445124812815) while True: embed = self.make_embed() @@ -29,10 +33,12 @@ class EasterFacts(commands.Cog): @commands.command(name='eggfact', aliases=['fact']) async def easter_facts(self, ctx): + """Get easter egg facts.""" embed = self.make_embed() await ctx.send(embed=embed) def make_embed(self): + """Makes a nice embed for the message to be sent.""" embed = discord.Embed() embed.colour = Colours.soft_red embed.title = 'Easter Egg Fact' @@ -42,5 +48,6 @@ class EasterFacts(commands.Cog): def setup(bot): + """Loading the cog""" bot.loop.create_task(EasterFacts(bot).background()) bot.add_cog(EasterFacts(bot)) -- cgit v1.2.3 From fede9d040921392d8d02d1c210c3d1e12750710f Mon Sep 17 00:00:00 2001 From: Owez Date: Wed, 17 Apr 2019 15:33:36 +0100 Subject: Update egghead_questions.json - Fixed question "Who is considered to be the founder of Earth Day?" - Fixed question "When was the first Earth Day?" --- bot/resources/easter/egghead_questions.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/resources/easter/egghead_questions.json b/bot/resources/easter/egghead_questions.json index e4e21ebe..38df1615 100644 --- a/bot/resources/easter/egghead_questions.json +++ b/bot/resources/easter/egghead_questions.json @@ -47,7 +47,7 @@ "1999", "1970" ], - "correct_answer": 2 + "correct_answer": 3 }, { "question": "Who is considered to be the founder of Earth Day?", @@ -55,7 +55,7 @@ "President Jimmy Carter", "President John F. Kennedy", "Vice President Al Gore", - "Senator Gaylord Nelson" + "John McConnell" ], "correct_answer": 3 }, @@ -178,4 +178,4 @@ ], "correct_answer": 0 } -] \ No newline at end of file +] -- cgit v1.2.3 From e7e0e5d918dcb761cdfda2a2e3f02941973cff95 Mon Sep 17 00:00:00 2001 From: Rohan Date: Tue, 23 Apr 2019 21:15:52 +0530 Subject: using top level array in json file --- bot/resources/easter/easter_egg_facts.json | 6 ++---- bot/seasons/easter/egg_facts.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/resources/easter/easter_egg_facts.json b/bot/resources/easter/easter_egg_facts.json index c8f72c8d..7248045e 100644 --- a/bot/resources/easter/easter_egg_facts.json +++ b/bot/resources/easter/easter_egg_facts.json @@ -1,5 +1,4 @@ -{ - "facts": [ +[ "The first story of a rabbit (later named the \"Easter Bunny\") hiding eggs in a garden was published in 1680.", "Rabbits are known to be prolific pro creators and are an ancient symbol of fertility and new life. The German immigrants brought the tale of Easter Bunny in the 1700s with the tradition of an egg-laying hare called ‘Osterhase\". The kids then would make nests in which the creature would lay coloured eggs. The tradition has been revolutionized in the form of candies and gifts instead of eggs.", "In earlier days, a festival of egg throwing was held in church, when the priest would throw a hard-boiled egg to one of the choirboys. It was then tossed from one choirboy to the next and whoever held the egg when the clock struck 12 on Easter, was the winner and could keep it.", @@ -18,5 +17,4 @@ "Medieval Easter eggs were boiled with onions to give them a golden sheen. Edward I, however, went one better and in 1290 he ordered 450 eggs to be covered in gold leaf and given as Easter gifts.", "The first chocolate Easter egg was produced in 1873 by Fry\"s. Before this, people would give hollow cardboard eggs, filled with gifts.", "John Cadbury soon followed suit and made his first Cadbury Easter egg in 1875. By 1892 the company was producing 19 different lines, all made from dark chocolate." - ] -} \ No newline at end of file +] diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index dcc15078..c4cd8cf9 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -42,7 +42,7 @@ class EasterFacts(commands.Cog): embed = discord.Embed() embed.colour = Colours.soft_red embed.title = 'Easter Egg Fact' - random_fact = random.choice(self.facts['facts']) + random_fact = random.choice(self.facts) embed.description = random_fact return embed -- cgit v1.2.3 From d4381c597371d8052efe6f2c46ced4df8dc6f989 Mon Sep 17 00:00:00 2001 From: twhaley6 Date: Thu, 25 Apr 2019 12:11:39 -0400 Subject: riddle easter questions added --- bot/resources/easter/easter_riddle.json | 82 +++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 bot/resources/easter/easter_riddle.json (limited to 'bot') diff --git a/bot/resources/easter/easter_riddle.json b/bot/resources/easter/easter_riddle.json new file mode 100644 index 00000000..069d727a --- /dev/null +++ b/bot/resources/easter/easter_riddle.json @@ -0,0 +1,82 @@ +[ + { + "question": "What kind of music do bunnies like?", + "riddles": [ + "two words", + "jump to the beat" + ], + "correct_answer": "Hip hop" + }, + { + "question": "What kind of jewelry do rabbits wear?", + "riddles": [ + "they can eat it too", + "14 ___ gold" + ], + "correct_answer": "14 carrot gold" + }, + { + "question": "What does the easter bunny get for making a basket?", + "riddles": [ + "KOBE!", + "1+1 = ?" + ], + "correct_answer": "2 points!" + }, + { + "question": "Where does the easter bunny eat breakfast?", + "riddles": [ + "No waffles here", + "An international home" + ], + "correct_answer": "IHOP" + }, + { + "question": "What do you call a rabbit with fleas?", + "riddles": [ + "A bit of a looney tune", + "What's up Doc?" + ], + "correct_answer": "Bugs Bunny" + }, + { + "question": "Why was the little girl sad after the race?", + "riddles": [ + "2nd place?", + "Who beat her?" + ], + "correct_answer": "Because an egg beater!" + }, + { + "question": "What happened to the Easter Bunny when he misbehaved at school?", + "riddles": [ + "Won't be back anymore", + "Worse than suspension" + ], + "correct_answer": "He was eggspelled." + }, + { + "question": "What kind of bunny can't hop?", + "riddles": [ + "Might melt in the sun", + "Fragile and Yummy" + ], + "correct_answer": "A chocolate one!" + }, + { + "question": "Where does the Easter Bunny get his eggs?", + "riddles": [ + "Not a bush or tree", + "Emoji for a body part" + ], + "correct_answer": "Eggplants" + }, + { + "question": "Why did the Easter Bunny have to fire the duck?", + "riddles": [ + "quack", + "MY EGGS!!!" + ], + "correct_answer": "He kept quacking the eggs!" + } +] \ No newline at end of file -- cgit v1.2.3 From 9c0685abac0bcb97fd5361d802780d4964c282d2 Mon Sep 17 00:00:00 2001 From: Rohan Date: Fri, 26 Apr 2019 09:52:53 +0530 Subject: changes a few doc strings and renamed the background method --- bot/seasons/easter/egg_facts.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index c4cd8cf9..e5abd685 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -10,21 +10,28 @@ from bot.constants import Colours class EasterFacts(commands.Cog): - """A cog for easter egg facts.""" + """ + A cog contains a command that will return an easter egg fact when called. + + It also contains a background task which sends an easter egg fact in the event channel everyday. + """ + def __init__(self, bot): self.bot = bot self.facts = self.load_json() @staticmethod def load_json(): - """Loading the json data""" + """Load a list of easter egg facts from the resource JSON file.""" + p = Path('bot', 'resources', 'easter', 'easter_egg_facts.json') with p.open(encoding="utf8") as f: facts = load(f) return facts - async def background(self): - """A background task that sends a easter egg fact.""" + async def send_egg_fact_daily(self): + """A background task that sends an easter egg fact in the event channel everyday.""" + channel = self.bot.get_channel(426566445124812815) while True: embed = self.make_embed() @@ -34,11 +41,13 @@ class EasterFacts(commands.Cog): @commands.command(name='eggfact', aliases=['fact']) async def easter_facts(self, ctx): """Get easter egg facts.""" + embed = self.make_embed() await ctx.send(embed=embed) def make_embed(self): """Makes a nice embed for the message to be sent.""" + embed = discord.Embed() embed.colour = Colours.soft_red embed.title = 'Easter Egg Fact' @@ -48,6 +57,7 @@ class EasterFacts(commands.Cog): def setup(bot): - """Loading the cog""" - bot.loop.create_task(EasterFacts(bot).background()) + """EasterEgg facts loaded.""" + + bot.loop.create_task(EasterFacts(bot).send_egg_fact_daily()) bot.add_cog(EasterFacts(bot)) -- cgit v1.2.3 From fdf447498231539f21d19b77b73b5a1dd9e014ec Mon Sep 17 00:00:00 2001 From: Rohan Date: Fri, 26 Apr 2019 09:57:53 +0530 Subject: fixed a small lint error --- bot/seasons/easter/egg_facts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index e5abd685..9ddb9440 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -57,7 +57,7 @@ class EasterFacts(commands.Cog): def setup(bot): - """EasterEgg facts loaded.""" + """Easter Egg facts loaded.""" bot.loop.create_task(EasterFacts(bot).send_egg_fact_daily()) bot.add_cog(EasterFacts(bot)) -- cgit v1.2.3 From 052db73f2747d75b28df190bf2cf20782ed85a82 Mon Sep 17 00:00:00 2001 From: twhaley6 Date: Fri, 26 Apr 2019 16:29:51 -0400 Subject: easter riddle .py created --- bot/seasons/easter/easter_riddle.py | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 bot/seasons/easter/easter_riddle.py (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py new file mode 100644 index 00000000..0fff5542 --- /dev/null +++ b/bot/seasons/easter/easter_riddle.py @@ -0,0 +1,71 @@ +import asyncio +import logging +import random +from json import load +from pathlib import Path + +import discord +from discord.ext import commands + +from bot.constants import Colours + +log = logging.getLogger(__name__) + +with open(Path('bot', 'resources', 'easter', 'easter_riddle.json'), 'r', encoding="utf8") as f: + RIDDLE_QUESTIONS = load(f) + +TIMELIMIT = 10 + + +class EasterRiddle(commands.Cog): + """This cog contains the command for the Easter quiz!""" + + def __init__(self, bot): + self.bot = bot + self.quiz_messages = {} + + @commands.command(aliases=["riddlemethis", "riddleme"]) + async def riddle(self, ctx): + """ + Gives a random riddle questions, then provides 2 hints at 10 second intervals before revealing the answer + + """ + random_question = random.choice(RIDDLE_QUESTIONS) + question, hints = random_question["question"], random_question["riddles"] + correct = random_question["correct_answer"] + + description = f"You have {TIMELIMIT} seconds before the first hint.\n\n" + + q_embed = discord.Embed(title=question, description=description, colour=Colours.pink) + + msg = await ctx.send(embed=q_embed) + await asyncio.sleep(TIMELIMIT) + + """ + a_embed = discord.Embed( + title=f"Here's a hint: {hints[0]}!", + colour=Colours.pink + ) + + msg = await ctx.send(embed=q_embed) + await asyncio.sleep(TIMELIMIT) + + a_embed = discord.Embed( + title=f"Here's a hint: {hints[0]}!", + colour=Colours.pink + ) + """ + + a_embed = discord.Embed( + title=f"The answer is: {correct}!", + colour=Colours.pink + ) + + await ctx.send(embed=a_embed) + + +def setup(bot): + """Cog load.""" + + bot.add_cog(EasterRiddle(bot)) + log.info("Easter Riddle bot loaded") -- cgit v1.2.3 From 6de1c25335d818928e594edddbf715451a4506c5 Mon Sep 17 00:00:00 2001 From: twhaley6 Date: Fri, 26 Apr 2019 16:59:06 -0400 Subject: added user input checks --- bot/seasons/easter/easter_riddle.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 0fff5542..2562fa17 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -23,6 +23,7 @@ class EasterRiddle(commands.Cog): def __init__(self, bot): self.bot = bot self.quiz_messages = {} + self.mention = " " @commands.command(aliases=["riddlemethis", "riddleme"]) async def riddle(self, ctx): @@ -38,30 +39,41 @@ class EasterRiddle(commands.Cog): q_embed = discord.Embed(title=question, description=description, colour=Colours.pink) - msg = await ctx.send(embed=q_embed) + await ctx.send(embed=q_embed) await asyncio.sleep(TIMELIMIT) """ - a_embed = discord.Embed( + h_embed = discord.Embed( title=f"Here's a hint: {hints[0]}!", colour=Colours.pink ) - msg = await ctx.send(embed=q_embed) + await ctx.send(embed=h_embed) await asyncio.sleep(TIMELIMIT) - a_embed = discord.Embed( - title=f"Here's a hint: {hints[0]}!", + h_embed = discord.Embed( + title=f"Here's a hint: {hints[1]}!", colour=Colours.pink ) + + await ctx.send(embed=h_embed) + await asyncio.sleep(TIMELIMIT) """ + content = f"Well done {mention} for getting it correct!" if mention else "Nobody got it right..." + a_embed = discord.Embed( title=f"The answer is: {correct}!", colour=Colours.pink ) - await ctx.send(embed=a_embed) + await ctx.send(content, embed=a_embed) + + @commands.Cog.listener() + async def on_message(self, message): + if message.lower() == self.correct.lower(): + self.mention = message.author + def setup(bot): -- cgit v1.2.3 From 69811b9fdaeb79758439ef5768ca60ab2fe9f529 Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Fri, 26 Apr 2019 17:09:23 -0400 Subject: fixed some errors in easter_riddle --- bot/seasons/easter/easter_riddle.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 2562fa17..7a76e203 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -14,7 +14,7 @@ log = logging.getLogger(__name__) with open(Path('bot', 'resources', 'easter', 'easter_riddle.json'), 'r', encoding="utf8") as f: RIDDLE_QUESTIONS = load(f) -TIMELIMIT = 10 +TIMELIMIT = 20 class EasterRiddle(commands.Cog): @@ -24,6 +24,7 @@ class EasterRiddle(commands.Cog): self.bot = bot self.quiz_messages = {} self.mention = " " + self.correct = "" @commands.command(aliases=["riddlemethis", "riddleme"]) async def riddle(self, ctx): @@ -60,7 +61,7 @@ class EasterRiddle(commands.Cog): await asyncio.sleep(TIMELIMIT) """ - content = f"Well done {mention} for getting it correct!" if mention else "Nobody got it right..." + content = f"Well done {self.mention} for getting it correct!" if self.mention else "Nobody got it right..." a_embed = discord.Embed( title=f"The answer is: {correct}!", @@ -71,7 +72,7 @@ class EasterRiddle(commands.Cog): @commands.Cog.listener() async def on_message(self, message): - if message.lower() == self.correct.lower(): + if message.content.lower() == self.correct.lower(): self.mention = message.author -- cgit v1.2.3 From 0d88fdd2f28bde5d0c6dce6877b5abd9b39286a7 Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Fri, 26 Apr 2019 17:11:29 -0400 Subject: removed punctuation from riddles --- bot/resources/easter/easter_riddle.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/resources/easter/easter_riddle.json b/bot/resources/easter/easter_riddle.json index 069d727a..f32b5258 100644 --- a/bot/resources/easter/easter_riddle.json +++ b/bot/resources/easter/easter_riddle.json @@ -21,7 +21,7 @@ "KOBE!", "1+1 = ?" ], - "correct_answer": "2 points!" + "correct_answer": "2 points" }, { "question": "Where does the easter bunny eat breakfast?", @@ -45,7 +45,7 @@ "2nd place?", "Who beat her?" ], - "correct_answer": "Because an egg beater!" + "correct_answer": "Because an egg beater" }, { "question": "What happened to the Easter Bunny when he misbehaved at school?", @@ -53,7 +53,7 @@ "Won't be back anymore", "Worse than suspension" ], - "correct_answer": "He was eggspelled." + "correct_answer": "He was eggspelled" }, { "question": "What kind of bunny can't hop?", @@ -61,7 +61,7 @@ "Might melt in the sun", "Fragile and Yummy" ], - "correct_answer": "A chocolate one!" + "correct_answer": "A chocolate one" }, { "question": "Where does the Easter Bunny get his eggs?", @@ -77,6 +77,6 @@ "quack", "MY EGGS!!!" ], - "correct_answer": "He kept quacking the eggs!" + "correct_answer": "He kept quacking the eggs" } -] \ No newline at end of file +] -- cgit v1.2.3 From e6d53a15c6152807443e63c63b6d67333be231b1 Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Fri, 26 Apr 2019 17:48:53 -0400 Subject: added answer parsing and pining winner --- bot/seasons/easter/easter_riddle.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 7a76e203..18b4c31d 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -14,7 +14,7 @@ log = logging.getLogger(__name__) with open(Path('bot', 'resources', 'easter', 'easter_riddle.json'), 'r', encoding="utf8") as f: RIDDLE_QUESTIONS = load(f) -TIMELIMIT = 20 +TIMELIMIT = 10 class EasterRiddle(commands.Cog): @@ -23,7 +23,7 @@ class EasterRiddle(commands.Cog): def __init__(self, bot): self.bot = bot self.quiz_messages = {} - self.mention = " " + self.winner = " " self.correct = "" @commands.command(aliases=["riddlemethis", "riddleme"]) @@ -34,7 +34,7 @@ class EasterRiddle(commands.Cog): """ random_question = random.choice(RIDDLE_QUESTIONS) question, hints = random_question["question"], random_question["riddles"] - correct = random_question["correct_answer"] + self.correct = random_question["correct_answer"] description = f"You have {TIMELIMIT} seconds before the first hint.\n\n" @@ -43,7 +43,6 @@ class EasterRiddle(commands.Cog): await ctx.send(embed=q_embed) await asyncio.sleep(TIMELIMIT) - """ h_embed = discord.Embed( title=f"Here's a hint: {hints[0]}!", colour=Colours.pink @@ -59,21 +58,25 @@ class EasterRiddle(commands.Cog): await ctx.send(embed=h_embed) await asyncio.sleep(TIMELIMIT) - """ - content = f"Well done {self.mention} for getting it correct!" if self.mention else "Nobody got it right..." + if self.winner != " ": + content = "Well done " + self.winner + " for getting it correct!" + else: + content = "Nobody got it right..." a_embed = discord.Embed( - title=f"The answer is: {correct}!", + title=f"The answer is: {self.correct}!", colour=Colours.pink ) await ctx.send(content, embed=a_embed) + self.winner = " " + @commands.Cog.listener() async def on_message(self, message): if message.content.lower() == self.correct.lower(): - self.mention = message.author + self.winner = message.author.mention -- cgit v1.2.3 From 120fa538166eb6fbaa40579633c0d5694cb8199e Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Fri, 26 Apr 2019 18:06:02 -0400 Subject: finalized riddles and fixed various easter_riddle bugs --- bot/resources/easter/easter_riddle.json | 12 ++++++------ bot/seasons/easter/easter_riddle.py | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/resources/easter/easter_riddle.json b/bot/resources/easter/easter_riddle.json index f32b5258..e93f6dad 100644 --- a/bot/resources/easter/easter_riddle.json +++ b/bot/resources/easter/easter_riddle.json @@ -2,15 +2,15 @@ { "question": "What kind of music do bunnies like?", "riddles": [ - "two words", - "jump to the beat" + "Two words", + "Jump to the beat" ], "correct_answer": "Hip hop" }, { "question": "What kind of jewelry do rabbits wear?", "riddles": [ - "they can eat it too", + "They can eat it too", "14 ___ gold" ], "correct_answer": "14 carrot gold" @@ -59,7 +59,7 @@ "question": "What kind of bunny can't hop?", "riddles": [ "Might melt in the sun", - "Fragile and Yummy" + "Fragile and yummy" ], "correct_answer": "A chocolate one" }, @@ -74,8 +74,8 @@ { "question": "Why did the Easter Bunny have to fire the duck?", "riddles": [ - "quack", - "MY EGGS!!!" + "Quack", + "MY EGGS!!" ], "correct_answer": "He kept quacking the eggs" } diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 18b4c31d..6b2ab5ca 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -60,7 +60,7 @@ class EasterRiddle(commands.Cog): await asyncio.sleep(TIMELIMIT) if self.winner != " ": - content = "Well done " + self.winner + " for getting it correct!" + content = "Well done " + self.winner + "for getting it correct!" else: content = "Nobody got it right..." @@ -75,8 +75,9 @@ class EasterRiddle(commands.Cog): @commands.Cog.listener() async def on_message(self, message): - if message.content.lower() == self.correct.lower(): - self.winner = message.author.mention + if self.bot.user != message.author: + if message.content.lower() == self.correct.lower(): + self.winner = self.winner + message.author.mention + " " -- cgit v1.2.3 From 2bb744fb58723355ba46e06a74706bca059631fd Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Fri, 26 Apr 2019 18:17:22 -0400 Subject: changes to meet flake8 --- bot/seasons/easter/easter_riddle.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 6b2ab5ca..ced09dae 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -55,7 +55,7 @@ class EasterRiddle(commands.Cog): title=f"Here's a hint: {hints[1]}!", colour=Colours.pink ) - + await ctx.send(embed=h_embed) await asyncio.sleep(TIMELIMIT) @@ -77,12 +77,10 @@ class EasterRiddle(commands.Cog): async def on_message(self, message): if self.bot.user != message.author: if message.content.lower() == self.correct.lower(): - self.winner = self.winner + message.author.mention + " " - + self.winner = self.winner + message.author.mention + " " def setup(bot): """Cog load.""" - bot.add_cog(EasterRiddle(bot)) log.info("Easter Riddle bot loaded") -- cgit v1.2.3 From d611f5472134a9882396901f4e0e69102390de79 Mon Sep 17 00:00:00 2001 From: glowingrunes Date: Sun, 28 Apr 2019 16:27:53 +0100 Subject: Created a JSON file to store the bunny names. --- bot/resources/easter/bunny_names.json | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 bot/resources/easter/bunny_names.json (limited to 'bot') diff --git a/bot/resources/easter/bunny_names.json b/bot/resources/easter/bunny_names.json new file mode 100644 index 00000000..8c97169c --- /dev/null +++ b/bot/resources/easter/bunny_names.json @@ -0,0 +1,29 @@ +{ + "names": [ + "Flopsy", + "Hopsalot", + "Thumper", + "Nibbles", + "Daisy", + "Fuzzy", + "Cottontail", + "Carrot Top", + "Marshmallow", + "Lucky", + "Clover", + "Daffodil", + "Buttercup", + "Goldie", + "Dizzy", + "Trixie", + "Snuffles", + "Hopscotch", + "Skipper", + "Thunderfoot", + "Bigwig", + "Dandelion", + "Pipkin", + "Buckthorn", + "Skipper" + ] +} -- cgit v1.2.3 From e76c79d68829d3ff7401e0b02acdd80f9616abfd Mon Sep 17 00:00:00 2001 From: glowingrunes Date: Sun, 28 Apr 2019 16:32:42 +0100 Subject: Finished the random bunny name generator command. --- bot/seasons/easter/bunny_name_generator.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 bot/seasons/easter/bunny_name_generator.py (limited to 'bot') diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py new file mode 100644 index 00000000..b2bfb893 --- /dev/null +++ b/bot/seasons/easter/bunny_name_generator.py @@ -0,0 +1,25 @@ +import random +import json +import logging + +from discord.ext import commands +from pathlib import Path + + +log = logging.getLogger(__name__) + +with open(Path("bot", "resources", "easter", "bunny_names.json"), "r", encoding="utf8") as f: + BUNNY_NAMES = json.load(f) + + +class BunnyNameGenerator(commands.Cog): + """Generate a random bunny name, or bunnify your Discord username!""" + + def __init__(self, bot): + self.bot = bot + + @commands.command() + async def bunnyname(self, ctx): + """Picks a random bunny name from a JSON file""" + + await ctx.send(random.choice(BUNNY_NAMES["names"])) -- cgit v1.2.3 From e71ef5adf6769a79577b2ba472b423a7a8224694 Mon Sep 17 00:00:00 2001 From: glowingrunes Date: Sun, 28 Apr 2019 16:34:55 +0100 Subject: Completed the 'bunnifyme' command. --- bot/seasons/easter/bunny_name_generator.py | 97 ++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) (limited to 'bot') diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index b2bfb893..918e11d4 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -1,6 +1,7 @@ import random import json import logging +import re from discord.ext import commands from pathlib import Path @@ -23,3 +24,99 @@ class BunnyNameGenerator(commands.Cog): """Picks a random bunny name from a JSON file""" await ctx.send(random.choice(BUNNY_NAMES["names"])) + + @commands.command() + async def bunnifyme(self, ctx): + """Gets your Discord username and bunnifies it""" + + def filter_name(displayname): + """Turns separators into whitespace""" + + if displayname.find('_') != -1: + displayname = displayname.replace('_', ' ') + if displayname.find('.') != -1: + displayname = displayname.replace('.', ' ') + + return displayname + + def find_spaces(displayname): + """ + Check if Discord name contains spaces so we can bunnify + an individual word in the name. + Spaces should not be bunnified so we remove them from + the list that is returned from the pattern matching. + """ + + contains_spaces = re.findall(r'^\w+|\s+|\w+$', displayname) + if len(contains_spaces) > 1: + contains_spaces.remove(' ') + displayname = contains_spaces + return displayname + + def find_vowels(displayname): + """ + If the Discord name contains a vowel and the letter y, + it will match one or more of these patterns. + Only the most recently matched pattern will apply the changes. + """ + + new_name = None + + option1 = re.sub(r'a.+y', 'patchy', displayname) + option2 = re.sub(r'e.+y', 'ears', displayname) + option3 = re.sub(r'i.+y', 'ditsy', displayname) + option4 = re.sub(r'o.+y', 'oofy', displayname) + option5 = re.sub(r'u.+y', 'uffy', displayname) + + if option1 != displayname: + new_name = option1 + if option2 != displayname: + new_name = option2 + if option3 != displayname: + new_name = option3 + if option4 != displayname: + new_name = option4 + if option5 != displayname: + new_name = option5 + + print(new_name) + + return new_name + + def append_name(displayname): + """Adds a suffix to the end of the Discord name""" + + extensions = ['foot', 'ear', 'nose', 'tail'] + suffix = random.choice(extensions) + appended_name = displayname + suffix + + return appended_name + + username = ctx.message.author.display_name + username_filtered = filter_name(username) # Filter username before pattern matching + + spaces_pattern = find_spaces(username_filtered) # Does the name contain spaces? + + vowels_pattern = find_vowels(username_filtered) # Does the name contain vowels? + # If so, does it match any of the patterns in this function? + + unmatched_name = append_name(username_filtered) # Default if name doesn't match the above patterns + + if spaces_pattern is not None: + replacements = ['Cotton', 'Fluff', 'Floof' 'Bounce', 'Snuffle', 'Nibble', 'Cuddle', 'Velvetpaw', 'Carrot'] + word_to_replace = random.choice(spaces_pattern) + substitute = random.choice(replacements) + bunnified_name = username_filtered.replace(word_to_replace, substitute) + elif vowels_pattern is not None: + bunnified_name = vowels_pattern + elif unmatched_name: + bunnified_name = unmatched_name + + await ctx.send(bunnified_name) + + +def setup(bot): + """Cog load.""" + + bot.add_cog(BunnyNameGenerator(bot)) + log.info("BunnyNameGenerator cog loaded.") -- cgit v1.2.3 From 9d2fb1b1fdd85e22aaf88f8306524b02d5676803 Mon Sep 17 00:00:00 2001 From: glowingrunes Date: Sun, 28 Apr 2019 16:37:21 +0100 Subject: Removed code that was added for debugging purposes. --- bot/seasons/easter/bunny_name_generator.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 918e11d4..df1f0e28 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -79,8 +79,6 @@ class BunnyNameGenerator(commands.Cog): if option5 != displayname: new_name = option5 - print(new_name) - return new_name def append_name(displayname): -- cgit v1.2.3 From c537f29473a2ae8e1b8da0f3e1ffa6929d22f7be Mon Sep 17 00:00:00 2001 From: glowingrunes Date: Sun, 28 Apr 2019 17:33:22 +0100 Subject: Fixed linting errors. --- bot/seasons/easter/bunny_name_generator.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index df1f0e28..7d40e219 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -1,11 +1,10 @@ -import random import json import logging +import random import re - -from discord.ext import commands from pathlib import Path +from discord.ext import commands log = logging.getLogger(__name__) @@ -41,8 +40,8 @@ class BunnyNameGenerator(commands.Cog): def find_spaces(displayname): """ - Check if Discord name contains spaces so we can bunnify - an individual word in the name. + Check if Discord name contains spaces so we can bunnify an individual word in the name. + Spaces should not be bunnified so we remove them from the list that is returned from the pattern matching. """ @@ -55,6 +54,8 @@ class BunnyNameGenerator(commands.Cog): def find_vowels(displayname): """ + Finds vowels in the user's display name. + If the Discord name contains a vowel and the letter y, it will match one or more of these patterns. Only the most recently matched pattern will apply the changes. -- cgit v1.2.3 From a0adcd7956f5cbf1734e39b37930f8c30f6292a0 Mon Sep 17 00:00:00 2001 From: glowingrunes Date: Fri, 3 May 2019 21:30:48 +0100 Subject: Combined filter_name and find_spaces into one method. Renamed variables to be more Pythonic, and improved the comments. --- bot/seasons/easter/bunny_name_generator.py | 133 +++++++++++++---------------- 1 file changed, 60 insertions(+), 73 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 7d40e219..f8d0eea3 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -18,6 +18,54 @@ class BunnyNameGenerator(commands.Cog): def __init__(self, bot): self.bot = bot + def find_separators(self, displayname): + """ + Check if Discord name contains spaces so we can bunnify an individual word in the name. + """ + + new_name = re.split(r'[_.\s]', str(displayname)) + if displayname not in new_name: + return new_name + + def find_vowels(self, displayname): + """ + Finds vowels in the user's display name. + + If the Discord name contains a vowel and the letter y, + it will match one or more of these patterns. + Only the most recently matched pattern will apply the changes. + """ + + new_name = None + + option1 = re.sub(r'a.+y', 'patchy', displayname) + option2 = re.sub(r'e.+y', 'ears', displayname) + option3 = re.sub(r'i.+y', 'ditsy', displayname) + option4 = re.sub(r'o.+y', 'oofy', displayname) + option5 = re.sub(r'u.+y', 'uffy', displayname) + + if option1 != displayname: + new_name = option1 + if option2 != displayname: + new_name = option2 + if option3 != displayname: + new_name = option3 + if option4 != displayname: + new_name = option4 + if option5 != displayname: + new_name = option5 + + return new_name + + def append_name(self, displayname): + """Adds a suffix to the end of the Discord name""" + + extensions = ['foot', 'ear', 'nose', 'tail'] + suffix = random.choice(extensions) + appended_name = displayname + suffix + + return appended_name + @commands.command() async def bunnyname(self, ctx): """Picks a random bunny name from a JSON file""" @@ -28,86 +76,25 @@ class BunnyNameGenerator(commands.Cog): async def bunnifyme(self, ctx): """Gets your Discord username and bunnifies it""" - def filter_name(displayname): - """Turns separators into whitespace""" - - if displayname.find('_') != -1: - displayname = displayname.replace('_', ' ') - if displayname.find('.') != -1: - displayname = displayname.replace('.', ' ') - - return displayname - - def find_spaces(displayname): - """ - Check if Discord name contains spaces so we can bunnify an individual word in the name. - - Spaces should not be bunnified so we remove them from - the list that is returned from the pattern matching. - """ - - contains_spaces = re.findall(r'^\w+|\s+|\w+$', displayname) - if len(contains_spaces) > 1: - contains_spaces.remove(' ') - displayname = contains_spaces - return displayname - - def find_vowels(displayname): - """ - Finds vowels in the user's display name. - - If the Discord name contains a vowel and the letter y, - it will match one or more of these patterns. - Only the most recently matched pattern will apply the changes. - """ - - new_name = None - - option1 = re.sub(r'a.+y', 'patchy', displayname) - option2 = re.sub(r'e.+y', 'ears', displayname) - option3 = re.sub(r'i.+y', 'ditsy', displayname) - option4 = re.sub(r'o.+y', 'oofy', displayname) - option5 = re.sub(r'u.+y', 'uffy', displayname) - - if option1 != displayname: - new_name = option1 - if option2 != displayname: - new_name = option2 - if option3 != displayname: - new_name = option3 - if option4 != displayname: - new_name = option4 - if option5 != displayname: - new_name = option5 - - return new_name - - def append_name(displayname): - """Adds a suffix to the end of the Discord name""" - - extensions = ['foot', 'ear', 'nose', 'tail'] - suffix = random.choice(extensions) - appended_name = displayname + suffix - - return appended_name - username = ctx.message.author.display_name - username_filtered = filter_name(username) # Filter username before pattern matching - spaces_pattern = find_spaces(username_filtered) # Does the name contain spaces? + # If name contains spaces or other separators, get the individual words to randomly bunnify + spaces_in_name = self.find_separators(username) - vowels_pattern = find_vowels(username_filtered) # Does the name contain vowels? - # If so, does it match any of the patterns in this function? + # If name contains vowels, see if it matches any of the patterns in this function + # If there are matches, the bunnified name is returned. + vowels_in_name = self.find_vowels(username) - unmatched_name = append_name(username_filtered) # Default if name doesn't match the above patterns + # Default if the checks above return None + unmatched_name = self.append_name(username) - if spaces_pattern is not None: + if spaces_in_name is not None: replacements = ['Cotton', 'Fluff', 'Floof' 'Bounce', 'Snuffle', 'Nibble', 'Cuddle', 'Velvetpaw', 'Carrot'] - word_to_replace = random.choice(spaces_pattern) + word_to_replace = random.choice(spaces_in_name) substitute = random.choice(replacements) - bunnified_name = username_filtered.replace(word_to_replace, substitute) - elif vowels_pattern is not None: - bunnified_name = vowels_pattern + bunnified_name = username.replace(word_to_replace, substitute) + elif vowels_in_name is not None: + bunnified_name = vowels_in_name elif unmatched_name: bunnified_name = unmatched_name -- cgit v1.2.3 From eece8c93fcfdd2b5c82ca87a2d4f15eec9de99ba Mon Sep 17 00:00:00 2001 From: glowingrunes Date: Fri, 3 May 2019 21:35:28 +0100 Subject: Fixed linting error. --- bot/seasons/easter/bunny_name_generator.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index f8d0eea3..5dcbe62f 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -19,9 +19,7 @@ class BunnyNameGenerator(commands.Cog): self.bot = bot def find_separators(self, displayname): - """ - Check if Discord name contains spaces so we can bunnify an individual word in the name. - """ + """Check if Discord name contains spaces so we can bunnify an individual word in the name.""" new_name = re.split(r'[_.\s]', str(displayname)) if displayname not in new_name: -- cgit v1.2.3 From 66165599a5e493520ca6e38e800dfdcd4e9388cf Mon Sep 17 00:00:00 2001 From: RohanRadia Date: Wed, 15 May 2019 19:13:33 +0100 Subject: Created the .issue command --- bot/seasons/evergreen/issues.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 bot/seasons/evergreen/issues.py (limited to 'bot') diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py new file mode 100644 index 00000000..0960b72e --- /dev/null +++ b/bot/seasons/evergreen/issues.py @@ -0,0 +1,41 @@ +import json +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"): + 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): + """Cog Retrieves Issues From Github""" + + bot.add_cog(Issues(bot)) + log.info("Issues cog loaded") -- cgit v1.2.3 From 8910fb1612f441f001df3491fd14393fac65974d Mon Sep 17 00:00:00 2001 From: RohanRadia Date: Wed, 15 May 2019 19:19:04 +0100 Subject: Blank line linter error fixed --- bot/seasons/evergreen/issues.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 0960b72e..4d7f9a68 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -34,6 +34,7 @@ class Issues(commands.Cog): valid.add_field(name="Description", value=json_data.get("body"), inline=False) await ctx.send(embed=valid) + def setup(bot): """Cog Retrieves Issues From Github""" -- cgit v1.2.3 From a29c624d6984c531745fa9a39fa5114e9564b421 Mon Sep 17 00:00:00 2001 From: RohanRadia Date: Wed, 15 May 2019 19:21:53 +0100 Subject: Added docstring to function --- bot/seasons/evergreen/issues.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 4d7f9a68..4de13abd 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -14,6 +14,7 @@ class Issues(commands.Cog): self.bot = bot @commands.command(aliases=("issues",)) + """Command to retrieve issues from a GitHub repository""" async def issue(self, ctx, number: int, repository: str = "seasonalbot", user: str = "python-discord"): 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}.", -- cgit v1.2.3 From 07bf44f8c0bce21b2edd9760cff6eeb4d00139e9 Mon Sep 17 00:00:00 2001 From: RohanRadia Date: Wed, 15 May 2019 19:26:15 +0100 Subject: Fixed docstring location --- bot/seasons/evergreen/issues.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 4de13abd..42a827c8 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -14,8 +14,9 @@ class Issues(commands.Cog): self.bot = bot @commands.command(aliases=("issues",)) - """Command to retrieve issues from a GitHub repository""" 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!"} -- cgit v1.2.3 From 56c9b4071c964a687355646aba1c32348b71e1f1 Mon Sep 17 00:00:00 2001 From: RohanRadia Date: Wed, 15 May 2019 19:29:20 +0100 Subject: Removed unused json --- bot/seasons/evergreen/issues.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 42a827c8..fb85323f 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -1,4 +1,3 @@ -import json import logging import discord -- cgit v1.2.3 From cc1ed80fbcffed41d465b5e39168cca6ae1b1bcf Mon Sep 17 00:00:00 2001 From: Owez Date: Thu, 16 May 2019 15:11:15 +0100 Subject: Update bot/resources/easter/egghead_questions.json Co-Authored-By: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> --- bot/resources/easter/egghead_questions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/resources/easter/egghead_questions.json b/bot/resources/easter/egghead_questions.json index 38df1615..3262521a 100644 --- a/bot/resources/easter/egghead_questions.json +++ b/bot/resources/easter/egghead_questions.json @@ -55,7 +55,7 @@ "President Jimmy Carter", "President John F. Kennedy", "Vice President Al Gore", - "John McConnell" + "Senator Gaylord Nelson" ], "correct_answer": 3 }, -- cgit v1.2.3 From 131643d489f4f11ce7ea0316ab8bcd0062b9cb97 Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Mon, 17 Jun 2019 13:03:59 -0400 Subject: most changes requested implemented, fixed linting errors --- bot/seasons/easter/easter_riddle.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index ced09dae..65d94e5c 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -23,17 +23,15 @@ class EasterRiddle(commands.Cog): def __init__(self, bot): self.bot = bot self.quiz_messages = {} - self.winner = " " + self.winner = "" self.correct = "" @commands.command(aliases=["riddlemethis", "riddleme"]) async def riddle(self, ctx): - """ - Gives a random riddle questions, then provides 2 hints at 10 second intervals before revealing the answer - - """ + """Gives a random riddle questions, then provides 2 hints at 10 second intervals before revealing the answer""" random_question = random.choice(RIDDLE_QUESTIONS) - question, hints = random_question["question"], random_question["riddles"] + question = random_question["question"] + hints = random_question["riddles"] self.correct = random_question["correct_answer"] description = f"You have {TIMELIMIT} seconds before the first hint.\n\n" @@ -59,8 +57,8 @@ class EasterRiddle(commands.Cog): await ctx.send(embed=h_embed) await asyncio.sleep(TIMELIMIT) - if self.winner != " ": - content = "Well done " + self.winner + "for getting it correct!" + if not self.winner: + content = f"Well done {self.winner} for getting it correct!" else: content = "Nobody got it right..." @@ -71,10 +69,11 @@ class EasterRiddle(commands.Cog): await ctx.send(content, embed=a_embed) - self.winner = " " + self.winner = "" @commands.Cog.listener() async def on_message(self, message): + """If a non-bot user enters a correct answer, their username gets added to self.winner""" if self.bot.user != message.author: if message.content.lower() == self.correct.lower(): self.winner = self.winner + message.author.mention + " " -- cgit v1.2.3 From 6f01cdf7ec7f71d32e547cf259c5af42b7fec543 Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Mon, 17 Jun 2019 16:01:31 -0400 Subject: fixed winner list format and self.winner bool --- bot/seasons/easter/easter_riddle.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 65d94e5c..d3efdc43 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -23,7 +23,7 @@ class EasterRiddle(commands.Cog): def __init__(self, bot): self.bot = bot self.quiz_messages = {} - self.winner = "" + self.winner = [] self.correct = "" @commands.command(aliases=["riddlemethis", "riddleme"]) @@ -57,8 +57,10 @@ class EasterRiddle(commands.Cog): await ctx.send(embed=h_embed) await asyncio.sleep(TIMELIMIT) - if not self.winner: - content = f"Well done {self.winner} for getting it correct!" + if self.winner: + win_list = " " + win_list = win_list.join(self.winner) + content = f"Well done {win_list} for getting it correct!" else: content = "Nobody got it right..." @@ -76,7 +78,7 @@ class EasterRiddle(commands.Cog): """If a non-bot user enters a correct answer, their username gets added to self.winner""" if self.bot.user != message.author: if message.content.lower() == self.correct.lower(): - self.winner = self.winner + message.author.mention + " " + self.winner.append(message.author.mention) def setup(bot): -- cgit v1.2.3 From bf75c4dbf08f22e138ce145452f7738e8c836f5f Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Mon, 17 Jun 2019 16:24:55 -0400 Subject: fix variables and typo --- bot/seasons/easter/easter_riddle.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index d3efdc43..747c6cdc 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -22,8 +22,7 @@ class EasterRiddle(commands.Cog): def __init__(self, bot): self.bot = bot - self.quiz_messages = {} - self.winner = [] + self.winners = [] self.correct = "" @commands.command(aliases=["riddlemethis", "riddleme"]) @@ -57,10 +56,10 @@ class EasterRiddle(commands.Cog): await ctx.send(embed=h_embed) await asyncio.sleep(TIMELIMIT) - if self.winner: - win_list = " " - win_list = win_list.join(self.winner) + if self.winners: + win_list = " ".join(self.winners) content = f"Well done {win_list} for getting it correct!" + self.winners = [] else: content = "Nobody got it right..." @@ -71,14 +70,12 @@ class EasterRiddle(commands.Cog): await ctx.send(content, embed=a_embed) - self.winner = "" - @commands.Cog.listener() async def on_message(self, message): - """If a non-bot user enters a correct answer, their username gets added to self.winner""" + """If a non-bot user enters a correct answer, their username gets added to self.winners""" if self.bot.user != message.author: if message.content.lower() == self.correct.lower(): - self.winner.append(message.author.mention) + self.winners.append(message.author.mention) def setup(bot): -- cgit v1.2.3 From 38405eacfb41650d69b91163ae3b12712128cc9b Mon Sep 17 00:00:00 2001 From: Rohan Date: Sat, 22 Jun 2019 22:27:06 +0530 Subject: made the requested following changes : 1. Using the seasonalbot channel id from the constants file 2. Added logging to the setup funciton. --- bot/seasons/easter/egg_facts.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 9ddb9440..74733910 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -1,4 +1,5 @@ import asyncio +import logging import random from json import load from pathlib import Path @@ -6,9 +7,13 @@ from pathlib import Path import discord from discord.ext import commands +from bot.constants import Channels from bot.constants import Colours +log = logging.getLogger(__name__) + + class EasterFacts(commands.Cog): """ A cog contains a command that will return an easter egg fact when called. @@ -32,7 +37,7 @@ class EasterFacts(commands.Cog): async def send_egg_fact_daily(self): """A background task that sends an easter egg fact in the event channel everyday.""" - channel = self.bot.get_channel(426566445124812815) + channel = Channels.seasonalbot_chat while True: embed = self.make_embed() await channel.send(embed=embed) @@ -61,3 +66,4 @@ def setup(bot): bot.loop.create_task(EasterFacts(bot).send_egg_fact_daily()) bot.add_cog(EasterFacts(bot)) + log.info("EasterFacts cog loaded") -- cgit v1.2.3 From 1898c3cca4ea70767fe50bcd0020ad95fe295252 Mon Sep 17 00:00:00 2001 From: Rohan Date: Sat, 22 Jun 2019 22:34:49 +0530 Subject: removed blank lines after function doc strings --- bot/seasons/easter/egg_facts.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 74733910..2597714b 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -20,7 +20,6 @@ class EasterFacts(commands.Cog): It also contains a background task which sends an easter egg fact in the event channel everyday. """ - def __init__(self, bot): self.bot = bot self.facts = self.load_json() @@ -28,7 +27,6 @@ class EasterFacts(commands.Cog): @staticmethod def load_json(): """Load a list of easter egg facts from the resource JSON file.""" - p = Path('bot', 'resources', 'easter', 'easter_egg_facts.json') with p.open(encoding="utf8") as f: facts = load(f) @@ -36,7 +34,6 @@ class EasterFacts(commands.Cog): async def send_egg_fact_daily(self): """A background task that sends an easter egg fact in the event channel everyday.""" - channel = Channels.seasonalbot_chat while True: embed = self.make_embed() @@ -46,13 +43,11 @@ class EasterFacts(commands.Cog): @commands.command(name='eggfact', aliases=['fact']) async def easter_facts(self, ctx): """Get easter egg facts.""" - embed = self.make_embed() await ctx.send(embed=embed) def make_embed(self): """Makes a nice embed for the message to be sent.""" - embed = discord.Embed() embed.colour = Colours.soft_red embed.title = 'Easter Egg Fact' @@ -63,7 +58,6 @@ class EasterFacts(commands.Cog): def setup(bot): """Easter Egg facts loaded.""" - bot.loop.create_task(EasterFacts(bot).send_egg_fact_daily()) bot.add_cog(EasterFacts(bot)) log.info("EasterFacts cog loaded") -- cgit v1.2.3 From 4c2f1c619025b348c91642716b7d0f9091765a3b Mon Sep 17 00:00:00 2001 From: Rohan Date: Sat, 22 Jun 2019 22:37:25 +0530 Subject: gave a blank line after class doc string... --- bot/seasons/easter/egg_facts.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 2597714b..094190ed 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -20,6 +20,7 @@ class EasterFacts(commands.Cog): It also contains a background task which sends an easter egg fact in the event channel everyday. """ + def __init__(self, bot): self.bot = bot self.facts = self.load_json() -- cgit v1.2.3 From 764d52390fcd483ff466c94b0bd3ef70703a67a5 Mon Sep 17 00:00:00 2001 From: Rohan Date: Sat, 22 Jun 2019 22:43:05 +0530 Subject: removed blank spaces in blank line... --- bot/seasons/easter/egg_facts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 094190ed..1a19e48f 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -20,7 +20,7 @@ class EasterFacts(commands.Cog): It also contains a background task which sends an easter egg fact in the event channel everyday. """ - + def __init__(self, bot): self.bot = bot self.facts = self.load_json() -- cgit v1.2.3 From f15ca02dfafcc12b3a05e9a1a7024daa6f035b4f Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Wed, 3 Jul 2019 12:44:22 +0200 Subject: Changing id of terning4 to the correct one --- bot/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 67dd9328..be53a764 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -80,7 +80,7 @@ class Emojis: terning1 = "<:terning1:431249668983488527>" terning2 = "<:terning2:462339216987127808>" terning3 = "<:terning3:431249694467948544>" - terning4 = "<:terning4:431249704769290241>" + terning4 = "<:terning4:579980271475228682>" terning5 = "<:terning5:431249716328792064>" terning6 = "<:terning6:431249726705369098>" -- cgit v1.2.3 From adc6457aa3496c1606b0728231d4f0f5131207ed Mon Sep 17 00:00:00 2001 From: JackyFWong Date: Sun, 7 Jul 2019 15:40:58 -0400 Subject: implemented one-at-a-time execution policy --- bot/seasons/easter/easter_riddle.py | 77 +++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 34 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 747c6cdc..2cae299a 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -24,58 +24,67 @@ class EasterRiddle(commands.Cog): self.bot = bot self.winners = [] self.correct = "" + self.current_channel = None @commands.command(aliases=["riddlemethis", "riddleme"]) async def riddle(self, ctx): """Gives a random riddle questions, then provides 2 hints at 10 second intervals before revealing the answer""" - random_question = random.choice(RIDDLE_QUESTIONS) - question = random_question["question"] - hints = random_question["riddles"] - self.correct = random_question["correct_answer"] + if not self.current_channel: + self.current_channel = ctx.message.channel - description = f"You have {TIMELIMIT} seconds before the first hint.\n\n" + random_question = random.choice(RIDDLE_QUESTIONS) + question = random_question["question"] + hints = random_question["riddles"] + self.correct = random_question["correct_answer"] - q_embed = discord.Embed(title=question, description=description, colour=Colours.pink) + description = f"You have {TIMELIMIT} seconds before the first hint.\n\n" - await ctx.send(embed=q_embed) - await asyncio.sleep(TIMELIMIT) + q_embed = discord.Embed(title=question, description=description, colour=Colours.pink) - h_embed = discord.Embed( - title=f"Here's a hint: {hints[0]}!", - colour=Colours.pink - ) + await ctx.send(embed=q_embed) + await asyncio.sleep(TIMELIMIT) - await ctx.send(embed=h_embed) - await asyncio.sleep(TIMELIMIT) + h_embed = discord.Embed( + title=f"Here's a hint: {hints[0]}!", + colour=Colours.pink + ) - h_embed = discord.Embed( - title=f"Here's a hint: {hints[1]}!", - colour=Colours.pink - ) + await ctx.send(embed=h_embed) + await asyncio.sleep(TIMELIMIT) - await ctx.send(embed=h_embed) - await asyncio.sleep(TIMELIMIT) + h_embed = discord.Embed( + title=f"Here's a hint: {hints[1]}!", + colour=Colours.pink + ) - if self.winners: - win_list = " ".join(self.winners) - content = f"Well done {win_list} for getting it correct!" - self.winners = [] - else: - content = "Nobody got it right..." + await ctx.send(embed=h_embed) + await asyncio.sleep(TIMELIMIT) + + if self.winners: + win_list = " ".join(self.winners) + content = f"Well done {win_list} for getting it correct!" + self.winners = [] + else: + content = "Nobody got it right..." - a_embed = discord.Embed( - title=f"The answer is: {self.correct}!", - colour=Colours.pink - ) + a_embed = discord.Embed( + title=f"The answer is: {self.correct}!", + colour=Colours.pink + ) - await ctx.send(content, embed=a_embed) + await ctx.send(content, embed=a_embed) + + self.current_channel = None @commands.Cog.listener() async def on_message(self, message): """If a non-bot user enters a correct answer, their username gets added to self.winners""" - if self.bot.user != message.author: - if message.content.lower() == self.correct.lower(): - self.winners.append(message.author.mention) + if self.current_channel == message.channel: + if self.bot.user != message.author: + if message.content.lower() == self.correct.lower(): + self.winners.append(message.author.mention) + else: + return def setup(bot): -- cgit v1.2.3 From 08587c416de6c037164516a14ddc1a8e1893aa3e Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 10 Jul 2019 16:48:32 -0400 Subject: Add missing whitespace to egghead questions JSON --- bot/resources/easter/egghead_questions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/resources/easter/egghead_questions.json b/bot/resources/easter/egghead_questions.json index 3262521a..5535f8e0 100644 --- a/bot/resources/easter/egghead_questions.json +++ b/bot/resources/easter/egghead_questions.json @@ -55,7 +55,7 @@ "President Jimmy Carter", "President John F. Kennedy", "Vice President Al Gore", - "Senator Gaylord Nelson" + "Senator Gaylord Nelson" ], "correct_answer": 3 }, -- cgit v1.2.3 From 3e0a39bc4aec73a9bade408f514f54aef8f4af44 Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 10 Jul 2019 16:49:42 -0400 Subject: Align setup docstring to database, fix docstring issues --- bot/seasons/evergreen/issues.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index fb85323f..840d9ead 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -7,15 +7,14 @@ log = logging.getLogger(__name__) class Issues(commands.Cog): - """Cog that allows users to retrieve issues from GitHub""" + """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""" - + """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!"} @@ -37,7 +36,6 @@ class Issues(commands.Cog): def setup(bot): - """Cog Retrieves Issues From Github""" - + """Github Issues Cog Load.""" bot.add_cog(Issues(bot)) log.info("Issues cog loaded") -- cgit v1.2.3 From 0f71d907aab685d2001e324c0641b040ce930c4c Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 10 Jul 2019 16:52:38 -0400 Subject: Clean up vowel finding logic, update docstrings for new linting rules --- bot/seasons/easter/bunny_name_generator.py | 40 ++++++++++-------------------- 1 file changed, 13 insertions(+), 27 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 5dcbe62f..1fca3c20 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -20,7 +20,6 @@ class BunnyNameGenerator(commands.Cog): def find_separators(self, displayname): """Check if Discord name contains spaces so we can bunnify an individual word in the name.""" - new_name = re.split(r'[_.\s]', str(displayname)) if displayname not in new_name: return new_name @@ -33,31 +32,21 @@ class BunnyNameGenerator(commands.Cog): it will match one or more of these patterns. Only the most recently matched pattern will apply the changes. """ - - new_name = None - - option1 = re.sub(r'a.+y', 'patchy', displayname) - option2 = re.sub(r'e.+y', 'ears', displayname) - option3 = re.sub(r'i.+y', 'ditsy', displayname) - option4 = re.sub(r'o.+y', 'oofy', displayname) - option5 = re.sub(r'u.+y', 'uffy', displayname) - - if option1 != displayname: - new_name = option1 - if option2 != displayname: - new_name = option2 - if option3 != displayname: - new_name = option3 - if option4 != displayname: - new_name = option4 - if option5 != displayname: - new_name = option5 - - return new_name + expressions = [ + (r'a.+y', 'patchy'), + (r'e.+y', 'ears'), + (r'i.+y', 'ditsy'), + (r'o.+y', 'oofy'), + (r'u.+y', 'uffy'), + ] + + for exp, vowel_sub in expressions: + new_name = re.sub(exp, vowel_sub, displayname) + if new_name != displayname: + return new_name def append_name(self, displayname): """Adds a suffix to the end of the Discord name""" - extensions = ['foot', 'ear', 'nose', 'tail'] suffix = random.choice(extensions) appended_name = displayname + suffix @@ -67,13 +56,11 @@ class BunnyNameGenerator(commands.Cog): @commands.command() async def bunnyname(self, ctx): """Picks a random bunny name from a JSON file""" - await ctx.send(random.choice(BUNNY_NAMES["names"])) @commands.command() async def bunnifyme(self, ctx): """Gets your Discord username and bunnifies it""" - username = ctx.message.author.display_name # If name contains spaces or other separators, get the individual words to randomly bunnify @@ -100,7 +87,6 @@ class BunnyNameGenerator(commands.Cog): def setup(bot): - """Cog load.""" - + """Bunny Name Generator Cog load.""" bot.add_cog(BunnyNameGenerator(bot)) log.info("BunnyNameGenerator cog loaded.") -- cgit v1.2.3 From 43839d74e71e8fb6b23704b8d07f6a63b2ba36de Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 10 Jul 2019 16:56:56 -0400 Subject: Add user feedback if a riddle is already in progress --- bot/seasons/easter/easter_riddle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 2cae299a..a6900d3b 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -75,6 +75,8 @@ class EasterRiddle(commands.Cog): await ctx.send(content, embed=a_embed) self.current_channel = None + else: + await ctx.send(f"A riddle is already being solved in {self.current_channel.mention}!") @commands.Cog.listener() async def on_message(self, message): @@ -88,6 +90,6 @@ class EasterRiddle(commands.Cog): def setup(bot): - """Cog load.""" + """Easter Riddle Cog load.""" bot.add_cog(EasterRiddle(bot)) log.info("Easter Riddle bot loaded") -- cgit v1.2.3 From 05793418504eeac85d52df78d51fc15be16c3b1e Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 10 Jul 2019 16:59:09 -0400 Subject: Add missing channel getter --- bot/seasons/easter/egg_facts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 1a19e48f..6155670e 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -35,7 +35,7 @@ class EasterFacts(commands.Cog): async def send_egg_fact_daily(self): """A background task that sends an easter egg fact in the event channel everyday.""" - channel = Channels.seasonalbot_chat + channel = self.bot.get_channel(Channels.seasonalbot_chat) while True: embed = self.make_embed() await channel.send(embed=embed) @@ -58,7 +58,7 @@ class EasterFacts(commands.Cog): def setup(bot): - """Easter Egg facts loaded.""" + """Easter Egg facts cog load.""" bot.loop.create_task(EasterFacts(bot).send_egg_fact_daily()) bot.add_cog(EasterFacts(bot)) log.info("EasterFacts cog loaded") -- cgit v1.2.3 From d8d3a4b194ff58f9331a05c71b2e675f40022e19 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Mon, 22 Jul 2019 13:34:36 -0400 Subject: Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update riddle docstring for clarity Minor change to feedback phrasing Co-Authored-By: Leon Sandøy --- bot/seasons/easter/easter_riddle.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index a6900d3b..09d11073 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -28,7 +28,11 @@ class EasterRiddle(commands.Cog): @commands.command(aliases=["riddlemethis", "riddleme"]) async def riddle(self, ctx): - """Gives a random riddle questions, then provides 2 hints at 10 second intervals before revealing the answer""" + """ + Gives a random riddle, then provides 2 hints at certain intervals before revealing the answer. + + The duration of the hint interval can be configured by changing the TIMELIMIT constant in this file. + """ if not self.current_channel: self.current_channel = ctx.message.channel @@ -62,7 +66,7 @@ class EasterRiddle(commands.Cog): if self.winners: win_list = " ".join(self.winners) - content = f"Well done {win_list} for getting it correct!" + content = f"Well done {win_list} for getting it right!" self.winners = [] else: content = "Nobody got it right..." -- cgit v1.2.3 From 2794a852294f66f3e232a9baad100468407c32e5 Mon Sep 17 00:00:00 2001 From: sco1 Date: Mon, 22 Jul 2019 13:43:32 -0400 Subject: Rename variables for clarity, fix extraneous whitespace from review --- bot/seasons/easter/easter_riddle.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 09d11073..2d4d1265 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -30,7 +30,7 @@ class EasterRiddle(commands.Cog): async def riddle(self, ctx): """ Gives a random riddle, then provides 2 hints at certain intervals before revealing the answer. - + The duration of the hint interval can be configured by changing the TIMELIMIT constant in this file. """ if not self.current_channel: @@ -43,25 +43,25 @@ class EasterRiddle(commands.Cog): description = f"You have {TIMELIMIT} seconds before the first hint.\n\n" - q_embed = discord.Embed(title=question, description=description, colour=Colours.pink) + riddle_embed = discord.Embed(title=question, description=description, colour=Colours.pink) - await ctx.send(embed=q_embed) + await ctx.send(embed=riddle_embed) await asyncio.sleep(TIMELIMIT) - h_embed = discord.Embed( + hint_embed = discord.Embed( title=f"Here's a hint: {hints[0]}!", colour=Colours.pink ) - await ctx.send(embed=h_embed) + await ctx.send(embed=hint_embed) await asyncio.sleep(TIMELIMIT) - h_embed = discord.Embed( + hint_embed = discord.Embed( title=f"Here's a hint: {hints[1]}!", colour=Colours.pink ) - await ctx.send(embed=h_embed) + await ctx.send(embed=hint_embed) await asyncio.sleep(TIMELIMIT) if self.winners: @@ -71,12 +71,12 @@ class EasterRiddle(commands.Cog): else: content = "Nobody got it right..." - a_embed = discord.Embed( + answer_embed = discord.Embed( title=f"The answer is: {self.correct}!", colour=Colours.pink ) - await ctx.send(content, embed=a_embed) + await ctx.send(content, embed=answer_embed) self.current_channel = None else: -- cgit v1.2.3 From 75585469ebef6e560c6650105c9e6b7a5521bd62 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Mon, 22 Jul 2019 17:44:21 -0400 Subject: Apply suggestions from code review * Unify use of apostophes & quotes in JSON resources * Sync use of pathlib to the rest of the codebase * Remove unnecessary string conversions * Remove unnecessary dictionary `.get()` call without specifying a default * Other minor syntax modifications Co-Authored-By: Mark --- bot/resources/easter/easter_egg_facts.json | 16 ++++++++-------- bot/seasons/easter/bunny_name_generator.py | 4 ++-- bot/seasons/easter/easter_riddle.py | 2 +- bot/seasons/easter/egg_facts.py | 4 ++-- bot/seasons/evergreen/issues.py | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) (limited to 'bot') diff --git a/bot/resources/easter/easter_egg_facts.json b/bot/resources/easter/easter_egg_facts.json index 7248045e..ee6d4678 100644 --- a/bot/resources/easter/easter_egg_facts.json +++ b/bot/resources/easter/easter_egg_facts.json @@ -1,20 +1,20 @@ [ "The first story of a rabbit (later named the \"Easter Bunny\") hiding eggs in a garden was published in 1680.", - "Rabbits are known to be prolific pro creators and are an ancient symbol of fertility and new life. The German immigrants brought the tale of Easter Bunny in the 1700s with the tradition of an egg-laying hare called ‘Osterhase\". The kids then would make nests in which the creature would lay coloured eggs. The tradition has been revolutionized in the form of candies and gifts instead of eggs.", + "Rabbits are known to be prolific pro creators and are an ancient symbol of fertility and new life. The German immigrants brought the tale of Easter Bunny in the 1700s with the tradition of an egg-laying hare called \"Osterhase\". The kids then would make nests in which the creature would lay coloured eggs. The tradition has been revolutionized in the form of candies and gifts instead of eggs.", "In earlier days, a festival of egg throwing was held in church, when the priest would throw a hard-boiled egg to one of the choirboys. It was then tossed from one choirboy to the next and whoever held the egg when the clock struck 12 on Easter, was the winner and could keep it.", "In medieval times, Easter eggs were boiled with onions to give them a golden sheen. Edward I went beyond this tradition in 1290 and ordered 450 eggs to be covered in gold leaf and given as Easter gifts.", "Decorating Easter eggs is an ancient tradition that dates back to 13th century. One of the explanations for this custom is that eggs were considered as a forbidden food during the Lenten season (40 days before Easter). Therefore, people would paint and decorate them to mark an end of the period of penance and fasting and later eat them on Easter. The tradition of decorating eggs is called Pysanka which is creating a traditional Ukrainian folk design using wax-resist method.", - "Members of the Greek Orthodox faith often paint their Easter eggs red, which symbolizes Jesus\" blood and his victory over death. The color red, symbolizes renewal of life, such as, Jesus\" resurrection.", - "Eggs rolling take place in many parts of the world which symbolizes stone which was rolled away from the tomb where Jesus\" body was laid after his death.", - "Easter eggs have been considered as a symbol of fertility, rebirth and new life. The custom of giving eggs has been derived from Egyptians, Persians, Gauls, Greeks and Romans.", - "The first chocolate Easter egg was made by Fry\"s in 1873. Before this, people would give hollow cardboard eggs, filled with gifts.", + "Members of the Greek Orthodox faith often paint their Easter eggs red, which symbolizes Jesus' blood and his victory over death. The color red, symbolizes renewal of life, such as, Jesus' resurrection.", + "Eggs rolling take place in many parts of the world which symbolizes stone which was rolled away from the tomb where Jesus' body was laid after his death.", + "Easter eggs have been considered as a symbol of fertility, rebirth and new life. The custom of giving eggs has been derived from Egyptians, Persians, Gauls, Greeks, and Romans.", + "The first chocolate Easter egg was made by Fry's in 1873. Before this, people would give hollow cardboard eggs, filled with gifts.", "The tallest chocolate Easter egg was made in Italy in 2011. Standing 10.39 metres tall and weighing 7,200 kg, it was taller than a giraffe and heavier than an elephant.", "The largest ever Easter egg hunt was in Florida, where 9,753 children searched for 501,000 eggs.", "In 2007, an Easter egg covered in diamonds sold for almost £9 million. Every hour, a cockerel made of jewels pops up from the top of the Faberge egg, flaps its wings four times, nods its head three times and makes a crowing noise. The gold-and-pink enamel egg was made by the Russian royal family as an engagement gift for French aristocrat Baron Edouard de Rothschild.", - "The White House held their first official egg roll in 1878 when Rutherford B. Hayes was the President. It is a race in which children push decorated, hard-boiled eggs across the White House lawn as an annual event held the Monday after Easter. In 2009, the Obamas hosted their first Easter egg roll with the theme, ‘Let\"s go play\" which was meant to encourage young people to lead healthy and active lives.", - "80 million chocolate Easter eggs are sold each year. This accounts for 10% of Britain\"s annual spending on chocolate!", + "The White House held their first official egg roll in 1878 when Rutherford B. Hayes was the President. It is a race in which children push decorated, hard-boiled eggs across the White House lawn as an annual event held the Monday after Easter. In 2009, the Obamas hosted their first Easter egg roll with the theme, \"Let's go play\" which was meant to encourage young people to lead healthy and active lives.", + "80 million chocolate Easter eggs are sold each year. This accounts for 10% of Britain's annual spending on chocolate!", "The tradition of giving eggs at Easter has been traced back to Egyptians, Persians, Gauls, Greeks and Romans, who saw the egg as a symbol of life.", "Medieval Easter eggs were boiled with onions to give them a golden sheen. Edward I, however, went one better and in 1290 he ordered 450 eggs to be covered in gold leaf and given as Easter gifts.", "The first chocolate Easter egg was produced in 1873 by Fry\"s. Before this, people would give hollow cardboard eggs, filled with gifts.", - "John Cadbury soon followed suit and made his first Cadbury Easter egg in 1875. By 1892 the company was producing 19 different lines, all made from dark chocolate." + "John Cadbury soon followed suit and made his first Cadbury Easter egg in 1875. By 1892 the company was producing 19 different lines, all made from dark chocolate." ] diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 1fca3c20..76d5c478 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -8,7 +8,7 @@ from discord.ext import commands log = logging.getLogger(__name__) -with open(Path("bot", "resources", "easter", "bunny_names.json"), "r", encoding="utf8") as f: +with Path("bot/resources/easter/bunny_names.json").open("r", encoding="utf8") as f: BUNNY_NAMES = json.load(f) @@ -20,7 +20,7 @@ class BunnyNameGenerator(commands.Cog): def find_separators(self, displayname): """Check if Discord name contains spaces so we can bunnify an individual word in the name.""" - new_name = re.split(r'[_.\s]', str(displayname)) + new_name = re.split(r'[_.\s]', displayname) if displayname not in new_name: return new_name diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 2d4d1265..d6e6c207 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -11,7 +11,7 @@ from bot.constants import Colours log = logging.getLogger(__name__) -with open(Path('bot', 'resources', 'easter', 'easter_riddle.json'), 'r', encoding="utf8") as f: +with Path("bot/resources/easter/easter_riddle.json").open("r", encoding="utf8") as f: RIDDLE_QUESTIONS = load(f) TIMELIMIT = 10 diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 6155670e..6d70225a 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -28,7 +28,7 @@ class EasterFacts(commands.Cog): @staticmethod def load_json(): """Load a list of easter egg facts from the resource JSON file.""" - p = Path('bot', 'resources', 'easter', 'easter_egg_facts.json') + p = Path("bot/resources/easter/easter_egg_facts.json") with p.open(encoding="utf8") as f: facts = load(f) return facts @@ -39,7 +39,7 @@ class EasterFacts(commands.Cog): while True: embed = self.make_embed() await channel.send(embed=embed) - await asyncio.sleep(24*60*60) + await asyncio.sleep(24 * 60 * 60) @commands.command(name='eggfact', aliases=['fact']) async def easter_facts(self, ctx): diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 840d9ead..c6dbe344 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -15,7 +15,7 @@ class Issues(commands.Cog): @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)}" + url = f"https://api.github.com/repos/{user}/{repository}/issues/{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!"} @@ -27,8 +27,8 @@ class Issues(commands.Cog): 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="Issue Number", value=f"#{number}", inline=False) + valid.add_field(name="Status", value=json_data["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) -- cgit v1.2.3 From 33fc6c798d6b4e32d028e7a506d056782b15ce12 Mon Sep 17 00:00:00 2001 From: sco1 Date: Mon, 22 Jul 2019 21:09:03 -0400 Subject: Additional review comment resolution * Remove duplicate easter egg facts, fix punctuation * Simplify egg fact JSON load * Simplify egg fact embed generation * Simplify issues command, remove redundant calls, and make some variables more explicit * Truncate issue body if its length is greater than 1024 characters (embed field limitation) Co-Authored-By: Mark --- bot/resources/easter/easter_egg_facts.json | 3 --- bot/seasons/easter/easter_riddle.py | 2 -- bot/seasons/easter/egg_facts.py | 14 +++++------- bot/seasons/evergreen/issues.py | 35 +++++++++++++++++++----------- 4 files changed, 28 insertions(+), 26 deletions(-) (limited to 'bot') diff --git a/bot/resources/easter/easter_egg_facts.json b/bot/resources/easter/easter_egg_facts.json index ee6d4678..b0650b84 100644 --- a/bot/resources/easter/easter_egg_facts.json +++ b/bot/resources/easter/easter_egg_facts.json @@ -13,8 +13,5 @@ "In 2007, an Easter egg covered in diamonds sold for almost £9 million. Every hour, a cockerel made of jewels pops up from the top of the Faberge egg, flaps its wings four times, nods its head three times and makes a crowing noise. The gold-and-pink enamel egg was made by the Russian royal family as an engagement gift for French aristocrat Baron Edouard de Rothschild.", "The White House held their first official egg roll in 1878 when Rutherford B. Hayes was the President. It is a race in which children push decorated, hard-boiled eggs across the White House lawn as an annual event held the Monday after Easter. In 2009, the Obamas hosted their first Easter egg roll with the theme, \"Let's go play\" which was meant to encourage young people to lead healthy and active lives.", "80 million chocolate Easter eggs are sold each year. This accounts for 10% of Britain's annual spending on chocolate!", - "The tradition of giving eggs at Easter has been traced back to Egyptians, Persians, Gauls, Greeks and Romans, who saw the egg as a symbol of life.", - "Medieval Easter eggs were boiled with onions to give them a golden sheen. Edward I, however, went one better and in 1290 he ordered 450 eggs to be covered in gold leaf and given as Easter gifts.", - "The first chocolate Easter egg was produced in 1873 by Fry\"s. Before this, people would give hollow cardboard eggs, filled with gifts.", "John Cadbury soon followed suit and made his first Cadbury Easter egg in 1875. By 1892 the company was producing 19 different lines, all made from dark chocolate." ] diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index d6e6c207..3c24075c 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -89,8 +89,6 @@ class EasterRiddle(commands.Cog): if self.bot.user != message.author: if message.content.lower() == self.correct.lower(): self.winners.append(message.author.mention) - else: - return def setup(bot): diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 6d70225a..ae08ccd4 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -30,8 +30,7 @@ class EasterFacts(commands.Cog): """Load a list of easter egg facts from the resource JSON file.""" p = Path("bot/resources/easter/easter_egg_facts.json") with p.open(encoding="utf8") as f: - facts = load(f) - return facts + return load(f) async def send_egg_fact_daily(self): """A background task that sends an easter egg fact in the event channel everyday.""" @@ -49,12 +48,11 @@ class EasterFacts(commands.Cog): def make_embed(self): """Makes a nice embed for the message to be sent.""" - embed = discord.Embed() - embed.colour = Colours.soft_red - embed.title = 'Easter Egg Fact' - random_fact = random.choice(self.facts) - embed.description = random_fact - return embed + return discord.Embed( + colour=Colours.soft_red, + title="Easter Egg Fact", + description=random.choice(self.facts) + ) def setup(bot): diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index c6dbe344..04531c54 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -16,23 +16,32 @@ class Issues(commands.Cog): 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/{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!"} + failed_status = { + 404: f"Issue #{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() + response_code = r.status - 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"#{number}", inline=False) - valid.add_field(name="Status", value=json_data["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) + if response_code in failed_status: + return await ctx.send(failed_status[response_code]) + + issue_embed = discord.Embed(colour=0x00ff37) + issue_embed.add_field(name="Repository", value=f"{user}/{repository}", inline=False) + issue_embed.add_field(name="Issue Number", value=f"#{number}", inline=False) + issue_embed.add_field(name="Status", value=json_data["state"].title()) + issue_embed.add_field(name="Link", value=url, inline=False) + + description = json_data["body"] + if len(description) > 1024: + truncation_message = "** ...\n\nContent truncated, please follow the link for more!**" + description = f"{description[:1024 - len(truncation_message)]}{truncation_message}" + + issue_embed.add_field(name="Description", value=description, inline=False) + + await ctx.send(embed=issue_embed) def setup(bot): -- cgit v1.2.3 From 03962ac0a774def04ec6fcd708a9daec3fae4ea1 Mon Sep 17 00:00:00 2001 From: sco1 Date: Mon, 22 Jul 2019 21:21:17 -0400 Subject: Fix typo in April Fools resource load --- bot/seasons/easter/april_fools_vids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/easter/april_fools_vids.py b/bot/seasons/easter/april_fools_vids.py index 9fbe87a0..d921d07c 100644 --- a/bot/seasons/easter/april_fools_vids.py +++ b/bot/seasons/easter/april_fools_vids.py @@ -19,7 +19,7 @@ class AprilFoolVideos(commands.Cog): @staticmethod def load_json(): """A function to load JSON data.""" - p = Path('bot/resources/easterapril_fools_vids.json') + p = Path('bot/resources/easter/april_fools_vids.json') with p.open() as json_file: all_vids = load(json_file) return all_vids -- cgit v1.2.3 From c1ef23d8f1ac956c00e98303bf0a5449daa57e87 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Wed, 31 Jul 2019 17:13:36 -0400 Subject: Apply issue embed suggestions from code review Co-Authored-By: Ava --- bot/seasons/evergreen/issues.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 04531c54..13665782 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -29,10 +29,10 @@ class Issues(commands.Cog): return await ctx.send(failed_status[response_code]) issue_embed = discord.Embed(colour=0x00ff37) - issue_embed.add_field(name="Repository", value=f"{user}/{repository}", inline=False) + issue_embed.add_field(name="Repository", value=f"[{user}/{repository}](json_data['repository_url'])", inline=False) issue_embed.add_field(name="Issue Number", value=f"#{number}", inline=False) issue_embed.add_field(name="Status", value=json_data["state"].title()) - issue_embed.add_field(name="Link", value=url, inline=False) + issue_embed.add_field(name="Link", value=json_data["url"], inline=False) description = json_data["body"] if len(description) > 1024: -- cgit v1.2.3 From bbe4347efc53c020e0df0ee80969202ce4f7619c Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 31 Jul 2019 17:47:20 -0400 Subject: Additional review comment resolution * Fix line length & broken f string * Use correct URLs for GH access * Align GH issue description truncation placeholder with textwrap.TextWrapper for consistency * Utilize color constant rather than hex code * Flatten logic blocks by reversing logic * Additional minor syntax changes Co-Authored-By: Ava --- bot/seasons/easter/easter_riddle.py | 84 +++++++++++++++++++------------------ bot/seasons/evergreen/issues.py | 17 ++++---- 2 files changed, 54 insertions(+), 47 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 3c24075c..56555586 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -33,62 +33,66 @@ class EasterRiddle(commands.Cog): The duration of the hint interval can be configured by changing the TIMELIMIT constant in this file. """ - if not self.current_channel: - self.current_channel = ctx.message.channel + if self.current_channel: + return await ctx.send(f"A riddle is already being solved in {self.current_channel.mention}!") - random_question = random.choice(RIDDLE_QUESTIONS) - question = random_question["question"] - hints = random_question["riddles"] - self.correct = random_question["correct_answer"] + self.current_channel = ctx.message.channel - description = f"You have {TIMELIMIT} seconds before the first hint.\n\n" + random_question = random.choice(RIDDLE_QUESTIONS) + question = random_question["question"] + hints = random_question["riddles"] + self.correct = random_question["correct_answer"] - riddle_embed = discord.Embed(title=question, description=description, colour=Colours.pink) + description = f"You have {TIMELIMIT} seconds before the first hint." - await ctx.send(embed=riddle_embed) - await asyncio.sleep(TIMELIMIT) + riddle_embed = discord.Embed(title=question, description=description, colour=Colours.pink) - hint_embed = discord.Embed( - title=f"Here's a hint: {hints[0]}!", - colour=Colours.pink - ) + await ctx.send(embed=riddle_embed) + await asyncio.sleep(TIMELIMIT) - await ctx.send(embed=hint_embed) - await asyncio.sleep(TIMELIMIT) + hint_embed = discord.Embed( + title=f"Here's a hint: {hints[0]}!", + colour=Colours.pink + ) - hint_embed = discord.Embed( - title=f"Here's a hint: {hints[1]}!", - colour=Colours.pink - ) + await ctx.send(embed=hint_embed) + await asyncio.sleep(TIMELIMIT) - await ctx.send(embed=hint_embed) - await asyncio.sleep(TIMELIMIT) + hint_embed = discord.Embed( + title=f"Here's a hint: {hints[1]}!", + colour=Colours.pink + ) - if self.winners: - win_list = " ".join(self.winners) - content = f"Well done {win_list} for getting it right!" - self.winners = [] - else: - content = "Nobody got it right..." + await ctx.send(embed=hint_embed) + await asyncio.sleep(TIMELIMIT) - answer_embed = discord.Embed( - title=f"The answer is: {self.correct}!", - colour=Colours.pink - ) + if self.winners: + win_list = " ".join(self.winners) + content = f"Well done {win_list} for getting it right!" + else: + content = "Nobody got it right..." - await ctx.send(content, embed=answer_embed) + answer_embed = discord.Embed( + title=f"The answer is: {self.correct}!", + colour=Colours.pink + ) - self.current_channel = None - else: - await ctx.send(f"A riddle is already being solved in {self.current_channel.mention}!") + await ctx.send(content, embed=answer_embed) + + self.winners = [] + self.current_channel = None @commands.Cog.listener() async def on_message(self, message): """If a non-bot user enters a correct answer, their username gets added to self.winners""" - if self.current_channel == message.channel: - if self.bot.user != message.author: - if message.content.lower() == self.correct.lower(): - self.winners.append(message.author.mention) + if self.current_channel != message.channel: + return + + if self.bot.user == message.author: + return + + if message.content.lower() == self.correct.lower(): + self.winners.append(message.author.mention) def setup(bot): diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 13665782..2a31a2e1 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -3,6 +3,8 @@ import logging import discord from discord.ext import commands +from bot.constants import Colours + log = logging.getLogger(__name__) @@ -15,29 +17,30 @@ class Issues(commands.Cog): @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/{number}" + api_url = f"https://api.github.com/repos/{user}/{repository}/issues/{number}" failed_status = { 404: f"Issue #{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: + async with self.bot.http_session.get(api_url) as r: json_data = await r.json() response_code = r.status if response_code in failed_status: return await ctx.send(failed_status[response_code]) - issue_embed = discord.Embed(colour=0x00ff37) - issue_embed.add_field(name="Repository", value=f"[{user}/{repository}](json_data['repository_url'])", inline=False) + repo_url = f"https://github.com/{user}/{repository}" + issue_embed = discord.Embed(colour=Colours.bright_green) + issue_embed.add_field(name="Repository", value=f"[{user}/{repository}]({repo_url})", inline=False) issue_embed.add_field(name="Issue Number", value=f"#{number}", inline=False) issue_embed.add_field(name="Status", value=json_data["state"].title()) - issue_embed.add_field(name="Link", value=json_data["url"], inline=False) + issue_embed.add_field(name="Link", value=json_data["html_url"], inline=False) description = json_data["body"] if len(description) > 1024: - truncation_message = "** ...\n\nContent truncated, please follow the link for more!**" - description = f"{description[:1024 - len(truncation_message)]}{truncation_message}" + placeholder = " [...]" + description = f"{description[:1024 - len(placeholder)]}{placeholder}" issue_embed.add_field(name="Description", value=description, inline=False) -- cgit v1.2.3 From 33397fa41d97550d46d73157327b5e8706d6b15a Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 1 Aug 2019 01:48:10 +0200 Subject: Untested retro gaming yolo commit. Adds the Wildcard season. --- bot/seasons/season.py | 2 +- bot/seasons/wildcard/__init__.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 bot/seasons/wildcard/__init__.py (limited to 'bot') diff --git a/bot/seasons/season.py b/bot/seasons/season.py index 71324127..3b623040 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -270,7 +270,7 @@ class SeasonBase: It will skip the announcement if the current active season is the "evergreen" default season. """ # Don't actually announce if reverting to normal season - if self.name == "evergreen": + if self.name in ("evergreen", "wildcard"): log.debug(f"Season Changed: {self.name}") return diff --git a/bot/seasons/wildcard/__init__.py b/bot/seasons/wildcard/__init__.py new file mode 100644 index 00000000..c0b041c5 --- /dev/null +++ b/bot/seasons/wildcard/__init__.py @@ -0,0 +1,33 @@ +from bot.constants import Colours +from bot.seasons import SeasonBase + + +class Wildcard(SeasonBase): + """ + For the month of August, the season is a Wildcard. + That means it changes every year. + + This docstring will not be used for announcements. + Instead, we'll do the announcement manually, since + it will change every year. + + This class needs slight changes every year, + such as the bot_name, bot_icon and icon. + + IMPORTANT: DO NOT ADD ANY FEATURES TO THIS FOLDER. + ALL WILDCARD FEATURES SHOULD BE ADDED + TO THE EVERGREEN FOLDER! + """ + + name = "wildcard" + bot_name = "RetroBot" + + # Duration of season + start_date = "01/07" + end_date = "31/07" + + # Season logo + bot_icon = "/logos/logo_seasonal/retro_gaming/logo_8bit_indexed_504.png" + icon = ( + "/logos/logo_seasonal/retro_gaming_animated/logo_spin_plain/logo_spin_plain_504.gif", + ) -- cgit v1.2.3 From 23528f536fe58da5cb755579dd4eaf8f2b79a82a Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 1 Aug 2019 01:51:06 +0200 Subject: Fix dates and linter complaint --- bot/seasons/wildcard/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/wildcard/__init__.py b/bot/seasons/wildcard/__init__.py index c0b041c5..5713c923 100644 --- a/bot/seasons/wildcard/__init__.py +++ b/bot/seasons/wildcard/__init__.py @@ -1,4 +1,3 @@ -from bot.constants import Colours from bot.seasons import SeasonBase @@ -23,8 +22,8 @@ class Wildcard(SeasonBase): bot_name = "RetroBot" # Duration of season - start_date = "01/07" - end_date = "31/07" + start_date = "01/08" + end_date = "01/09" # Season logo bot_icon = "/logos/logo_seasonal/retro_gaming/logo_8bit_indexed_504.png" -- cgit v1.2.3 From 47a1fcc0c4ba79e9a8f4c168b0e607ce091acfa0 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 1 Aug 2019 01:54:40 +0200 Subject: aghhh I suck at programming --- bot/seasons/wildcard/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/wildcard/__init__.py b/bot/seasons/wildcard/__init__.py index 5713c923..354e979d 100644 --- a/bot/seasons/wildcard/__init__.py +++ b/bot/seasons/wildcard/__init__.py @@ -4,7 +4,6 @@ from bot.seasons import SeasonBase class Wildcard(SeasonBase): """ For the month of August, the season is a Wildcard. - That means it changes every year. This docstring will not be used for announcements. Instead, we'll do the announcement manually, since -- cgit v1.2.3 From 1304806244caaba179449053b9d83985e5e44c4f Mon Sep 17 00:00:00 2001 From: Suhail Date: Sat, 3 Aug 2019 23:19:40 +0100 Subject: 8bitify --- bot/seasons/evergreen/8bitify.py | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 bot/seasons/evergreen/8bitify.py (limited to 'bot') diff --git a/bot/seasons/evergreen/8bitify.py b/bot/seasons/evergreen/8bitify.py new file mode 100644 index 00000000..ae732fe5 --- /dev/null +++ b/bot/seasons/evergreen/8bitify.py @@ -0,0 +1,54 @@ +from io import BytesIO + +import discord +from PIL import Image +from discord.ext import commands + + +class EightBitify(commands.Cog): + """Make your avatar 8bit!""" + + def __init__(self, bot): + self.bot = bot + + @staticmethod + def pixelate(image: Image): + """Takes an image and pixelates it""" + return image.resize((32, 32)).resize((1024, 1024)) + + @staticmethod + def quantize(image: Image): + """Reduces colour palette to 256 colours""" + return image.quantize(colors=32) + + @commands.command(name="8bitify") + async def eightbit_command(self, ctx): + """Pixelates your avatar and changes the palette to an 8bit one""" + async with ctx.typing(): + image_bytes = await ctx.author.avatar_url.read() + avatar = Image.open(BytesIO(image_bytes)) + avatar = avatar.convert("RGBA").resize((1024, 1024)) + + eightbit = self.pixelate(avatar) + eightbit = self.quantize(eightbit) + + bufferedio = BytesIO() + eightbit.save(bufferedio, format="PNG") + bufferedio.seek(0) + + file = discord.File(bufferedio, filename="8bitavatar.png") + + embed = discord.Embed( + name="You're 8-bit avatar", + description='Here is your avatar. I think it looks all cool and "retro"' + ) + + embed.set_image(url="attachment://8bitavatar.png") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + + await ctx.send(file=file, embed=embed) + + +def setup(bot): + """Cog load.""" + bot.add_cog(EightBitify(bot)) -- cgit v1.2.3 From 89184caa8aa236acecc34be251423f10e84ecf0a Mon Sep 17 00:00:00 2001 From: Suhail6inkling <38522108+Suhail6inkling@users.noreply.github.com> Date: Sun, 4 Aug 2019 00:23:54 +0100 Subject: Add annotations Co-Authored-By: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> --- bot/seasons/evergreen/8bitify.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/8bitify.py b/bot/seasons/evergreen/8bitify.py index ae732fe5..5ad68036 100644 --- a/bot/seasons/evergreen/8bitify.py +++ b/bot/seasons/evergreen/8bitify.py @@ -8,21 +8,21 @@ from discord.ext import commands class EightBitify(commands.Cog): """Make your avatar 8bit!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot @staticmethod - def pixelate(image: Image): + def pixelate(image: Image) -> Image: """Takes an image and pixelates it""" return image.resize((32, 32)).resize((1024, 1024)) @staticmethod - def quantize(image: Image): + def quantize(image: Image) -> Image: """Reduces colour palette to 256 colours""" return image.quantize(colors=32) @commands.command(name="8bitify") - async def eightbit_command(self, ctx): + async def eightbit_command(self, ctx: commands.Context) -> None: """Pixelates your avatar and changes the palette to an 8bit one""" async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() @@ -49,6 +49,6 @@ class EightBitify(commands.Cog): await ctx.send(file=file, embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog load.""" bot.add_cog(EightBitify(bot)) -- cgit v1.2.3 From 245f7e6b3f668da20047d8a7007a68083ec6418f Mon Sep 17 00:00:00 2001 From: Suhail6inkling <38522108+Suhail6inkling@users.noreply.github.com> Date: Sun, 4 Aug 2019 05:03:07 +0100 Subject: Apply sugge Co-Authored-By: kosayoda --- bot/seasons/evergreen/8bitify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/8bitify.py b/bot/seasons/evergreen/8bitify.py index 5ad68036..54db71db 100644 --- a/bot/seasons/evergreen/8bitify.py +++ b/bot/seasons/evergreen/8bitify.py @@ -39,7 +39,7 @@ class EightBitify(commands.Cog): file = discord.File(bufferedio, filename="8bitavatar.png") embed = discord.Embed( - name="You're 8-bit avatar", + title="Your 8-bit avatar", description='Here is your avatar. I think it looks all cool and "retro"' ) -- cgit v1.2.3 From 1b481b7c91ff1e329f7419901b939674db969c7c Mon Sep 17 00:00:00 2001 From: sco1 Date: Sun, 4 Aug 2019 10:31:12 -0400 Subject: Add new Seasonalbot channel IDs to constants Change Show Your Projects channel to an env var --- bot/constants.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index be53a764..c9b8f6b5 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -46,10 +46,12 @@ class Channels(NamedTuple): python = 267624335836053506 reddit = 458224812528238616 seasonalbot_chat = int(environ.get('CHANNEL_SEASONALBOT_CHAT', 542272993192050698)) + seasonalbot_commands = int(environ.get('CHANNEL_SEASONALBOT_COMMANDS', 607247579608121354)) + seasonalbot_voice = int(environ.get('CHANNEL_SEASONALBOT_VOICE', 606259004230074378)) staff_lounge = 464905259261755392 verification = 352442727016693763 python_discussion = 267624335836053506 - show_your_projects = 303934982764625920 + show_your_projects = int(environ.get('CHANNEL_SHOW_YOUR_PROJECTS', 303934982764625920)) show_your_projects_discussion = 360148304664723466 -- cgit v1.2.3 From 51a204e9d79b43f8142e01a06c2f676463165f3d Mon Sep 17 00:00:00 2001 From: sco1 Date: Sun, 4 Aug 2019 10:33:53 -0400 Subject: Unify constants file quotation use --- bot/constants.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index c9b8f6b5..8902d918 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -23,12 +23,12 @@ class AdventOfCode: class Channels(NamedTuple): admins = 365960823622991872 - announcements = int(environ.get('CHANNEL_ANNOUNCEMENTS', 354619224620138496)) + announcements = int(environ.get("CHANNEL_ANNOUNCEMENTS", 354619224620138496)) big_brother_logs = 468507907357409333 bot = 267659945086812160 checkpoint_test = 422077681434099723 devalerts = 460181980097675264 - devlog = int(environ.get('CHANNEL_DEVLOG', 548438471685963776)) + devlog = int(environ.get("CHANNEL_DEVLOG", 548438471685963776)) devtest = 414574275865870337 help_0 = 303906576991780866 help_1 = 303906556754395136 @@ -45,22 +45,22 @@ class Channels(NamedTuple): off_topic_2 = 463035268514185226 python = 267624335836053506 reddit = 458224812528238616 - seasonalbot_chat = int(environ.get('CHANNEL_SEASONALBOT_CHAT', 542272993192050698)) - seasonalbot_commands = int(environ.get('CHANNEL_SEASONALBOT_COMMANDS', 607247579608121354)) - seasonalbot_voice = int(environ.get('CHANNEL_SEASONALBOT_VOICE', 606259004230074378)) + seasonalbot_chat = int(environ.get("CHANNEL_SEASONALBOT_CHAT", 542272993192050698)) + seasonalbot_commands = int(environ.get("CHANNEL_SEASONALBOT_COMMANDS", 607247579608121354)) + seasonalbot_voice = int(environ.get("CHANNEL_SEASONALBOT_VOICE", 606259004230074378)) staff_lounge = 464905259261755392 verification = 352442727016693763 python_discussion = 267624335836053506 - show_your_projects = int(environ.get('CHANNEL_SHOW_YOUR_PROJECTS', 303934982764625920)) + show_your_projects = int(environ.get("CHANNEL_SHOW_YOUR_PROJECTS", 303934982764625920)) show_your_projects_discussion = 360148304664723466 class Client(NamedTuple): - guild = int(environ.get('SEASONALBOT_GUILD', 267624335836053506)) + guild = int(environ.get("SEASONALBOT_GUILD", 267624335836053506)) prefix = environ.get("PREFIX", ".") - token = environ.get('SEASONALBOT_TOKEN') - debug = environ.get('SEASONALBOT_DEBUG', '').lower() == 'true' - season_override = environ.get('SEASON_OVERRIDE') + token = environ.get("SEASONALBOT_TOKEN") + debug = environ.get("SEASONALBOT_DEBUG", "").lower() == "true" + season_override = environ.get("SEASON_OVERRIDE") class Colours: @@ -96,7 +96,7 @@ class Hacktoberfest(NamedTuple): class Roles(NamedTuple): - admin = int(environ.get('SEASONALBOT_ADMIN_ROLE_ID', 267628507062992896)) + admin = int(environ.get("SEASONALBOT_ADMIN_ROLE_ID", 267628507062992896)) announcements = 463658397560995840 champion = 430492892331769857 contributor = 295488872404484098 -- cgit v1.2.3 From 100214ce76a59128abfb81eae629f05334399533 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 5 Aug 2019 10:54:26 +0200 Subject: Making sure the image BytesIO buffer gets processed correctly by using an existing utility function for it --- bot/seasons/evergreen/snakes/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index 88fb2032..5d3b0dee 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -593,9 +593,8 @@ class SnakeAndLaddersGame: y_offset -= BOARD_PLAYER_SIZE * math.floor(i / player_row_size) board_img.paste(self.avatar_images[player.id], box=(x_offset, y_offset)) - stream = io.BytesIO() - board_img.save(stream, format='JPEG') - board_file = File(stream.getvalue(), filename='Board.jpg') + + board_file = File(frame_to_png_bytes(board_img), filename='Board.jpg') player_list = '\n'.join((user.mention + ": Tile " + str(self.player_tiles[user.id])) for user in self.players) # Store and send new messages -- cgit v1.2.3 From eb10679be3e53f73cf8b34789971000ec597c10a Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 5 Aug 2019 21:32:31 +0800 Subject: Move bot out of constants.py to bot.py --- bot/__main__.py | 3 ++- bot/bot.py | 9 ++++++--- bot/constants.py | 7 +------ bot/seasons/easter/egg_hunt/cog.py | 3 ++- bot/seasons/easter/egg_hunt/constants.py | 3 ++- bot/seasons/season.py | 3 ++- 6 files changed, 15 insertions(+), 13 deletions(-) (limited to 'bot') diff --git a/bot/__main__.py b/bot/__main__.py index a3b68ec1..416bca35 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,6 +1,7 @@ import logging -from bot.constants import Client, bot +from bot.bot import bot +from bot.constants import Client log = logging.getLogger(__name__) diff --git a/bot/bot.py b/bot/bot.py index 24e919f2..86028838 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -7,11 +7,11 @@ from aiohttp import AsyncResolver, ClientSession, TCPConnector from discord import Embed from discord.ext import commands -from bot import constants +from bot.constants import Channels, Client log = logging.getLogger(__name__) -__all__ = ('SeasonalBot',) +__all__ = ('SeasonalBot', 'bot') class SeasonalBot(commands.Bot): @@ -42,7 +42,7 @@ class SeasonalBot(commands.Bot): async def send_log(self, title: str, details: str = None, *, icon: str = None): """Send an embed message to the devlog channel.""" - devlog = self.get_channel(constants.Channels.devlog) + devlog = self.get_channel(Channels.devlog) if not devlog: log.warning("Log failed to send. Devlog channel not found.") @@ -62,3 +62,6 @@ class SeasonalBot(commands.Bot): context.command.reset_cooldown(context) else: await super().on_command_error(context, exception) + + +bot = SeasonalBot(command_prefix=Client.prefix) diff --git a/bot/constants.py b/bot/constants.py index 8902d918..f46f21d6 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -2,11 +2,9 @@ import logging from os import environ from typing import NamedTuple -from bot.bot import SeasonalBot - __all__ = ( "AdventOfCode", "Channels", "Client", "Colours", "Emojis", "Hacktoberfest", "Roles", - "Tokens", "ERROR_REPLIES", "bot" + "Tokens", "ERROR_REPLIES", ) log = logging.getLogger(__name__) @@ -130,6 +128,3 @@ ERROR_REPLIES = [ "Noooooo!!", "I can't believe you've done this", ] - - -bot = SeasonalBot(command_prefix=Client.prefix) diff --git a/bot/seasons/easter/egg_hunt/cog.py b/bot/seasons/easter/egg_hunt/cog.py index 30fd3284..a4ad27df 100644 --- a/bot/seasons/easter/egg_hunt/cog.py +++ b/bot/seasons/easter/egg_hunt/cog.py @@ -9,7 +9,8 @@ from pathlib import Path import discord from discord.ext import commands -from bot.constants import Channels, Client, Roles as MainRoles, bot +from bot.bot import bot +from bot.constants import Channels, Client, Roles as MainRoles from bot.decorators import with_role from .constants import Colours, EggHuntSettings, Emoji, Roles diff --git a/bot/seasons/easter/egg_hunt/constants.py b/bot/seasons/easter/egg_hunt/constants.py index c7d9818b..02f6e9f2 100644 --- a/bot/seasons/easter/egg_hunt/constants.py +++ b/bot/seasons/easter/egg_hunt/constants.py @@ -2,7 +2,8 @@ import os from discord import Colour -from bot.constants import Channels, Client, bot +from bot.bot import bot +from bot.constants import Channels, Client GUILD = bot.get_guild(Client.guild) diff --git a/bot/seasons/season.py b/bot/seasons/season.py index 3b623040..c88ef2a7 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -12,7 +12,8 @@ import async_timeout import discord from discord.ext import commands -from bot.constants import Channels, Client, Roles, bot +from bot.bot import bot +from bot.constants import Channels, Client, Roles from bot.decorators import with_role log = logging.getLogger(__name__) -- cgit v1.2.3 From b5ceff1f6cf68c9e81dcd336dcea3fa8c67ca3cf Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 5 Aug 2019 23:30:02 +0800 Subject: Add constant groups to `constants.py` The following groups are added: STAFF_ROLES, MODERATION_ROLES, WHITELISTED_CHANNELS, POSITIVE_REPLIES, NEGATIVE_REPLIES --- bot/constants.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index f46f21d6..dbf35754 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -3,8 +3,9 @@ from os import environ from typing import NamedTuple __all__ = ( - "AdventOfCode", "Channels", "Client", "Colours", "Emojis", "Hacktoberfest", "Roles", - "Tokens", "ERROR_REPLIES", + "AdventOfCode", "Channels", "Client", "Colours", "Emojis", "Hacktoberfest", "Roles", "Tokens", + "WHITELISTED_CHANNELS", "STAFF_ROLES", "MODERATION_ROLES", + "POSITIVE_REPLIES", "NEGATIVE_REPLIES", "ERROR_REPLIES", ) log = logging.getLogger(__name__) @@ -116,6 +117,58 @@ class Tokens(NamedTuple): youtube = environ.get("YOUTUBE_API_KEY") +# Default role combinations +MODERATION_ROLES = Roles.moderator, Roles.admin, Roles.owner +STAFF_ROLES = Roles.helpers, Roles.moderator, Roles.admin, Roles.owner + +# Whitelisted channels +WHITELISTED_CHANNELS = ( + Channels.bot, Channels.seasonalbot_commands, + Channels.off_topic_0, Channels.off_topic_1, Channels.off_topic_2, + Channels.devtest, +) + +# Bot replies +NEGATIVE_REPLIES = [ + "Noooooo!!", + "Nope.", + "I'm sorry Dave, I'm afraid I can't do that.", + "I don't think so.", + "Not gonna happen.", + "Out of the question.", + "Huh? No.", + "Nah.", + "Naw.", + "Not likely.", + "No way, José.", + "Not in a million years.", + "Fat chance.", + "Certainly not.", + "NEGATORY.", + "Nuh-uh.", + "Not in my house!", +] + +POSITIVE_REPLIES = [ + "Yep.", + "Absolutely!", + "Can do!", + "Affirmative!", + "Yeah okay.", + "Sure.", + "Sure thing!", + "You're the boss!", + "Okay.", + "No problem.", + "I got you.", + "Alright.", + "You got it!", + "ROGER THAT", + "Of course!", + "Aye aye, cap'n!", + "I'll allow it.", +] + ERROR_REPLIES = [ "Please don't do that.", "You have to stop.", -- cgit v1.2.3 From 999aac742abe475048b4322ba6c849b0dc1d82df Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 5 Aug 2019 23:41:55 +0800 Subject: Split in_channel's predicate and check, add bypass_roles functionality Separate the predicate function from `commands.check` to allow the predicate check to be added to the bot. --- bot/decorators.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 8 deletions(-) (limited to 'bot') diff --git a/bot/decorators.py b/bot/decorators.py index dfe80e5c..f556660e 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -1,18 +1,25 @@ import logging import random +import typing from asyncio import Lock from functools import wraps from weakref import WeakValueDictionary from discord import Colour, Embed from discord.ext import commands -from discord.ext.commands import Context +from discord.ext.commands import CheckFailure, Context from bot.constants import ERROR_REPLIES log = logging.getLogger(__name__) +class InChannelCheckFailure(CheckFailure): + """Check failure when the user runs a command in a non-whitelisted channel.""" + + pass + + def with_role(*role_ids: int): """Check to see whether the invoking user has any of the roles specified in role_ids.""" async def predicate(ctx: Context): @@ -48,14 +55,53 @@ def without_role(*role_ids: int): return commands.check(predicate) -def in_channel(channel_id): - """Check that the command invocation is in the channel specified by channel_id.""" - async def predicate(ctx: Context): - check = ctx.channel.id == channel_id +def in_channel_check(*channels: int, bypass_roles: typing.Container[int] = None) -> typing.Callable[[Context], bool]: + """Checks that the message is in a whitelisted channel or optionally has a bypass role.""" + def predicate(ctx: Context) -> bool: + if not ctx.guild: + log.debug(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM.") + return True + + if ctx.channel.id in channels: + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The command was used in a whitelisted channel.") + return True + + if hasattr(ctx.command.callback, "in_channel_override"): + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The command was not used in a whitelisted channel, " + f"but the command was whitelisted to bypass the in_channel check.") + return True + + if bypass_roles: + if any(r.id in bypass_roles for r in ctx.author.roles): + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The command was not used in a whitelisted channel, " + f"but the author had a role to bypass the in_channel check.") + return True + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The result of the in_channel check was {check}.") - return check - return commands.check(predicate) + f"The in_channel check failed.") + + channels_str = ', '.join(f"<#{c_id}>" for c_id in channels) + raise InChannelCheckFailure( + f"Sorry, but you may only use this command within {channels_str}." + ) + + return predicate + + +in_channel = commands.check(in_channel_check) + + +def override_in_channel(func: typing.Callable) -> typing.Callable: + """ + Set command callback attribute for detection in `in_channel_check`. + + This decorator has to go before (below) below the `command` decorator. + """ + func.in_channel_override = True + return func def locked(): -- cgit v1.2.3 From 9bd8ac2ea92ee99b486b3f3bdf8872c7bfbbc38b Mon Sep 17 00:00:00 2001 From: kosayoda Date: Tue, 6 Aug 2019 00:12:17 +0800 Subject: Add global check to SeasonalBot --- bot/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/__main__.py b/bot/__main__.py index 416bca35..9dc0b173 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,9 +1,11 @@ import logging from bot.bot import bot -from bot.constants import Client +from bot.constants import Client, STAFF_ROLES, WHITELISTED_CHANNELS +from bot.decorators import in_channel_check log = logging.getLogger(__name__) +bot.add_check(in_channel_check(*WHITELISTED_CHANNELS, bypass_roles=STAFF_ROLES)) bot.load_extension("bot.seasons") bot.run(Client.token) -- cgit v1.2.3 From ef95acda7c8511de52d125d6f355be89984b1bfe Mon Sep 17 00:00:00 2001 From: kosayoda Date: Tue, 6 Aug 2019 00:39:25 +0800 Subject: Implement error handling; add `in_channel` overrides to `!issue` and AoC commands --- bot/seasons/christmas/adventofcode.py | 2 ++ bot/seasons/evergreen/error_handler.py | 15 +++++++++++++++ bot/seasons/evergreen/issues.py | 2 ++ 3 files changed, 19 insertions(+) (limited to 'bot') diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index 08b07e83..a9e72805 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -14,6 +14,7 @@ from discord.ext import commands from pytz import timezone from bot.constants import AdventOfCode as AocConfig, Channels, Colours, Emojis, Tokens +from bot.decorators import override_in_channel log = logging.getLogger(__name__) @@ -125,6 +126,7 @@ class AdventOfCode(commands.Cog): self.status_task = asyncio.ensure_future(self.bot.loop.create_task(status_coro)) @commands.group(name="adventofcode", aliases=("aoc",), invoke_without_command=True) + @override_in_channel async def adventofcode_group(self, ctx: commands.Context): """All of the Advent of Code commands.""" await ctx.send_help(ctx.command) diff --git a/bot/seasons/evergreen/error_handler.py b/bot/seasons/evergreen/error_handler.py index f4457f8f..6690cf89 100644 --- a/bot/seasons/evergreen/error_handler.py +++ b/bot/seasons/evergreen/error_handler.py @@ -1,10 +1,15 @@ import logging import math +import random import sys import traceback +from discord import Colour, Embed from discord.ext import commands +from bot.constants import NEGATIVE_REPLIES +from bot.decorators import InChannelCheckFailure + log = logging.getLogger(__name__) @@ -34,6 +39,16 @@ class CommandErrorHandler(commands.Cog): error = getattr(error, 'original', error) + if isinstance(error, InChannelCheckFailure): + logging.debug( + f"{ctx.author} the command '{ctx.command}', but they did not have " + f"permissions to run commands in the channel {ctx.channel}!" + ) + embed = Embed(colour=Colour.red()) + embed.title = random.choice(NEGATIVE_REPLIES) + embed.description = str(error) + return await ctx.send(embed=embed) + if isinstance(error, commands.CommandNotFound): return logging.debug( f"{ctx.author} called '{ctx.message.content}' but no command was found." diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 2a31a2e1..f19a1129 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -4,6 +4,7 @@ import discord from discord.ext import commands from bot.constants import Colours +from bot.decorators import override_in_channel log = logging.getLogger(__name__) @@ -15,6 +16,7 @@ class Issues(commands.Cog): self.bot = bot @commands.command(aliases=("issues",)) + @override_in_channel async def issue(self, ctx, number: int, repository: str = "seasonalbot", user: str = "python-discord"): """Command to retrieve issues from a GitHub repository.""" api_url = f"https://api.github.com/repos/{user}/{repository}/issues/{number}" -- cgit v1.2.3 From fe7d6c6535ccfa756d255cd11e68157714992581 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Fri, 9 Aug 2019 17:23:31 +0200 Subject: added minesweeper cog --- bot/seasons/evergreen/minesweeper.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 bot/seasons/evergreen/minesweeper.py (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py new file mode 100644 index 00000000..e5276df5 --- /dev/null +++ b/bot/seasons/evergreen/minesweeper.py @@ -0,0 +1,13 @@ +from discord.ext import commands + + +class Minesweeper(commands.Cog): + """play a game of minesweeper""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + +def setup(bot: commands.Bot) -> None: + """Cog load.""" + bot.add_cog(Minesweeper(bot)) -- cgit v1.2.3 From f32fdda6bb95225ea536c07446a764e015145ca4 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Fri, 9 Aug 2019 17:36:56 +0200 Subject: added logic for when a user already have a game running --- bot/seasons/evergreen/minesweeper.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index e5276df5..3540df80 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -1,11 +1,30 @@ +import typing + +import discord from discord.ext import commands class Minesweeper(commands.Cog): - """play a game of minesweeper""" + """Play a game of minesweeper.""" def __init__(self, bot: commands.Bot) -> None: self.bot = bot + self.games: typing.Dict[discord.member, typing.Dict] = {} # Store the currently running games + + @commands.command(name="minesweeper") + async def minesweeper_command(self, ctx: commands.Context) -> None: + """Start a game of minesweeper.""" + if ctx.author in self.games.keys(): # Player is already playing + msg = await ctx.send(f"{ctx.author.mention} you already have a game running") + await msg.delete(delay=2) + await ctx.message.delete(delay=2) + return + + # Add game to list + + self.games[ctx.author] = { + + } def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From dc488fb963e680c9ba8cef8bbd4f0bd6f1c8ed32 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Fri, 9 Aug 2019 18:09:55 +0200 Subject: add generate_board (it might have a bug, will begone more apparent when adding formatting for discord.) --- bot/seasons/evergreen/minesweeper.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 3540df80..39428000 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -1,4 +1,5 @@ import typing +from random import random import discord from discord.ext import commands @@ -11,6 +12,29 @@ class Minesweeper(commands.Cog): self.bot = bot self.games: typing.Dict[discord.member, typing.Dict] = {} # Store the currently running games + @staticmethod + def is_bomb(cell: typing.Union[str, int]) -> int: + """Returns 1 if `cell` is a bomb if not 0""" + return 1 if cell == "bomb" else 0 + + def generate_board(self) -> typing.List[typing.List[typing.Union[str, int]]]: + """Generate a 2d array for the board.""" + board: typing.List[typing.List[typing.Union[str, int]]] = [ + ["bomb" if random() <= .2 else "number" for _ in range(10)] for _ in range(9)] + for y, row in enumerate(board): + for x, cell in enumerate(row): + if cell == "number": + # calculate bombs near it + to_check = [] + for xt in [x - 1, x, x + 1]: + for yt in [y - 1, y, y + 1]: + if xt != -1 and xt != 10 and yt != -1 and yt != 9: + to_check.append(board[yt][xt]) + + bombs = sum(map(self.is_bomb, to_check)) + board[y][x] = bombs + return board + @commands.command(name="minesweeper") async def minesweeper_command(self, ctx: commands.Context) -> None: """Start a game of minesweeper.""" @@ -21,7 +45,7 @@ class Minesweeper(commands.Cog): return # Add game to list - + await ctx.send(str(self.generate_board())) self.games[ctx.author] = { } -- cgit v1.2.3 From 51a3b6ddb044548519fcf4bb53c236ef3172ecc9 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Fri, 9 Aug 2019 19:00:10 +0200 Subject: added formating for discord (there was no bug) --- bot/seasons/evergreen/minesweeper.py | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 39428000..64499111 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -20,7 +20,7 @@ class Minesweeper(commands.Cog): def generate_board(self) -> typing.List[typing.List[typing.Union[str, int]]]: """Generate a 2d array for the board.""" board: typing.List[typing.List[typing.Union[str, int]]] = [ - ["bomb" if random() <= .2 else "number" for _ in range(10)] for _ in range(9)] + ["bomb" if random() <= .2 else "number" for _ in range(10)] for _ in range(10)] for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "number": @@ -28,13 +28,42 @@ class Minesweeper(commands.Cog): to_check = [] for xt in [x - 1, x, x + 1]: for yt in [y - 1, y, y + 1]: - if xt != -1 and xt != 10 and yt != -1 and yt != 9: + if xt != -1 and xt != 10 and yt != -1 and yt != 10: to_check.append(board[yt][xt]) bombs = sum(map(self.is_bomb, to_check)) board[y][x] = bombs return board + @staticmethod + def format_for_discord(board: typing.List[typing.List[typing.Union[str, int]]]): + """Format the board to a string for discord.""" + mapping = { + 0: ":stop_button:", + 1: ":one:", + 2: ":two:", + 3: ":three:", + 4: ":four:", + 5: ":five:", + 6: ":six:", + 7: ":seven:", + 8: ":eight:", + 9: ":nine:", + 10: ":keycap_ten:", + "bomb": ":bomb:" + } + + discord_msg = ":stop_button: :one::two::three::four::five::six::seven::eight::nine::keycap_ten:\n\n" + rows: typing.List[str] = [] + for row_number, row in enumerate(board): + new_row = mapping[row_number + 1] + " " + for cell in row: + new_row += mapping[cell] + rows.append(new_row) + + discord_msg += "\n".join(rows) + return discord_msg + @commands.command(name="minesweeper") async def minesweeper_command(self, ctx: commands.Context) -> None: """Start a game of minesweeper.""" @@ -45,9 +74,10 @@ class Minesweeper(commands.Cog): return # Add game to list - await ctx.send(str(self.generate_board())) + board = self.generate_board() + await ctx.send(self.format_for_discord(board)) self.games[ctx.author] = { - + "board": board } -- cgit v1.2.3 From 794a08ace5618c5d756e9caf105e234e1e64ca0e Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Fri, 9 Aug 2019 21:41:12 +0200 Subject: added extra stuff to `format_for_discord` and also added dm and channel message --- bot/seasons/evergreen/minesweeper.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 64499111..543eab39 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -36,7 +36,7 @@ class Minesweeper(commands.Cog): return board @staticmethod - def format_for_discord(board: typing.List[typing.List[typing.Union[str, int]]]): + def format_for_discord(board: typing.List[typing.List[typing.Union[str, int]]]) -> str: """Format the board to a string for discord.""" mapping = { 0: ":stop_button:", @@ -50,7 +50,9 @@ class Minesweeper(commands.Cog): 8: ":eight:", 9: ":nine:", 10: ":keycap_ten:", - "bomb": ":bomb:" + "bomb": ":bomb:", + "hidden": ":grey_question:", + "flag": ":triangular_flag_on_post:" } discord_msg = ":stop_button: :one::two::three::four::five::six::seven::eight::nine::keycap_ten:\n\n" @@ -75,9 +77,19 @@ class Minesweeper(commands.Cog): # Add game to list board = self.generate_board() - await ctx.send(self.format_for_discord(board)) + reveled_board = [["hidden" for _ in range(10)]for _ in range(10)] + + await ctx.send(f"{ctx.author.mention} is playing minesweeper") + chat_msg = await ctx.send(self.format_for_discord(reveled_board)) + + await ctx.author.send("play by typing: `.reveal x y` and `.flag x y`") + dm_msg = await ctx.author.send(self.format_for_discord(reveled_board)) + self.games[ctx.author] = { - "board": board + "board": board, + "reveled": reveled_board, + "dm_msg": dm_msg, + "chat_msg": chat_msg } -- cgit v1.2.3 From c467362ea2c8a675ca67f7b612015bfef3dba2aa Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Fri, 9 Aug 2019 15:56:29 -0400 Subject: added speedrun cog and its resource --- .gitignore | 5 ++++- bot/resources/evergreen/speedrun_links.json | 20 +++++++++++++++++ bot/seasons/evergreen/speedrun.py | 34 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 bot/resources/evergreen/speedrun_links.json create mode 100644 bot/seasons/evergreen/speedrun.py (limited to 'bot') diff --git a/.gitignore b/.gitignore index 8f848483..8a21b668 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,7 @@ venv.bak/ # jetbrains .idea/ -.DS_Store \ No newline at end of file +.DS_Store + +# vscode +.vscode/ diff --git a/bot/resources/evergreen/speedrun_links.json b/bot/resources/evergreen/speedrun_links.json new file mode 100644 index 00000000..1ae1e325 --- /dev/null +++ b/bot/resources/evergreen/speedrun_links.json @@ -0,0 +1,20 @@ +{ + "links": [ + "https://www.youtube.com/watch?v=jNE28SDXdyQ", + "https://www.youtube.com/watch?v=iI8Giq7zQDk", + "https://www.youtube.com/watch?v=VqNnkqQgFbc", + "https://www.youtube.com/watch?v=Gum4GI2Jr0s", + "https://www.youtube.com/watch?v=5YHjHzHJKkU", + "https://www.youtube.com/watch?v=X0pJSTy4tJI", + "https://www.youtube.com/watch?v=aVFq0H6D6_M", + "https://www.youtube.com/watch?v=1O6LuJbEbSI", + "https://www.youtube.com/watch?v=Bgh30BiWG58", + "https://www.youtube.com/watch?v=wwvgAAvhxM8", + "https://www.youtube.com/watch?v=0TWQr0_fi80", + "https://www.youtube.com/watch?v=hatqZby-0to", + "https://www.youtube.com/watch?v=X0pJSTy4tJI", + "https://www.youtube.com/watch?v=tmnMq2Hw72w", + "https://www.youtube.com/watch?v=UTkyeTCAucA", + "https://www.youtube.com/watch?v=67kQ3l-1qMs" + ] +} \ No newline at end of file diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py new file mode 100644 index 00000000..8bbbfc83 --- /dev/null +++ b/bot/seasons/evergreen/speedrun.py @@ -0,0 +1,34 @@ +import json +import logging +from random import choice +from discord.ext import commands +from pathlib import Path + + +log = logging.getLogger(__name__) + + +class Speedrun(commands.Cog): + """ + A command that will link a random speedrun video from youtube to Discord. + """ + + def __init__(self, bot): + self.bot = bot + + @commands.command(name="speedrun") + async def get_speedrun(self, ctx): + """ + Sends a link to Discord of a random speedrun from youtube. + Utilizes speedrun_links.json to find links. + """ + + with open(Path('bot/resources/evergreen/speedrun_links.json')) as file: + data = json.load(file) + links = data['links'] + await ctx.send(choice(links)) + + +def setup(bot): + bot.add_cog(Speedrun(bot)) + log.info("Speedrun cog loaded") -- cgit v1.2.3 From 654bf0794ee56653739f468101cd0595dbca2495 Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Fri, 9 Aug 2019 16:17:01 -0400 Subject: Cleaned up lint issues with speedrun --- bot/seasons/evergreen/speedrun.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index 8bbbfc83..e3ecdca2 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -1,17 +1,17 @@ import json import logging +from pathlib import Path from random import choice + + from discord.ext import commands -from pathlib import Path log = logging.getLogger(__name__) class Speedrun(commands.Cog): - """ - A command that will link a random speedrun video from youtube to Discord. - """ + """A command that will link a random speedrun video from youtube to Discord.""" def __init__(self, bot): self.bot = bot @@ -20,9 +20,9 @@ class Speedrun(commands.Cog): async def get_speedrun(self, ctx): """ Sends a link to Discord of a random speedrun from youtube. + Utilizes speedrun_links.json to find links. """ - with open(Path('bot/resources/evergreen/speedrun_links.json')) as file: data = json.load(file) links = data['links'] @@ -30,5 +30,6 @@ class Speedrun(commands.Cog): def setup(bot): + """Cog load""" bot.add_cog(Speedrun(bot)) log.info("Speedrun cog loaded") -- cgit v1.2.3 From 4495cc3bf19fe67f5ae52d632324f3631a35d0b1 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Fri, 9 Aug 2019 23:49:45 +0200 Subject: changed format_for_discord, as suggested by Ava, to now have letters at the top instead of numbers --- bot/seasons/evergreen/minesweeper.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 543eab39..85339865 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -55,7 +55,9 @@ class Minesweeper(commands.Cog): "flag": ":triangular_flag_on_post:" } - discord_msg = ":stop_button: :one::two::three::four::five::six::seven::eight::nine::keycap_ten:\n\n" + discord_msg = ":stop_button: :regional_indicator_a::regional_indicator_b::regional_indicator_c:" \ + ":regional_indicator_d::regional_indicator_e::regional_indicator_f::regional_indicator_g:" \ + ":regional_indicator_h::regional_indicator_i::regional_indicator_j:\n\n" rows: typing.List[str] = [] for row_number, row in enumerate(board): new_row = mapping[row_number + 1] + " " @@ -77,12 +79,12 @@ class Minesweeper(commands.Cog): # Add game to list board = self.generate_board() - reveled_board = [["hidden" for _ in range(10)]for _ in range(10)] + reveled_board = [["hidden" for _ in range(10)] for _ in range(10)] await ctx.send(f"{ctx.author.mention} is playing minesweeper") chat_msg = await ctx.send(self.format_for_discord(reveled_board)) - await ctx.author.send("play by typing: `.reveal x y` and `.flag x y`") + await ctx.author.send("play by typing: `.reveal x y` or `.flag x y` \nclose the game with `.end`") dm_msg = await ctx.author.send(self.format_for_discord(reveled_board)) self.games[ctx.author] = { -- cgit v1.2.3 From b3e377cf105b1b1e9572c25aa186aa4d5caf57fe Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 00:31:31 +0200 Subject: added `.flag` command and a board reload function --- bot/seasons/evergreen/minesweeper.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 85339865..b05fc255 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -94,6 +94,32 @@ class Minesweeper(commands.Cog): "chat_msg": chat_msg } + @staticmethod + def get_cords(value1: str, value2: str) -> typing.Tuple[int, int]: + """Take in 2 values for the cords and turn them into numbers""" + if value1.isnumeric(): + return int(value1) - 1, ord(value2.lower()) - 97 + else: + return ord(value1.lower()) - 97, int(value2) - 1 + + async def reload_board(self, ctx: commands.Context) -> None: + """Update both playing boards.""" + game = self.games[ctx.author] + await game["dm_msg"].delete() + game["dm_msg"] = await ctx.author.send(self.format_for_discord(game["reveled"])) + await game["chat_msg"].edit(content=self.format_for_discord(game["reveled"])) + + @commands.dm_only() + @commands.command(name="flag") + async def flag_command(self, ctx: commands.Context, value1, value2) -> None: + """Place a flag on the board""" + x, y = self.get_cords(value1, value2) # ints + board = self.games[ctx.author]["reveled"] + if board[y][x] == "hidden": + board[y][x] = "flag" + + await self.reload_board(ctx) + def setup(bot: commands.Bot) -> None: """Cog load.""" -- cgit v1.2.3 From c65f2af7cdc4341e403c12bb639e43c3db18804a Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 01:46:18 +0200 Subject: added .reveal and won and lost commands --- bot/seasons/evergreen/minesweeper.py | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index b05fc255..39e84940 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -120,6 +120,59 @@ class Minesweeper(commands.Cog): await self.reload_board(ctx) + async def lost(self, ctx: commands.Context) -> None: + """The player lost the game""" + game = self.games[ctx.author] + game["reveled"] = game["board"] + await self.reload_board(ctx) + await ctx.author.send(":fire: You lost :fire: ") + await game["chat_msg"].channel.send(f":fire: {ctx.author.mention} just lost minesweeper :fire:") + del self.games[ctx.author] + print(self.games) + + async def won(self, ctx: commands.Context): + """The player won the game""" + game = self.games[ctx.author] + game["reveled"] = game["board"] + await self.reload_board(ctx) + await ctx.author.send(":tada: You won! :tada: ") + await game["chat_msg"].channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") + del self.games[ctx.author] + print(self.games) + + def reveal(self, reveled: typing.List, board: typing.List, x: int, y: int) -> None: + """Used when a 0 is encountered to do a flood fill""" + for x_ in [x - 1, x, x + 1]: + for y_ in [y - 1, y, y + 1]: + if x_ == -1 or x_ == 10 or y_ == -1 or y_ == 10 or reveled[y_][x_] != "hidden": + continue + reveled[y_][x_] = board[y_][x_] + if board[y_][x_] == 0: + self.reveal(reveled, board, x_, y_) + + @commands.dm_only() + @commands.command(name="reveal") + async def reveal_command(self, ctx: commands.Context, value1, value2): + """Reveal a cell""" + x, y = self.get_cords(value1, value2) + game = self.games[ctx.author] + reveled = game["reveled"] + board = game["board"] + reveled[y][x] = board[y][x] + if board[y][x] == "bomb": + await self.lost(ctx) + elif board[y][x] == 0: + self.reveal(reveled, board, x, y) + + # check if won + for x_ in range(10): + for y_ in range(10): + if not (reveled[y_][x_] == "hidden" and board[y_][x_] == "bomb"): + await self.won(ctx) + break + + await self.reload_board(ctx) + def setup(bot: commands.Bot) -> None: """Cog load.""" -- cgit v1.2.3 From 01d132dcd5cb2612ff1c7ccd586b959c9d51c312 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 01:58:01 +0200 Subject: added `.end` command --- bot/seasons/evergreen/minesweeper.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 39e84940..e67a043b 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -128,7 +128,6 @@ class Minesweeper(commands.Cog): await ctx.author.send(":fire: You lost :fire: ") await game["chat_msg"].channel.send(f":fire: {ctx.author.mention} just lost minesweeper :fire:") del self.games[ctx.author] - print(self.games) async def won(self, ctx: commands.Context): """The player won the game""" @@ -138,7 +137,6 @@ class Minesweeper(commands.Cog): await ctx.author.send(":tada: You won! :tada: ") await game["chat_msg"].channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") del self.games[ctx.author] - print(self.games) def reveal(self, reveled: typing.List, board: typing.List, x: int, y: int) -> None: """Used when a 0 is encountered to do a flood fill""" @@ -173,6 +171,17 @@ class Minesweeper(commands.Cog): await self.reload_board(ctx) + @commands.dm_only() + @commands.command(name="end") + async def end_command(self, ctx: commands.Context): + """End the current game""" + game = self.games[ctx.author] + game["reveled"] = game["board"] + await self.reload_board(ctx) + await ctx.author.send(":no_entry: you canceled the game :no_entry:") + await game["chat_msg"].channel.send(f"{ctx.author.mention} just canceled minesweeper") + del self.games[ctx.author] + def setup(bot: commands.Bot) -> None: """Cog load.""" -- cgit v1.2.3 From 93370a5d55909224a2abb3463f5a1ee5657f02c1 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 02:17:21 +0200 Subject: fixed issue with winning detection --- bot/seasons/evergreen/minesweeper.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index e67a043b..241aa81d 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -163,11 +163,17 @@ class Minesweeper(commands.Cog): self.reveal(reveled, board, x, y) # check if won + break_ = False for x_ in range(10): for y_ in range(10): - if not (reveled[y_][x_] == "hidden" and board[y_][x_] == "bomb"): - await self.won(ctx) + if reveled[y_][x_] == "hidden" and board[y_][x_] != "bomb": + break_ = True break + else: + await self.won(ctx) + break + if break_: + break await self.reload_board(ctx) -- cgit v1.2.3 From df8a68fae9877c1f2943045093fa4a40f0fcb293 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 02:30:36 +0200 Subject: added that you can provide a custom chance for bombs --- bot/seasons/evergreen/minesweeper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 241aa81d..3c3ee011 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -17,10 +17,10 @@ class Minesweeper(commands.Cog): """Returns 1 if `cell` is a bomb if not 0""" return 1 if cell == "bomb" else 0 - def generate_board(self) -> typing.List[typing.List[typing.Union[str, int]]]: + def generate_board(self, bomb_chance: float) -> typing.List[typing.List[typing.Union[str, int]]]: """Generate a 2d array for the board.""" board: typing.List[typing.List[typing.Union[str, int]]] = [ - ["bomb" if random() <= .2 else "number" for _ in range(10)] for _ in range(10)] + ["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "number": @@ -69,7 +69,7 @@ class Minesweeper(commands.Cog): return discord_msg @commands.command(name="minesweeper") - async def minesweeper_command(self, ctx: commands.Context) -> None: + async def minesweeper_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None: """Start a game of minesweeper.""" if ctx.author in self.games.keys(): # Player is already playing msg = await ctx.send(f"{ctx.author.mention} you already have a game running") @@ -78,7 +78,7 @@ class Minesweeper(commands.Cog): return # Add game to list - board = self.generate_board() + board = self.generate_board(bomb_chance) reveled_board = [["hidden" for _ in range(10)] for _ in range(10)] await ctx.send(f"{ctx.author.mention} is playing minesweeper") -- cgit v1.2.3 From 90e89ecc5be19464f83f0943d21320948dccce17 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 02:51:09 +0200 Subject: fixed winning code again --- bot/seasons/evergreen/minesweeper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 3c3ee011..e48e3bba 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -86,6 +86,7 @@ class Minesweeper(commands.Cog): await ctx.author.send("play by typing: `.reveal x y` or `.flag x y` \nclose the game with `.end`") dm_msg = await ctx.author.send(self.format_for_discord(reveled_board)) + await ctx.author.send(self.format_for_discord(board)) self.games[ctx.author] = { "board": board, @@ -169,11 +170,10 @@ class Minesweeper(commands.Cog): if reveled[y_][x_] == "hidden" and board[y_][x_] != "bomb": break_ = True break - else: - await self.won(ctx) - break if break_: break + else: + await self.won(ctx) await self.reload_board(ctx) -- cgit v1.2.3 From e08b45cee1a70d15ce1248564254f456a2dac6bf Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 03:00:46 +0200 Subject: removed debugging message --- bot/seasons/evergreen/minesweeper.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index e48e3bba..07a19cde 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -86,7 +86,6 @@ class Minesweeper(commands.Cog): await ctx.author.send("play by typing: `.reveal x y` or `.flag x y` \nclose the game with `.end`") dm_msg = await ctx.author.send(self.format_for_discord(reveled_board)) - await ctx.author.send(self.format_for_discord(board)) self.games[ctx.author] = { "board": board, -- cgit v1.2.3 From 4d35dd6bd23a905bf0ef741f66fd6a5d46b07d84 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 10:24:19 +0200 Subject: fixed bug in get_cords --- bot/seasons/evergreen/minesweeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 07a19cde..e742d7d5 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -98,7 +98,7 @@ class Minesweeper(commands.Cog): def get_cords(value1: str, value2: str) -> typing.Tuple[int, int]: """Take in 2 values for the cords and turn them into numbers""" if value1.isnumeric(): - return int(value1) - 1, ord(value2.lower()) - 97 + return ord(value2.lower()) - 97, int(value1) - 1 else: return ord(value1.lower()) - 97, int(value2) - 1 -- cgit v1.2.3 From 5120dc9610869df302ba83e572aec029d35b370f Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 14:07:28 +0200 Subject: changed .flag and .reveal to be abel to run multiple cords at once --- bot/seasons/evergreen/minesweeper.py | 41 ++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index e742d7d5..ac1b287b 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -84,7 +84,7 @@ class Minesweeper(commands.Cog): await ctx.send(f"{ctx.author.mention} is playing minesweeper") chat_msg = await ctx.send(self.format_for_discord(reveled_board)) - await ctx.author.send("play by typing: `.reveal x y` or `.flag x y` \nclose the game with `.end`") + await ctx.author.send("play by typing: `.reveal xy xy ...` or `.flag xy xy ...` \nclose the game with `.end`") dm_msg = await ctx.author.send(self.format_for_discord(reveled_board)) self.games[ctx.author] = { @@ -111,12 +111,13 @@ class Minesweeper(commands.Cog): @commands.dm_only() @commands.command(name="flag") - async def flag_command(self, ctx: commands.Context, value1, value2) -> None: - """Place a flag on the board""" - x, y = self.get_cords(value1, value2) # ints + async def flag_command(self, ctx: commands.Context, *cords) -> None: + """Place multiple flags on the board""" board = self.games[ctx.author]["reveled"] - if board[y][x] == "hidden": - board[y][x] = "flag" + for cord in cords: + x, y = self.get_cords(cord[0], cord[1]) + if board[y][x] == "hidden": + board[y][x] = "flag" await self.reload_board(ctx) @@ -138,7 +139,7 @@ class Minesweeper(commands.Cog): await game["chat_msg"].channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") del self.games[ctx.author] - def reveal(self, reveled: typing.List, board: typing.List, x: int, y: int) -> None: + def reveal_zeros(self, reveled: typing.List, board: typing.List, x: int, y: int) -> None: """Used when a 0 is encountered to do a flood fill""" for x_ in [x - 1, x, x + 1]: for y_ in [y - 1, y, y + 1]: @@ -146,21 +147,15 @@ class Minesweeper(commands.Cog): continue reveled[y_][x_] = board[y_][x_] if board[y_][x_] == 0: - self.reveal(reveled, board, x_, y_) + self.reveal_zeros(reveled, board, x_, y_) - @commands.dm_only() - @commands.command(name="reveal") - async def reveal_command(self, ctx: commands.Context, value1, value2): - """Reveal a cell""" - x, y = self.get_cords(value1, value2) - game = self.games[ctx.author] - reveled = game["reveled"] - board = game["board"] + async def reveal_one(self, ctx: commands.Context, reveled: typing.List, board: typing.List, x: int, y: int) -> None: + """Reveal one square.""" reveled[y][x] = board[y][x] if board[y][x] == "bomb": await self.lost(ctx) elif board[y][x] == 0: - self.reveal(reveled, board, x, y) + self.reveal_zeros(reveled, board, x, y) # check if won break_ = False @@ -173,9 +168,19 @@ class Minesweeper(commands.Cog): break else: await self.won(ctx) - await self.reload_board(ctx) + @commands.dm_only() + @commands.command(name="reveal") + async def reveal_command(self, ctx: commands.Context, *cords): + """Reveal multiple cells""" + game = self.games[ctx.author] + reveled = game["reveled"] + board = game["board"] + for cord in cords: + x, y = self.get_cords(cord[0], cord[1]) + await self.reveal_one(ctx, reveled, board, x, y) + @commands.dm_only() @commands.command(name="end") async def end_command(self, ctx: commands.Context): -- cgit v1.2.3 From deda7968e2509d2ccdad35674d98144deaa88b86 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 14:34:22 +0200 Subject: fixed a small bug --- bot/seasons/evergreen/minesweeper.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index ac1b287b..b1d27735 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -84,7 +84,8 @@ class Minesweeper(commands.Cog): await ctx.send(f"{ctx.author.mention} is playing minesweeper") chat_msg = await ctx.send(self.format_for_discord(reveled_board)) - await ctx.author.send("play by typing: `.reveal xy xy ...` or `.flag xy xy ...` \nclose the game with `.end`") + await ctx.author.send("play by typing: `.reveal xy xy ...` or `.flag xy xy ...` \nclose the game with `.end`\n" + "cords must be in format `") dm_msg = await ctx.author.send(self.format_for_discord(reveled_board)) self.games[ctx.author] = { @@ -97,10 +98,7 @@ class Minesweeper(commands.Cog): @staticmethod def get_cords(value1: str, value2: str) -> typing.Tuple[int, int]: """Take in 2 values for the cords and turn them into numbers""" - if value1.isnumeric(): - return ord(value2.lower()) - 97, int(value1) - 1 - else: - return ord(value1.lower()) - 97, int(value2) - 1 + return ord(value1.lower()) - 97, int(value2) - 1 async def reload_board(self, ctx: commands.Context) -> None: """Update both playing boards.""" @@ -115,7 +113,7 @@ class Minesweeper(commands.Cog): """Place multiple flags on the board""" board = self.games[ctx.author]["reveled"] for cord in cords: - x, y = self.get_cords(cord[0], cord[1]) + x, y = self.get_cords(cord[0], cord[1:]) if board[y][x] == "hidden": board[y][x] = "flag" @@ -178,7 +176,7 @@ class Minesweeper(commands.Cog): reveled = game["reveled"] board = game["board"] for cord in cords: - x, y = self.get_cords(cord[0], cord[1]) + x, y = self.get_cords(cord[0], cord[1:]) await self.reveal_one(ctx, reveled, board, x, y) @commands.dm_only() -- cgit v1.2.3 From 1cb1ec544a918d585bc2160c5a822906d6c0c1d7 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 15:06:45 +0200 Subject: changed thing after requests. --- bot/seasons/evergreen/minesweeper.py | 127 ++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 63 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index b1d27735..9cd35faa 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -1,42 +1,41 @@ import typing from random import random -import discord from discord.ext import commands +GameBoard = typing.List[typing.List[typing.Union[str, int]]] +DictOfGames = typing.Dict[int, typing.Dict] + class Minesweeper(commands.Cog): """Play a game of minesweeper.""" def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - self.games: typing.Dict[discord.member, typing.Dict] = {} # Store the currently running games + self.games: DictOfGames = {} # Store the currently running games @staticmethod def is_bomb(cell: typing.Union[str, int]) -> int: """Returns 1 if `cell` is a bomb if not 0""" - return 1 if cell == "bomb" else 0 + return cell == "bomb" - def generate_board(self, bomb_chance: float) -> typing.List[typing.List[typing.Union[str, int]]]: + def generate_board(self, bomb_chance: float) -> GameBoard: """Generate a 2d array for the board.""" - board: typing.List[typing.List[typing.Union[str, int]]] = [ - ["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] + board: GameBoard = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "number": # calculate bombs near it - to_check = [] - for xt in [x - 1, x, x + 1]: - for yt in [y - 1, y, y + 1]: - if xt != -1 and xt != 10 and yt != -1 and yt != 10: - to_check.append(board[yt][xt]) + bombs = 0 + for x_ in [x - 1, x, x + 1]: + for y_ in [y - 1, y, y + 1]: + if x_ != -1 and x_ != 10 and y_ != -1 and y_ != 10 and board[y_][x_] == "bomb": + bombs += 1 - bombs = sum(map(self.is_bomb, to_check)) board[y][x] = bombs return board @staticmethod - def format_for_discord(board: typing.List[typing.List[typing.Union[str, int]]]) -> str: + def format_for_discord(board: GameBoard) -> str: """Format the board to a string for discord.""" mapping = { 0: ":stop_button:", @@ -55,9 +54,11 @@ class Minesweeper(commands.Cog): "flag": ":triangular_flag_on_post:" } - discord_msg = ":stop_button: :regional_indicator_a::regional_indicator_b::regional_indicator_c:" \ - ":regional_indicator_d::regional_indicator_e::regional_indicator_f::regional_indicator_g:" \ - ":regional_indicator_h::regional_indicator_i::regional_indicator_j:\n\n" + discord_msg = ( + ":stop_button: :regional_indicator_a::regional_indicator_b::regional_indicator_c:" + ":regional_indicator_d::regional_indicator_e::regional_indicator_f::regional_indicator_g:" + ":regional_indicator_h::regional_indicator_i::regional_indicator_j:\n\n" + ) rows: typing.List[str] = [] for row_number, row in enumerate(board): new_row = mapping[row_number + 1] + " " @@ -71,26 +72,27 @@ class Minesweeper(commands.Cog): @commands.command(name="minesweeper") async def minesweeper_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None: """Start a game of minesweeper.""" - if ctx.author in self.games.keys(): # Player is already playing + if ctx.author.id in self.games.keys(): # Player is already playing msg = await ctx.send(f"{ctx.author.mention} you already have a game running") await msg.delete(delay=2) await ctx.message.delete(delay=2) return # Add game to list - board = self.generate_board(bomb_chance) - reveled_board = [["hidden" for _ in range(10)] for _ in range(10)] + board: GameBoard = self.generate_board(bomb_chance) + revealed_board: GameBoard = [["hidden" for _ in range(10)] for _ in range(10)] await ctx.send(f"{ctx.author.mention} is playing minesweeper") - chat_msg = await ctx.send(self.format_for_discord(reveled_board)) + chat_msg = await ctx.send(self.format_for_discord(revealed_board)) - await ctx.author.send("play by typing: `.reveal xy xy ...` or `.flag xy xy ...` \nclose the game with `.end`\n" - "cords must be in format `") - dm_msg = await ctx.author.send(self.format_for_discord(reveled_board)) + await ctx.author.send("play by typing: `.reveal xy [xy]` or `.flag xy [xy]` \n" + "close the game with `.end`\n" + "cords must be in format ``") + dm_msg = await ctx.author.send(self.format_for_discord(revealed_board)) - self.games[ctx.author] = { + self.games[ctx.author.id] = { "board": board, - "reveled": reveled_board, + "revealed": revealed_board, "dm_msg": dm_msg, "chat_msg": chat_msg } @@ -102,16 +104,16 @@ class Minesweeper(commands.Cog): async def reload_board(self, ctx: commands.Context) -> None: """Update both playing boards.""" - game = self.games[ctx.author] + game = self.games[ctx.author.id] await game["dm_msg"].delete() - game["dm_msg"] = await ctx.author.send(self.format_for_discord(game["reveled"])) - await game["chat_msg"].edit(content=self.format_for_discord(game["reveled"])) + game["dm_msg"] = await ctx.author.send(self.format_for_discord(game["revealed"])) + await game["chat_msg"].edit(content=self.format_for_discord(game["revealed"])) @commands.dm_only() @commands.command(name="flag") async def flag_command(self, ctx: commands.Context, *cords) -> None: """Place multiple flags on the board""" - board = self.games[ctx.author]["reveled"] + board: GameBoard = self.games[ctx.author.id]["revealed"] for cord in cords: x, y = self.get_cords(cord[0], cord[1:]) if board[y][x] == "hidden": @@ -121,74 +123,73 @@ class Minesweeper(commands.Cog): async def lost(self, ctx: commands.Context) -> None: """The player lost the game""" - game = self.games[ctx.author] - game["reveled"] = game["board"] + game = self.games[ctx.author.id] + game["revealed"] = game["board"] await self.reload_board(ctx) await ctx.author.send(":fire: You lost :fire: ") await game["chat_msg"].channel.send(f":fire: {ctx.author.mention} just lost minesweeper :fire:") - del self.games[ctx.author] + del self.games[ctx.author.id] - async def won(self, ctx: commands.Context): + async def won(self, ctx: commands.Context) -> None: """The player won the game""" - game = self.games[ctx.author] - game["reveled"] = game["board"] + game = self.games[ctx.author.id] + game["revealed"] = game["board"] await self.reload_board(ctx) await ctx.author.send(":tada: You won! :tada: ") await game["chat_msg"].channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") - del self.games[ctx.author] + del self.games[ctx.author.id] - def reveal_zeros(self, reveled: typing.List, board: typing.List, x: int, y: int) -> None: + def reveal_zeros(self, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None: """Used when a 0 is encountered to do a flood fill""" for x_ in [x - 1, x, x + 1]: for y_ in [y - 1, y, y + 1]: - if x_ == -1 or x_ == 10 or y_ == -1 or y_ == 10 or reveled[y_][x_] != "hidden": + if x_ == -1 or x_ == 10 or y_ == -1 or y_ == 10 or revealed[y_][x_] != "hidden": continue - reveled[y_][x_] = board[y_][x_] + revealed[y_][x_] = board[y_][x_] if board[y_][x_] == 0: - self.reveal_zeros(reveled, board, x_, y_) + self.reveal_zeros(revealed, board, x_, y_) + + async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> None: + """Checks if a player has won""" + for x_ in range(10): + for y_ in range(10): + if revealed[y_][x_] == "hidden" and board[y_][x_] != "bomb": + return + else: + await self.won(ctx) - async def reveal_one(self, ctx: commands.Context, reveled: typing.List, board: typing.List, x: int, y: int) -> None: + async def reveal_one(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None: """Reveal one square.""" - reveled[y][x] = board[y][x] + revealed[y][x] = board[y][x] if board[y][x] == "bomb": await self.lost(ctx) elif board[y][x] == 0: - self.reveal_zeros(reveled, board, x, y) + self.reveal_zeros(revealed, board, x, y) + await self.check_if_won(ctx, revealed, board) - # check if won - break_ = False - for x_ in range(10): - for y_ in range(10): - if reveled[y_][x_] == "hidden" and board[y_][x_] != "bomb": - break_ = True - break - if break_: - break - else: - await self.won(ctx) await self.reload_board(ctx) @commands.dm_only() @commands.command(name="reveal") - async def reveal_command(self, ctx: commands.Context, *cords): + async def reveal_command(self, ctx: commands.Context, *cords) -> None: """Reveal multiple cells""" - game = self.games[ctx.author] - reveled = game["reveled"] - board = game["board"] + game = self.games[ctx.author.id] + revealed: GameBoard = game["revealed"] + board: GameBoard = game["board"] for cord in cords: x, y = self.get_cords(cord[0], cord[1:]) - await self.reveal_one(ctx, reveled, board, x, y) + await self.reveal_one(ctx, revealed, board, x, y) @commands.dm_only() @commands.command(name="end") async def end_command(self, ctx: commands.Context): """End the current game""" - game = self.games[ctx.author] - game["reveled"] = game["board"] + game = self.games[ctx.author.id] + game["revealed"] = game["board"] await self.reload_board(ctx) await ctx.author.send(":no_entry: you canceled the game :no_entry:") await game["chat_msg"].channel.send(f"{ctx.author.mention} just canceled minesweeper") - del self.games[ctx.author] + del self.games[ctx.author.id] def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From 940af8928a9e4aebb0e81f94c26899f23e83ac08 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 10 Aug 2019 19:53:05 +0200 Subject: fixed bugs and turned get_cords into a converter --- bot/seasons/evergreen/minesweeper.py | 53 +++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 19 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 9cd35faa..9e7fad12 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -7,6 +7,24 @@ GameBoard = typing.List[typing.List[typing.Union[str, int]]] DictOfGames = typing.Dict[int, typing.Dict] +class CordConverter(commands.Converter): + """Converter for cords.""" + + async def convert(self, ctx, cord: str) -> typing.Tuple[int, int]: + """Take in a cord string and turn it into x, y""" + if not 2 <= len(cord) <= 3: + raise commands.ArgumentParsingError() + value1 = cord[0] + value2 = cord[1:] + if not value2.isdigit(): + raise commands.ArgumentParsingError() + x = ord(value1) - 97 + y = int(value2) - 1 + if (not 0 <= x <= 9) or (not 0 <= y <= 9): + raise commands.ArgumentParsingError() + return x, y + + class Minesweeper(commands.Cog): """Play a game of minesweeper.""" @@ -97,11 +115,6 @@ class Minesweeper(commands.Cog): "chat_msg": chat_msg } - @staticmethod - def get_cords(value1: str, value2: str) -> typing.Tuple[int, int]: - """Take in 2 values for the cords and turn them into numbers""" - return ord(value1.lower()) - 97, int(value2) - 1 - async def reload_board(self, ctx: commands.Context) -> None: """Update both playing boards.""" game = self.games[ctx.author.id] @@ -111,11 +124,10 @@ class Minesweeper(commands.Cog): @commands.dm_only() @commands.command(name="flag") - async def flag_command(self, ctx: commands.Context, *cords) -> None: + async def flag_command(self, ctx: commands.Context, *cords: CordConverter) -> None: """Place multiple flags on the board""" board: GameBoard = self.games[ctx.author.id]["revealed"] - for cord in cords: - x, y = self.get_cords(cord[0], cord[1:]) + for x, y in cords: if board[y][x] == "hidden": board[y][x] = "flag" @@ -149,38 +161,41 @@ class Minesweeper(commands.Cog): if board[y_][x_] == 0: self.reveal_zeros(revealed, board, x_, y_) - async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> None: + async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool: """Checks if a player has won""" for x_ in range(10): for y_ in range(10): if revealed[y_][x_] == "hidden" and board[y_][x_] != "bomb": - return + return True else: await self.won(ctx) + return False - async def reveal_one(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None: + async def reveal_one(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard, x: int, y: int) -> bool: """Reveal one square.""" revealed[y][x] = board[y][x] if board[y][x] == "bomb": await self.lost(ctx) + return False elif board[y][x] == 0: self.reveal_zeros(revealed, board, x, y) - await self.check_if_won(ctx, revealed, board) - - await self.reload_board(ctx) + return await self.check_if_won(ctx, revealed, board) @commands.dm_only() @commands.command(name="reveal") - async def reveal_command(self, ctx: commands.Context, *cords) -> None: + async def reveal_command(self, ctx: commands.Context, *cords: CordConverter) -> None: """Reveal multiple cells""" game = self.games[ctx.author.id] revealed: GameBoard = game["revealed"] board: GameBoard = game["board"] - for cord in cords: - x, y = self.get_cords(cord[0], cord[1:]) - await self.reveal_one(ctx, revealed, board, x, y) - @commands.dm_only() + reload_board = True + for x, y in cords: + if not await self.reveal_one(ctx, revealed, board, x, y): + reload_board = False + if reload_board: + await self.reload_board(ctx) + @commands.command(name="end") async def end_command(self, ctx: commands.Context): """End the current game""" -- cgit v1.2.3 From af83526ca6b303732b9445135b5487909a9e9a2b Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 12 Aug 2019 17:03:49 +0800 Subject: Fix indent style and reduce logging message verbosity Addresses PR review requests by Mark. --- bot/decorators.py | 55 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 21 deletions(-) (limited to 'bot') diff --git a/bot/decorators.py b/bot/decorators.py index f556660e..02cf4b8a 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -24,8 +24,10 @@ def with_role(*role_ids: int): """Check to see whether the invoking user has any of the roles specified in role_ids.""" async def predicate(ctx: Context): if not ctx.guild: # Return False in a DM - log.debug(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " - "This command is restricted by the with_role decorator. Rejecting request.") + log.debug( + f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " + "This command is restricted by the with_role decorator. Rejecting request." + ) return False for role in ctx.author.roles: @@ -33,8 +35,10 @@ def with_role(*role_ids: int): log.debug(f"{ctx.author} has the '{role.name}' role, and passes the check.") return True - log.debug(f"{ctx.author} does not have the required role to use " - f"the '{ctx.command.name}' command, so the request is rejected.") + log.debug( + f"{ctx.author} does not have the required role to use " + f"the '{ctx.command.name}' command, so the request is rejected." + ) return False return commands.check(predicate) @@ -43,14 +47,18 @@ def without_role(*role_ids: int): """Check whether the invoking user does not have all of the roles specified in role_ids.""" async def predicate(ctx: Context): if not ctx.guild: # Return False in a DM - log.debug(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " - "This command is restricted by the without_role decorator. Rejecting request.") + log.debug( + f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " + "This command is restricted by the without_role decorator. Rejecting request." + ) return False author_roles = [role.id for role in ctx.author.roles] check = all(role not in author_roles for role in role_ids) - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The result of the without_role check was {check}.") + log.debug( + f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The result of the without_role check was {check}." + ) return check return commands.check(predicate) @@ -63,25 +71,30 @@ def in_channel_check(*channels: int, bypass_roles: typing.Container[int] = None) return True if ctx.channel.id in channels: - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The command was used in a whitelisted channel.") + log.debug( + f"{ctx.author} tried to call the '{ctx.command.name}' command " + f"and the command was used in a whitelisted channel." + ) return True if hasattr(ctx.command.callback, "in_channel_override"): - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The command was not used in a whitelisted channel, " - f"but the command was whitelisted to bypass the in_channel check.") + log.debug( + f"{ctx.author} called the '{ctx.command.name}' command " + f"and the command was whitelisted to bypass the in_channel check." + ) return True - if bypass_roles: - if any(r.id in bypass_roles for r in ctx.author.roles): - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The command was not used in a whitelisted channel, " - f"but the author had a role to bypass the in_channel check.") - return True + if bypass_roles and any(r.id in bypass_roles for r in ctx.author.roles): + log.debug( + f"{ctx.author} called the '{ctx.command.name}' command and " + f"had a role to bypass the in_channel check." + ) + return True - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The in_channel check failed.") + log.debug( + f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The in_channel check failed." + ) channels_str = ', '.join(f"<#{c_id}>" for c_id in channels) raise InChannelCheckFailure( -- cgit v1.2.3 From 12aefcf543ae5e2fb00ddc1dd0fbebadab0691d5 Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Mon, 12 Aug 2019 16:41:19 -0400 Subject: Cleaned speedrun.py, removed duplicate in json file --- bot/resources/evergreen/speedrun_links.json | 10 ++++------ bot/seasons/evergreen/speedrun.py | 16 +++++----------- 2 files changed, 9 insertions(+), 17 deletions(-) (limited to 'bot') diff --git a/bot/resources/evergreen/speedrun_links.json b/bot/resources/evergreen/speedrun_links.json index 1ae1e325..acb5746a 100644 --- a/bot/resources/evergreen/speedrun_links.json +++ b/bot/resources/evergreen/speedrun_links.json @@ -1,5 +1,4 @@ -{ - "links": [ + [ "https://www.youtube.com/watch?v=jNE28SDXdyQ", "https://www.youtube.com/watch?v=iI8Giq7zQDk", "https://www.youtube.com/watch?v=VqNnkqQgFbc", @@ -12,9 +11,8 @@ "https://www.youtube.com/watch?v=wwvgAAvhxM8", "https://www.youtube.com/watch?v=0TWQr0_fi80", "https://www.youtube.com/watch?v=hatqZby-0to", - "https://www.youtube.com/watch?v=X0pJSTy4tJI", "https://www.youtube.com/watch?v=tmnMq2Hw72w", "https://www.youtube.com/watch?v=UTkyeTCAucA", - "https://www.youtube.com/watch?v=67kQ3l-1qMs" - ] -} \ No newline at end of file + "https://www.youtube.com/watch?v=67kQ3l-1qMs", + "https://www.youtube.com/watch?v=14wqBA5Q1yc" +] diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index e3ecdca2..9123bb84 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -3,33 +3,27 @@ import logging from pathlib import Path from random import choice - from discord.ext import commands - log = logging.getLogger(__name__) class Speedrun(commands.Cog): - """A command that will link a random speedrun video from youtube to Discord.""" + """Commands about the video game speedrunning community.""" def __init__(self, bot): self.bot = bot @commands.command(name="speedrun") async def get_speedrun(self, ctx): - """ - Sends a link to Discord of a random speedrun from youtube. - - Utilizes speedrun_links.json to find links. - """ + """Sends a link to a video of a random speedrun.""" with open(Path('bot/resources/evergreen/speedrun_links.json')) as file: data = json.load(file) - links = data['links'] - await ctx.send(choice(links)) + # links = data['links'] + await ctx.send(choice(data)) def setup(bot): - """Cog load""" + """Load the Speedrun cog""" bot.add_cog(Speedrun(bot)) log.info("Speedrun cog loaded") -- cgit v1.2.3 From 789f088a3459feb8fbbdbac0152e7dead27664ef Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Mon, 12 Aug 2019 17:15:40 -0400 Subject: removed old comment, added encoding to open statment, moved data grab to init --- bot/seasons/evergreen/speedrun.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index 9123bb84..3cf531bf 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -13,14 +13,13 @@ class Speedrun(commands.Cog): def __init__(self, bot): self.bot = bot + with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf-8") as file: + self.data = json.load(file) @commands.command(name="speedrun") async def get_speedrun(self, ctx): """Sends a link to a video of a random speedrun.""" - with open(Path('bot/resources/evergreen/speedrun_links.json')) as file: - data = json.load(file) - # links = data['links'] - await ctx.send(choice(data)) + await ctx.send(choice(self.data)) def setup(bot): -- cgit v1.2.3 From 1c9221cee0eb33355e6a8063542d3452fd2ac362 Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Mon, 12 Aug 2019 17:41:30 -0400 Subject: moved data grab to module level, changed var to constant --- bot/seasons/evergreen/speedrun.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index 3cf531bf..f6a43a63 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -6,6 +6,8 @@ from random import choice from discord.ext import commands log = logging.getLogger(__name__) +with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf-8") as file: + LINKS = json.load(file) class Speedrun(commands.Cog): @@ -13,13 +15,11 @@ class Speedrun(commands.Cog): def __init__(self, bot): self.bot = bot - with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf-8") as file: - self.data = json.load(file) @commands.command(name="speedrun") async def get_speedrun(self, ctx): """Sends a link to a video of a random speedrun.""" - await ctx.send(choice(self.data)) + await ctx.send(choice(LINKS)) def setup(bot): -- cgit v1.2.3 From 13c81c808728b58b532fa19e5d1bd9abb0986943 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 02:09:52 +0200 Subject: Apply suggestions from code review Co-Authored-By: Mark --- bot/seasons/evergreen/minesweeper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 9e7fad12..30c871ee 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -139,7 +139,7 @@ class Minesweeper(commands.Cog): game["revealed"] = game["board"] await self.reload_board(ctx) await ctx.author.send(":fire: You lost :fire: ") - await game["chat_msg"].channel.send(f":fire: {ctx.author.mention} just lost minesweeper :fire:") + await game["chat_msg"].channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:") del self.games[ctx.author.id] async def won(self, ctx: commands.Context) -> None: @@ -147,8 +147,8 @@ class Minesweeper(commands.Cog): game = self.games[ctx.author.id] game["revealed"] = game["board"] await self.reload_board(ctx) - await ctx.author.send(":tada: You won! :tada: ") - await game["chat_msg"].channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") + await ctx.author.send(":tada: You won! :tada: ") + await game["chat_msg"].channel.send(f":tada: {ctx.author.mention} just won Minesweeper! :tada:") del self.games[ctx.author.id] def reveal_zeros(self, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None: @@ -203,7 +203,7 @@ class Minesweeper(commands.Cog): game["revealed"] = game["board"] await self.reload_board(ctx) await ctx.author.send(":no_entry: you canceled the game :no_entry:") - await game["chat_msg"].channel.send(f"{ctx.author.mention} just canceled minesweeper") + await game["chat_msg"].channel.send(f"{ctx.author.mention} just canceled Minesweeper.") del self.games[ctx.author.id] -- cgit v1.2.3 From 1a05919ea87d8f8b64bc30dedfe7b112f2c58a94 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 02:50:32 +0200 Subject: change after suggestions. --- bot/seasons/evergreen/minesweeper.py | 241 +++++++++++++++++++---------------- 1 file changed, 131 insertions(+), 110 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 9e7fad12..60e0c2ae 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -1,21 +1,42 @@ +import logging import typing +from dataclasses import dataclass from random import random +import discord from discord.ext import commands -GameBoard = typing.List[typing.List[typing.Union[str, int]]] -DictOfGames = typing.Dict[int, typing.Dict] - - -class CordConverter(commands.Converter): - """Converter for cords.""" - - async def convert(self, ctx, cord: str) -> typing.Tuple[int, int]: - """Take in a cord string and turn it into x, y""" - if not 2 <= len(cord) <= 3: +from bot.constants import Client + +MESSAGE_MAPPING = { + 0: ":stop_button:", + 1: ":one:", + 2: ":two:", + 3: ":three:", + 4: ":four:", + 5: ":five:", + 6: ":six:", + 7: ":seven:", + 8: ":eight:", + 9: ":nine:", + 10: ":keycap_ten:", + "bomb": ":bomb:", + "hidden": ":grey_question:", + "flag": ":triangular_flag_on_post:" +} + +log = logging.getLogger(__name__) + + +class CoordinateConverter(commands.Converter): + """Converter for Coordinates.""" + + async def convert(self, ctx, Coordinate: str) -> typing.Tuple[int, int]: + """Take in a Coordinate string and turn it into x, y""" + if not 2 <= len(Coordinate) <= 3: raise commands.ArgumentParsingError() - value1 = cord[0] - value2 = cord[1:] + value1 = Coordinate[0] + value2 = Coordinate[1:] if not value2.isdigit(): raise commands.ArgumentParsingError() x = ord(value1) - 97 @@ -25,53 +46,53 @@ class CordConverter(commands.Converter): return x, y +GameDict = typing.List[typing.List[typing.Union[str, int]]] + + +@dataclass +class Game: + """The data for a game.""" + + board: GameDict + revealed: GameDict + dm_msg: discord.message + chat_msg: discord.message + + +DictOfGames = typing.Dict[int, Game] + + class Minesweeper(commands.Cog): - """Play a game of minesweeper.""" + """Play a game of Minesweeper.""" def __init__(self, bot: commands.Bot) -> None: self.games: DictOfGames = {} # Store the currently running games @staticmethod - def is_bomb(cell: typing.Union[str, int]) -> int: - """Returns 1 if `cell` is a bomb if not 0""" - return cell == "bomb" + def get_neighbours(x: int, y: int) -> typing.Generator: + """Get all the neighbouring x and y including it self.""" + for x_ in [x - 1, x, x + 1]: + for y_ in [y - 1, y, y + 1]: + if x_ != -1 and x_ != 10 and y_ != -1 and y_ != 10: + yield x_, y_ - def generate_board(self, bomb_chance: float) -> GameBoard: + def generate_board(self, bomb_chance: float) -> GameDict: """Generate a 2d array for the board.""" - board: GameBoard = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] + board: GameDict = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "number": # calculate bombs near it bombs = 0 - for x_ in [x - 1, x, x + 1]: - for y_ in [y - 1, y, y + 1]: - if x_ != -1 and x_ != 10 and y_ != -1 and y_ != 10 and board[y_][x_] == "bomb": - bombs += 1 - + for x_, y_ in self.get_neighbours(x, y): + if board[y_][x_] == "bomb": + bombs += 1 board[y][x] = bombs return board @staticmethod - def format_for_discord(board: GameBoard) -> str: - """Format the board to a string for discord.""" - mapping = { - 0: ":stop_button:", - 1: ":one:", - 2: ":two:", - 3: ":three:", - 4: ":four:", - 5: ":five:", - 6: ":six:", - 7: ":seven:", - 8: ":eight:", - 9: ":nine:", - 10: ":keycap_ten:", - "bomb": ":bomb:", - "hidden": ":grey_question:", - "flag": ":triangular_flag_on_post:" - } - + def format_for_discord(board: GameDict) -> str: + """Format the board as a string for Discord.""" discord_msg = ( ":stop_button: :regional_indicator_a::regional_indicator_b::regional_indicator_c:" ":regional_indicator_d::regional_indicator_e::regional_indicator_f::regional_indicator_g:" @@ -79,9 +100,9 @@ class Minesweeper(commands.Cog): ) rows: typing.List[str] = [] for row_number, row in enumerate(board): - new_row = mapping[row_number + 1] + " " + new_row = MESSAGE_MAPPING[row_number + 1] + " " for cell in row: - new_row += mapping[cell] + new_row += MESSAGE_MAPPING[cell] rows.append(new_row) discord_msg += "\n".join(rows) @@ -89,124 +110,124 @@ class Minesweeper(commands.Cog): @commands.command(name="minesweeper") async def minesweeper_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None: - """Start a game of minesweeper.""" - if ctx.author.id in self.games.keys(): # Player is already playing - msg = await ctx.send(f"{ctx.author.mention} you already have a game running") + """Start a game of Minesweeper.""" + if ctx.author.id in self.games: # Player is already playing + msg = await ctx.send(f"{ctx.author.mention} you already have a game running!") await msg.delete(delay=2) await ctx.message.delete(delay=2) return # Add game to list - board: GameBoard = self.generate_board(bomb_chance) - revealed_board: GameBoard = [["hidden" for _ in range(10)] for _ in range(10)] + board: GameDict = self.generate_board(bomb_chance) + revealed_board: GameDict = [["hidden"] * 10 for _ in range(10)] - await ctx.send(f"{ctx.author.mention} is playing minesweeper") + await ctx.send(f"{ctx.author.mention} is playing Minesweeper") chat_msg = await ctx.send(self.format_for_discord(revealed_board)) - await ctx.author.send("play by typing: `.reveal xy [xy]` or `.flag xy [xy]` \n" - "close the game with `.end`\n" - "cords must be in format ``") + await ctx.author.send( + f"Play by typing: `{Client.prefix}reveal xy [xy]` or `{Client.prefix}flag xy [xy]` \n" + "Close the game with `.end`\n" + "Coordinates must be in format ``" + ) dm_msg = await ctx.author.send(self.format_for_discord(revealed_board)) - self.games[ctx.author.id] = { - "board": board, - "revealed": revealed_board, - "dm_msg": dm_msg, - "chat_msg": chat_msg - } + self.games[ctx.author.id] = Game( + board=board, + revealed=revealed_board, + dm_msg=dm_msg, + chat_msg=chat_msg + ) - async def reload_board(self, ctx: commands.Context) -> None: + async def update_boards(self, ctx: commands.Context) -> None: """Update both playing boards.""" game = self.games[ctx.author.id] - await game["dm_msg"].delete() - game["dm_msg"] = await ctx.author.send(self.format_for_discord(game["revealed"])) - await game["chat_msg"].edit(content=self.format_for_discord(game["revealed"])) + await game.dm_msg.delete() + game.dm_msg = await ctx.author.send(self.format_for_discord(game.revealed)) + await game.chat_msg.edit(content=self.format_for_discord(game.revealed)) @commands.dm_only() @commands.command(name="flag") - async def flag_command(self, ctx: commands.Context, *cords: CordConverter) -> None: + async def flag_command(self, ctx: commands.Context, *Coordinates: CoordinateConverter) -> None: """Place multiple flags on the board""" - board: GameBoard = self.games[ctx.author.id]["revealed"] - for x, y in cords: + board: GameDict = self.games[ctx.author.id].revealed + for x, y in Coordinates: if board[y][x] == "hidden": board[y][x] = "flag" - await self.reload_board(ctx) + await self.update_boards(ctx) async def lost(self, ctx: commands.Context) -> None: """The player lost the game""" game = self.games[ctx.author.id] - game["revealed"] = game["board"] - await self.reload_board(ctx) - await ctx.author.send(":fire: You lost :fire: ") - await game["chat_msg"].channel.send(f":fire: {ctx.author.mention} just lost minesweeper :fire:") - del self.games[ctx.author.id] + game.revealed = game.board + await ctx.author.send(":fire: You lost! :fire: ") + await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost minesweeper :fire:") async def won(self, ctx: commands.Context) -> None: """The player won the game""" game = self.games[ctx.author.id] - game["revealed"] = game["board"] - await self.reload_board(ctx) + game.revealed = game.board await ctx.author.send(":tada: You won! :tada: ") - await game["chat_msg"].channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") - del self.games[ctx.author.id] - - def reveal_zeros(self, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None: - """Used when a 0 is encountered to do a flood fill""" - for x_ in [x - 1, x, x + 1]: - for y_ in [y - 1, y, y + 1]: - if x_ == -1 or x_ == 10 or y_ == -1 or y_ == 10 or revealed[y_][x_] != "hidden": - continue - revealed[y_][x_] = board[y_][x_] - if board[y_][x_] == 0: - self.reveal_zeros(revealed, board, x_, y_) - - async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool: + await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") + + def reveal_zeros(self, revealed: GameDict, board: GameDict, x: int, y: int) -> None: + """Recursively reveal adjacent cells when a 0 cell is encountered.""" + for x_, y_ in self.get_neighbours(x, y): + if revealed[y_][x_] != "hidden": + continue + revealed[y_][x_] = board[y_][x_] + if board[y_][x_] == 0: + self.reveal_zeros(revealed, board, x_, y_) + + async def check_if_won(self, ctx, revealed: GameDict, board: GameDict) -> bool: """Checks if a player has won""" - for x_ in range(10): - for y_ in range(10): - if revealed[y_][x_] == "hidden" and board[y_][x_] != "bomb": - return True + for x in range(10): + for y in range(10): + if revealed[y][x] == "hidden" and board[y][x] != "bomb": + return False else: await self.won(ctx) - return False + return True - async def reveal_one(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard, x: int, y: int) -> bool: + async def reveal_one(self, ctx: commands.Context, revealed: GameDict, board: GameDict, x: int, y: int) -> bool: """Reveal one square.""" revealed[y][x] = board[y][x] if board[y][x] == "bomb": await self.lost(ctx) - return False + return True # game ended elif board[y][x] == 0: self.reveal_zeros(revealed, board, x, y) - return await self.check_if_won(ctx, revealed, board) + won = await self.check_if_won(ctx, revealed, board) + return won @commands.dm_only() @commands.command(name="reveal") - async def reveal_command(self, ctx: commands.Context, *cords: CordConverter) -> None: + async def reveal_command(self, ctx: commands.Context, *Coordinates: CoordinateConverter) -> None: """Reveal multiple cells""" game = self.games[ctx.author.id] - revealed: GameBoard = game["revealed"] - board: GameBoard = game["board"] - - reload_board = True - for x, y in cords: - if not await self.reveal_one(ctx, revealed, board, x, y): - reload_board = False - if reload_board: - await self.reload_board(ctx) + revealed: GameDict = game.revealed + board: GameDict = game.board + + for x, y in Coordinates: + if await self.reveal_one(ctx, revealed, board, x, y): # game ended + await self.update_boards(ctx) + del self.games[ctx.author.id] + break + else: + await self.update_boards(ctx) @commands.command(name="end") async def end_command(self, ctx: commands.Context): """End the current game""" game = self.games[ctx.author.id] - game["revealed"] = game["board"] - await self.reload_board(ctx) + game.revealed = game.board + await self.update_boards(ctx) await ctx.author.send(":no_entry: you canceled the game :no_entry:") - await game["chat_msg"].channel.send(f"{ctx.author.mention} just canceled minesweeper") + await game.chat_msg.channel.send(f"{ctx.author.mention} just canceled minesweeper") del self.games[ctx.author.id] def setup(bot: commands.Bot) -> None: - """Cog load.""" + """Load the Minesweeper cog.""" bot.add_cog(Minesweeper(bot)) + log.info("minesweeper cog loaded") -- cgit v1.2.3 From 9896148f12a38590f7727a65033591ecaa2fa646 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 04:29:07 +0200 Subject: fixed commit issue. --- bot/seasons/evergreen/minesweeper.py | 231 +++++++++++++++++++---------------- 1 file changed, 128 insertions(+), 103 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index f6c1d35a..60e0c2ae 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -1,21 +1,42 @@ +import logging import typing +from dataclasses import dataclass from random import random +import discord from discord.ext import commands -GameBoard = typing.List[typing.List[typing.Union[str, int]]] -DictOfGames = typing.Dict[int, typing.Dict] - - -class CordConverter(commands.Converter): - """Converter for cords.""" - - async def convert(self, ctx, cord: str) -> typing.Tuple[int, int]: - """Take in a cord string and turn it into x, y""" - if not 2 <= len(cord) <= 3: +from bot.constants import Client + +MESSAGE_MAPPING = { + 0: ":stop_button:", + 1: ":one:", + 2: ":two:", + 3: ":three:", + 4: ":four:", + 5: ":five:", + 6: ":six:", + 7: ":seven:", + 8: ":eight:", + 9: ":nine:", + 10: ":keycap_ten:", + "bomb": ":bomb:", + "hidden": ":grey_question:", + "flag": ":triangular_flag_on_post:" +} + +log = logging.getLogger(__name__) + + +class CoordinateConverter(commands.Converter): + """Converter for Coordinates.""" + + async def convert(self, ctx, Coordinate: str) -> typing.Tuple[int, int]: + """Take in a Coordinate string and turn it into x, y""" + if not 2 <= len(Coordinate) <= 3: raise commands.ArgumentParsingError() - value1 = cord[0] - value2 = cord[1:] + value1 = Coordinate[0] + value2 = Coordinate[1:] if not value2.isdigit(): raise commands.ArgumentParsingError() x = ord(value1) - 97 @@ -25,53 +46,53 @@ class CordConverter(commands.Converter): return x, y +GameDict = typing.List[typing.List[typing.Union[str, int]]] + + +@dataclass +class Game: + """The data for a game.""" + + board: GameDict + revealed: GameDict + dm_msg: discord.message + chat_msg: discord.message + + +DictOfGames = typing.Dict[int, Game] + + class Minesweeper(commands.Cog): - """Play a game of minesweeper.""" + """Play a game of Minesweeper.""" def __init__(self, bot: commands.Bot) -> None: self.games: DictOfGames = {} # Store the currently running games @staticmethod - def is_bomb(cell: typing.Union[str, int]) -> int: - """Returns 1 if `cell` is a bomb if not 0""" - return cell == "bomb" + def get_neighbours(x: int, y: int) -> typing.Generator: + """Get all the neighbouring x and y including it self.""" + for x_ in [x - 1, x, x + 1]: + for y_ in [y - 1, y, y + 1]: + if x_ != -1 and x_ != 10 and y_ != -1 and y_ != 10: + yield x_, y_ - def generate_board(self, bomb_chance: float) -> GameBoard: + def generate_board(self, bomb_chance: float) -> GameDict: """Generate a 2d array for the board.""" - board: GameBoard = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] + board: GameDict = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "number": # calculate bombs near it bombs = 0 - for x_ in [x - 1, x, x + 1]: - for y_ in [y - 1, y, y + 1]: - if x_ != -1 and x_ != 10 and y_ != -1 and y_ != 10 and board[y_][x_] == "bomb": - bombs += 1 - + for x_, y_ in self.get_neighbours(x, y): + if board[y_][x_] == "bomb": + bombs += 1 board[y][x] = bombs return board @staticmethod - def format_for_discord(board: GameBoard) -> str: - """Format the board to a string for discord.""" - mapping = { - 0: ":stop_button:", - 1: ":one:", - 2: ":two:", - 3: ":three:", - 4: ":four:", - 5: ":five:", - 6: ":six:", - 7: ":seven:", - 8: ":eight:", - 9: ":nine:", - 10: ":keycap_ten:", - "bomb": ":bomb:", - "hidden": ":grey_question:", - "flag": ":triangular_flag_on_post:" - } - + def format_for_discord(board: GameDict) -> str: + """Format the board as a string for Discord.""" discord_msg = ( ":stop_button: :regional_indicator_a::regional_indicator_b::regional_indicator_c:" ":regional_indicator_d::regional_indicator_e::regional_indicator_f::regional_indicator_g:" @@ -79,9 +100,9 @@ class Minesweeper(commands.Cog): ) rows: typing.List[str] = [] for row_number, row in enumerate(board): - new_row = mapping[row_number + 1] + " " + new_row = MESSAGE_MAPPING[row_number + 1] + " " for cell in row: - new_row += mapping[cell] + new_row += MESSAGE_MAPPING[cell] rows.append(new_row) discord_msg += "\n".join(rows) @@ -89,120 +110,124 @@ class Minesweeper(commands.Cog): @commands.command(name="minesweeper") async def minesweeper_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None: - """Start a game of minesweeper.""" - if ctx.author.id in self.games.keys(): # Player is already playing - msg = await ctx.send(f"{ctx.author.mention} you already have a game running") + """Start a game of Minesweeper.""" + if ctx.author.id in self.games: # Player is already playing + msg = await ctx.send(f"{ctx.author.mention} you already have a game running!") await msg.delete(delay=2) await ctx.message.delete(delay=2) return # Add game to list - board: GameBoard = self.generate_board(bomb_chance) - revealed_board: GameBoard = [["hidden" for _ in range(10)] for _ in range(10)] + board: GameDict = self.generate_board(bomb_chance) + revealed_board: GameDict = [["hidden"] * 10 for _ in range(10)] - await ctx.send(f"{ctx.author.mention} is playing minesweeper") + await ctx.send(f"{ctx.author.mention} is playing Minesweeper") chat_msg = await ctx.send(self.format_for_discord(revealed_board)) - await ctx.author.send("play by typing: `.reveal xy [xy]` or `.flag xy [xy]` \n" - "close the game with `.end`\n" - "cords must be in format ``") + await ctx.author.send( + f"Play by typing: `{Client.prefix}reveal xy [xy]` or `{Client.prefix}flag xy [xy]` \n" + "Close the game with `.end`\n" + "Coordinates must be in format ``" + ) dm_msg = await ctx.author.send(self.format_for_discord(revealed_board)) - self.games[ctx.author.id] = { - "board": board, - "revealed": revealed_board, - "dm_msg": dm_msg, - "chat_msg": chat_msg - } + self.games[ctx.author.id] = Game( + board=board, + revealed=revealed_board, + dm_msg=dm_msg, + chat_msg=chat_msg + ) - async def reload_board(self, ctx: commands.Context) -> None: + async def update_boards(self, ctx: commands.Context) -> None: """Update both playing boards.""" game = self.games[ctx.author.id] - await game["dm_msg"].delete() - game["dm_msg"] = await ctx.author.send(self.format_for_discord(game["revealed"])) - await game["chat_msg"].edit(content=self.format_for_discord(game["revealed"])) + await game.dm_msg.delete() + game.dm_msg = await ctx.author.send(self.format_for_discord(game.revealed)) + await game.chat_msg.edit(content=self.format_for_discord(game.revealed)) @commands.dm_only() @commands.command(name="flag") - async def flag_command(self, ctx: commands.Context, *cords: CordConverter) -> None: + async def flag_command(self, ctx: commands.Context, *Coordinates: CoordinateConverter) -> None: """Place multiple flags on the board""" - board: GameBoard = self.games[ctx.author.id]["revealed"] - for x, y in cords: + board: GameDict = self.games[ctx.author.id].revealed + for x, y in Coordinates: if board[y][x] == "hidden": board[y][x] = "flag" - await self.reload_board(ctx) + await self.update_boards(ctx) async def lost(self, ctx: commands.Context) -> None: """The player lost the game""" game = self.games[ctx.author.id] game.revealed = game.board await ctx.author.send(":fire: You lost! :fire: ") - await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:") + await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost minesweeper :fire:") async def won(self, ctx: commands.Context) -> None: """The player won the game""" game = self.games[ctx.author.id] game.revealed = game.board await ctx.author.send(":tada: You won! :tada: ") - await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won Minesweeper! :tada:") - - def reveal_zeros(self, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None: - """Used when a 0 is encountered to do a flood fill""" - for x_ in [x - 1, x, x + 1]: - for y_ in [y - 1, y, y + 1]: - if x_ == -1 or x_ == 10 or y_ == -1 or y_ == 10 or revealed[y_][x_] != "hidden": - continue - revealed[y_][x_] = board[y_][x_] - if board[y_][x_] == 0: - self.reveal_zeros(revealed, board, x_, y_) - - async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool: + await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") + + def reveal_zeros(self, revealed: GameDict, board: GameDict, x: int, y: int) -> None: + """Recursively reveal adjacent cells when a 0 cell is encountered.""" + for x_, y_ in self.get_neighbours(x, y): + if revealed[y_][x_] != "hidden": + continue + revealed[y_][x_] = board[y_][x_] + if board[y_][x_] == 0: + self.reveal_zeros(revealed, board, x_, y_) + + async def check_if_won(self, ctx, revealed: GameDict, board: GameDict) -> bool: """Checks if a player has won""" - for x_ in range(10): - for y_ in range(10): - if revealed[y_][x_] == "hidden" and board[y_][x_] != "bomb": - return True + for x in range(10): + for y in range(10): + if revealed[y][x] == "hidden" and board[y][x] != "bomb": + return False else: await self.won(ctx) - return False + return True - async def reveal_one(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard, x: int, y: int) -> bool: + async def reveal_one(self, ctx: commands.Context, revealed: GameDict, board: GameDict, x: int, y: int) -> bool: """Reveal one square.""" revealed[y][x] = board[y][x] if board[y][x] == "bomb": await self.lost(ctx) - return False + return True # game ended elif board[y][x] == 0: self.reveal_zeros(revealed, board, x, y) - return await self.check_if_won(ctx, revealed, board) + won = await self.check_if_won(ctx, revealed, board) + return won @commands.dm_only() @commands.command(name="reveal") - async def reveal_command(self, ctx: commands.Context, *cords: CordConverter) -> None: + async def reveal_command(self, ctx: commands.Context, *Coordinates: CoordinateConverter) -> None: """Reveal multiple cells""" game = self.games[ctx.author.id] - revealed: GameBoard = game["revealed"] - board: GameBoard = game["board"] - - reload_board = True - for x, y in cords: - if not await self.reveal_one(ctx, revealed, board, x, y): - reload_board = False - if reload_board: - await self.reload_board(ctx) + revealed: GameDict = game.revealed + board: GameDict = game.board + + for x, y in Coordinates: + if await self.reveal_one(ctx, revealed, board, x, y): # game ended + await self.update_boards(ctx) + del self.games[ctx.author.id] + break + else: + await self.update_boards(ctx) @commands.command(name="end") async def end_command(self, ctx: commands.Context): """End the current game""" game = self.games[ctx.author.id] - game["revealed"] = game["board"] - await self.reload_board(ctx) + game.revealed = game.board + await self.update_boards(ctx) await ctx.author.send(":no_entry: you canceled the game :no_entry:") - await game.chat_msg.channel.send(f"{ctx.author.mention} just canceled Minesweeper") + await game.chat_msg.channel.send(f"{ctx.author.mention} just canceled minesweeper") del self.games[ctx.author.id] def setup(bot: commands.Bot) -> None: - """Cog load.""" + """Load the Minesweeper cog.""" bot.add_cog(Minesweeper(bot)) + log.info("minesweeper cog loaded") -- cgit v1.2.3 From 5bfbd5a07896736a03a5e07625acfa6f6b47a073 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 04:58:22 +0200 Subject: Apply suggestions from code review Co-Authored-By: Mark --- bot/seasons/evergreen/minesweeper.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 60e0c2ae..9ca36d57 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -126,8 +126,8 @@ class Minesweeper(commands.Cog): await ctx.author.send( f"Play by typing: `{Client.prefix}reveal xy [xy]` or `{Client.prefix}flag xy [xy]` \n" - "Close the game with `.end`\n" - "Coordinates must be in format ``" + f"Close the game with `{Client.prefix}end`\n" + "Coordinates must be in the format ``" ) dm_msg = await ctx.author.send(self.format_for_discord(revealed_board)) @@ -160,15 +160,15 @@ class Minesweeper(commands.Cog): """The player lost the game""" game = self.games[ctx.author.id] game.revealed = game.board - await ctx.author.send(":fire: You lost! :fire: ") - await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost minesweeper :fire:") + await ctx.author.send(":fire: You lost! :fire:") + await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:") async def won(self, ctx: commands.Context) -> None: """The player won the game""" game = self.games[ctx.author.id] game.revealed = game.board - await ctx.author.send(":tada: You won! :tada: ") - await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") + await ctx.author.send(":tada: You won! :tada:") + await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won Minesweeper! :tada:") def reveal_zeros(self, revealed: GameDict, board: GameDict, x: int, y: int) -> None: """Recursively reveal adjacent cells when a 0 cell is encountered.""" @@ -183,7 +183,7 @@ class Minesweeper(commands.Cog): """Checks if a player has won""" for x in range(10): for y in range(10): - if revealed[y][x] == "hidden" and board[y][x] != "bomb": + if revealed[y][x] == "hidden": return False else: await self.won(ctx) @@ -222,12 +222,12 @@ class Minesweeper(commands.Cog): game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) - await ctx.author.send(":no_entry: you canceled the game :no_entry:") - await game.chat_msg.channel.send(f"{ctx.author.mention} just canceled minesweeper") + await ctx.author.send(":no_entry: You canceled the game. :no_entry:") + await game.chat_msg.channel.send(f"{ctx.author.mention} just canceled Minesweeper.") del self.games[ctx.author.id] def setup(bot: commands.Bot) -> None: """Load the Minesweeper cog.""" bot.add_cog(Minesweeper(bot)) - log.info("minesweeper cog loaded") + log.info("Minesweeper cog loaded") -- cgit v1.2.3 From 293624295803f0442d6e65451a250560e559eea8 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 05:12:46 +0200 Subject: change after suggestions. --- bot/seasons/evergreen/minesweeper.py | 68 ++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 30 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 60e0c2ae..3bd2f431 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -31,42 +31,46 @@ log = logging.getLogger(__name__) class CoordinateConverter(commands.Converter): """Converter for Coordinates.""" - async def convert(self, ctx, Coordinate: str) -> typing.Tuple[int, int]: - """Take in a Coordinate string and turn it into x, y""" - if not 2 <= len(Coordinate) <= 3: + async def convert(self, ctx, coordinate: str) -> typing.Tuple[int, int]: + """Take in a coordinate string and turn it into x, y""" + if not 2 <= len(coordinate) <= 3: raise commands.ArgumentParsingError() - value1 = Coordinate[0] - value2 = Coordinate[1:] + + value1 = coordinate[0] + value2 = coordinate[1:] + if not value2.isdigit(): raise commands.ArgumentParsingError() + x = ord(value1) - 97 y = int(value2) - 1 + if (not 0 <= x <= 9) or (not 0 <= y <= 9): raise commands.ArgumentParsingError() return x, y -GameDict = typing.List[typing.List[typing.Union[str, int]]] +GameBoard = typing.List[typing.List[typing.Union[str, int]]] @dataclass class Game: """The data for a game.""" - board: GameDict - revealed: GameDict + board: GameBoard + revealed: GameBoard dm_msg: discord.message chat_msg: discord.message -DictOfGames = typing.Dict[int, Game] +GamesDict = typing.Dict[int, Game] class Minesweeper(commands.Cog): """Play a game of Minesweeper.""" def __init__(self, bot: commands.Bot) -> None: - self.games: DictOfGames = {} # Store the currently running games + self.games: GamesDict = {} # Store the currently running games @staticmethod def get_neighbours(x: int, y: int) -> typing.Generator: @@ -76,9 +80,9 @@ class Minesweeper(commands.Cog): if x_ != -1 and x_ != 10 and y_ != -1 and y_ != 10: yield x_, y_ - def generate_board(self, bomb_chance: float) -> GameDict: + def generate_board(self, bomb_chance: float) -> GameBoard: """Generate a 2d array for the board.""" - board: GameDict = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] + board: GameBoard = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "number": @@ -91,7 +95,7 @@ class Minesweeper(commands.Cog): return board @staticmethod - def format_for_discord(board: GameDict) -> str: + def format_for_discord(board: GameBoard) -> str: """Format the board as a string for Discord.""" discord_msg = ( ":stop_button: :regional_indicator_a::regional_indicator_b::regional_indicator_c:" @@ -118,8 +122,8 @@ class Minesweeper(commands.Cog): return # Add game to list - board: GameDict = self.generate_board(bomb_chance) - revealed_board: GameDict = [["hidden"] * 10 for _ in range(10)] + board: GameBoard = self.generate_board(bomb_chance) + revealed_board: GameBoard = [["hidden"] * 10 for _ in range(10)] await ctx.send(f"{ctx.author.mention} is playing Minesweeper") chat_msg = await ctx.send(self.format_for_discord(revealed_board)) @@ -147,10 +151,10 @@ class Minesweeper(commands.Cog): @commands.dm_only() @commands.command(name="flag") - async def flag_command(self, ctx: commands.Context, *Coordinates: CoordinateConverter) -> None: + async def flag_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: """Place multiple flags on the board""" - board: GameDict = self.games[ctx.author.id].revealed - for x, y in Coordinates: + board: GameBoard = self.games[ctx.author.id].revealed + for x, y in coordinates: if board[y][x] == "hidden": board[y][x] = "flag" @@ -170,7 +174,7 @@ class Minesweeper(commands.Cog): await ctx.author.send(":tada: You won! :tada: ") await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won minesweeper :tada:") - def reveal_zeros(self, revealed: GameDict, board: GameDict, x: int, y: int) -> None: + def reveal_zeros(self, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None: """Recursively reveal adjacent cells when a 0 cell is encountered.""" for x_, y_ in self.get_neighbours(x, y): if revealed[y_][x_] != "hidden": @@ -179,7 +183,7 @@ class Minesweeper(commands.Cog): if board[y_][x_] == 0: self.reveal_zeros(revealed, board, x_, y_) - async def check_if_won(self, ctx, revealed: GameDict, board: GameDict) -> bool: + async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool: """Checks if a player has won""" for x in range(10): for y in range(10): @@ -189,26 +193,29 @@ class Minesweeper(commands.Cog): await self.won(ctx) return True - async def reveal_one(self, ctx: commands.Context, revealed: GameDict, board: GameDict, x: int, y: int) -> bool: - """Reveal one square.""" + async def reveal_one(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard, x: int, y: int) -> bool: + """ + Reveal one square. + + return is True if the game ended, breaking the loop in `reveal_command` and deleting the game + """ revealed[y][x] = board[y][x] if board[y][x] == "bomb": await self.lost(ctx) return True # game ended elif board[y][x] == 0: self.reveal_zeros(revealed, board, x, y) - won = await self.check_if_won(ctx, revealed, board) - return won + return await self.check_if_won(ctx, revealed, board) @commands.dm_only() @commands.command(name="reveal") - async def reveal_command(self, ctx: commands.Context, *Coordinates: CoordinateConverter) -> None: + async def reveal_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: """Reveal multiple cells""" game = self.games[ctx.author.id] - revealed: GameDict = game.revealed - board: GameDict = game.board + revealed: GameBoard = game.revealed + board: GameBoard = game.board - for x, y in Coordinates: + for x, y in coordinates: if await self.reveal_one(ctx, revealed, board, x, y): # game ended await self.update_boards(ctx) del self.games[ctx.author.id] @@ -222,8 +229,9 @@ class Minesweeper(commands.Cog): game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) - await ctx.author.send(":no_entry: you canceled the game :no_entry:") - await game.chat_msg.channel.send(f"{ctx.author.mention} just canceled minesweeper") + new_msg = f":no_entry: Game canceled :no_entry:\n{self.format_for_discord(game.revealed)}" + await game.dm_msg.edit(content=new_msg) + await game.chat_msg.edit(content=new_msg) del self.games[ctx.author.id] -- cgit v1.2.3 From a6bcd09015fc37464aec953f1c385f061cebe351 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 05:24:58 +0200 Subject: changed from using `format_discord` to use the content of the last message. --- bot/seasons/evergreen/minesweeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index e0cb9b13..19486033 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -229,7 +229,7 @@ class Minesweeper(commands.Cog): game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) - new_msg = f":no_entry: Game canceled :no_entry:\n{self.format_for_discord(game.revealed)}" + new_msg = f":no_entry: Game canceled :no_entry:\n{game.dm_msg.content}" await game.dm_msg.edit(content=new_msg) await game.chat_msg.edit(content=new_msg) del self.games[ctx.author.id] -- cgit v1.2.3 From 00647b2a00e0392563ee768fc84ef57d0502e01f Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 19:22:30 +0200 Subject: Apply suggestions from code review Co-Authored-By: Ava --- bot/seasons/evergreen/minesweeper.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 19486033..65d293c9 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -34,19 +34,19 @@ class CoordinateConverter(commands.Converter): async def convert(self, ctx, coordinate: str) -> typing.Tuple[int, int]: """Take in a coordinate string and turn it into x, y""" if not 2 <= len(coordinate) <= 3: - raise commands.ArgumentParsingError() + raise commands.BadArgument('Invalid co-ordinate provided') - value1 = coordinate[0] + digit, letter = sorted(coordinate.lower()) value2 = coordinate[1:] if not value2.isdigit(): - raise commands.ArgumentParsingError() + raise commands.BadArgument - x = ord(value1) - 97 + x = ord(value1) - ord('a') y = int(value2) - 1 if (not 0 <= x <= 9) or (not 0 <= y <= 9): - raise commands.ArgumentParsingError() + raise commands.BadArgument return x, y @@ -59,8 +59,8 @@ class Game: board: GameBoard revealed: GameBoard - dm_msg: discord.message - chat_msg: discord.message + dm_msg: discord.Message + chat_msg: discord.Message GamesDict = typing.Dict[int, Game] @@ -116,7 +116,7 @@ class Minesweeper(commands.Cog): async def minesweeper_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None: """Start a game of Minesweeper.""" if ctx.author.id in self.games: # Player is already playing - msg = await ctx.send(f"{ctx.author.mention} you already have a game running!") + msg = await ctx.send(f"{ctx.author.mention} you already have a game running!", delete_after=2) await msg.delete(delay=2) await ctx.message.delete(delay=2) return -- cgit v1.2.3 From 0b3687b799d633eef12cd48ea4a6f0cae35080f8 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 20:19:49 +0200 Subject: changed after suggestions. --- bot/seasons/evergreen/minesweeper.py | 93 +++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 34 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 19486033..d43233d4 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -36,14 +36,13 @@ class CoordinateConverter(commands.Converter): if not 2 <= len(coordinate) <= 3: raise commands.ArgumentParsingError() - value1 = coordinate[0] - value2 = coordinate[1:] + digit, letter = sorted(coordinate.lower()) - if not value2.isdigit(): + if not letter.isdigit(): raise commands.ArgumentParsingError() - x = ord(value1) - 97 - y = int(value2) - 1 + x = ord(letter.lower()) - 97 + y = int(digit) - 1 if (not 0 <= x <= 9) or (not 0 <= y <= 9): raise commands.ArgumentParsingError() @@ -61,6 +60,7 @@ class Game: revealed: GameBoard dm_msg: discord.message chat_msg: discord.message + activated_on_server: bool GamesDict = typing.Dict[int, Game] @@ -72,8 +72,13 @@ class Minesweeper(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.games: GamesDict = {} # Store the currently running games + @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) + async def minesweeper_group(self, ctx: commands.Context): + """Commands for Playing Minesweeper""" + await ctx.send_help(ctx.command) + @staticmethod - def get_neighbours(x: int, y: int) -> typing.Generator: + def get_neighbours(x: int, y: int) -> typing.Generator[typing.Tuple[int, int], None, None]: """Get all the neighbouring x and y including it self.""" for x_ in [x - 1, x, x + 1]: for y_ in [y - 1, y, y + 1]: @@ -82,7 +87,12 @@ class Minesweeper(commands.Cog): def generate_board(self, bomb_chance: float) -> GameBoard: """Generate a 2d array for the board.""" - board: GameBoard = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)] + board: GameBoard = [ + [ + "bomb" if random() <= bomb_chance else "number" + for _ in range(10) + ] for _ in range(10) + ] for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "number": @@ -102,22 +112,20 @@ class Minesweeper(commands.Cog): ":regional_indicator_d::regional_indicator_e::regional_indicator_f::regional_indicator_g:" ":regional_indicator_h::regional_indicator_i::regional_indicator_j:\n\n" ) - rows: typing.List[str] = [] + rows = [] for row_number, row in enumerate(board): - new_row = MESSAGE_MAPPING[row_number + 1] + " " - for cell in row: - new_row += MESSAGE_MAPPING[cell] + new_row = f"{MESSAGE_MAPPING[row_number + 1]} " + new_row += "".join(MESSAGE_MAPPING[cell] for cell in row) rows.append(new_row) discord_msg += "\n".join(rows) return discord_msg - @commands.command(name="minesweeper") - async def minesweeper_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None: + @minesweeper_group.command(name="start") + async def start_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None: """Start a game of Minesweeper.""" if ctx.author.id in self.games: # Player is already playing - msg = await ctx.send(f"{ctx.author.mention} you already have a game running!") - await msg.delete(delay=2) + await ctx.send(f"{ctx.author.mention} you already have a game running!") await ctx.message.delete(delay=2) return @@ -125,13 +133,15 @@ class Minesweeper(commands.Cog): board: GameBoard = self.generate_board(bomb_chance) revealed_board: GameBoard = [["hidden"] * 10 for _ in range(10)] - await ctx.send(f"{ctx.author.mention} is playing Minesweeper") - chat_msg = await ctx.send(self.format_for_discord(revealed_board)) + if ctx.guild: + await ctx.send(f"{ctx.author.mention} is playing Minesweeper") + chat_msg = await ctx.send(self.format_for_discord(revealed_board)) + else: + chat_msg = None await ctx.author.send( f"Play by typing: `{Client.prefix}reveal xy [xy]` or `{Client.prefix}flag xy [xy]` \n" f"Close the game with `{Client.prefix}end`\n" - "Coordinates must be in format ``" ) dm_msg = await ctx.author.send(self.format_for_discord(revealed_board)) @@ -139,7 +149,8 @@ class Minesweeper(commands.Cog): board=board, revealed=revealed_board, dm_msg=dm_msg, - chat_msg=chat_msg + chat_msg=chat_msg, + activated_on_server=ctx.guild is not None ) async def update_boards(self, ctx: commands.Context) -> None: @@ -147,10 +158,11 @@ class Minesweeper(commands.Cog): game = self.games[ctx.author.id] await game.dm_msg.delete() game.dm_msg = await ctx.author.send(self.format_for_discord(game.revealed)) - await game.chat_msg.edit(content=self.format_for_discord(game.revealed)) + if game.activated_on_server: + await game.chat_msg.edit(content=self.format_for_discord(game.revealed)) @commands.dm_only() - @commands.command(name="flag") + @minesweeper_group.command(name="flag") async def flag_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: """Place multiple flags on the board""" board: GameBoard = self.games[ctx.author.id].revealed @@ -165,14 +177,16 @@ class Minesweeper(commands.Cog): game = self.games[ctx.author.id] game.revealed = game.board await ctx.author.send(":fire: You lost! :fire:") - await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:") + if game.activated_on_server: + await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:") async def won(self, ctx: commands.Context) -> None: """The player won the game""" game = self.games[ctx.author.id] game.revealed = game.board await ctx.author.send(":tada: You won! :tada:") - await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won Minesweeper! :tada:") + if game.activated_on_server: + await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won Minesweeper! :tada:") def reveal_zeros(self, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None: """Recursively reveal adjacent cells when a 0 cell is encountered.""" @@ -185,15 +199,24 @@ class Minesweeper(commands.Cog): async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool: """Checks if a player has won""" - for x in range(10): - for y in range(10): - if revealed[y][x] == "hidden": - return False + if any( + revealed[y][x] in ["hidden", "flag"] and board[y][x] != "bomb" + for x in range(10) + for y in range(10) + ): + return False else: await self.won(ctx) return True - async def reveal_one(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard, x: int, y: int) -> bool: + async def reveal_one( + self, + ctx: commands.Context, + revealed: GameBoard, + board: GameBoard, + x: int, + y: int + ) -> bool: """ Reveal one square. @@ -202,13 +225,13 @@ class Minesweeper(commands.Cog): revealed[y][x] = board[y][x] if board[y][x] == "bomb": await self.lost(ctx) - return True # game ended + return True elif board[y][x] == 0: self.reveal_zeros(revealed, board, x, y) return await self.check_if_won(ctx, revealed, board) @commands.dm_only() - @commands.command(name="reveal") + @minesweeper_group.command(name="reveal") async def reveal_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: """Reveal multiple cells""" game = self.games[ctx.author.id] @@ -216,22 +239,24 @@ class Minesweeper(commands.Cog): board: GameBoard = game.board for x, y in coordinates: - if await self.reveal_one(ctx, revealed, board, x, y): # game ended + # reveal_one returns True if the revealed cell is a bomb or the player won., ending the game + if await self.reveal_one(ctx, revealed, board, x, y): await self.update_boards(ctx) del self.games[ctx.author.id] break else: await self.update_boards(ctx) - @commands.command(name="end") + @minesweeper_group.command(name="end") async def end_command(self, ctx: commands.Context): - """End the current game""" + """End your current game""" game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) new_msg = f":no_entry: Game canceled :no_entry:\n{game.dm_msg.content}" await game.dm_msg.edit(content=new_msg) - await game.chat_msg.edit(content=new_msg) + if game.activated_on_server: + await game.chat_msg.edit(content=new_msg) del self.games[ctx.author.id] -- cgit v1.2.3 From 95bea08cb90975962ceb25af7180334d25254999 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Tue, 13 Aug 2019 22:26:25 +0200 Subject: Apply suggestions from code review Co-Authored-By: Thomas Petersson --- bot/seasons/evergreen/minesweeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index e005bbb9..b742512d 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -239,7 +239,7 @@ class Minesweeper(commands.Cog): board: GameBoard = game.board for x, y in coordinates: - # reveal_one returns True if the revealed cell is a bomb or the player won., ending the game + # reveal_one returns True if the revealed cell is a bomb or the player won, ending the game if await self.reveal_one(ctx, revealed, board, x, y): await self.update_boards(ctx) del self.games[ctx.author.id] -- cgit v1.2.3 From 3fffa3a0a5f8d819ecd11baf5b0907f983a737f0 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 14 Aug 2019 02:12:48 +0200 Subject: Apply suggestions from code review Co-Authored-By: Mark --- bot/seasons/evergreen/minesweeper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index b742512d..27ca9675 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -38,7 +38,7 @@ class CoordinateConverter(commands.Converter): digit, letter = sorted(coordinate.lower()) - if not letter.isdigit(): + if not digit.isdigit(): raise commands.BadArgument x = ord(letter) - ord('a') @@ -140,8 +140,8 @@ class Minesweeper(commands.Cog): chat_msg = None await ctx.author.send( - f"Play by typing: `{Client.prefix}reveal xy [xy]` or `{Client.prefix}flag xy [xy]` \n" - f"Close the game with `{Client.prefix}end`\n" + f"Play by typing: `{Client.prefix}ms reveal xy [xy]` or `{Client.prefix}ms flag xy [xy]` \n" + f"Close the game with `{Client.prefix}ms end`\n" ) dm_msg = await ctx.author.send(self.format_for_discord(revealed_board)) -- cgit v1.2.3 From 6a7aec50e891557c08e42b473fc3770f04537a1c Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 14 Aug 2019 09:39:41 +0200 Subject: added header to message. so the board looks correct on compact mode. --- bot/seasons/evergreen/minesweeper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 27ca9675..9333bb68 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -135,7 +135,7 @@ class Minesweeper(commands.Cog): if ctx.guild: await ctx.send(f"{ctx.author.mention} is playing Minesweeper") - chat_msg = await ctx.send(self.format_for_discord(revealed_board)) + chat_msg = await ctx.send(f"Here's there board!\n{self.format_for_discord(revealed_board)}") else: chat_msg = None @@ -143,7 +143,7 @@ class Minesweeper(commands.Cog): f"Play by typing: `{Client.prefix}ms reveal xy [xy]` or `{Client.prefix}ms flag xy [xy]` \n" f"Close the game with `{Client.prefix}ms end`\n" ) - dm_msg = await ctx.author.send(self.format_for_discord(revealed_board)) + dm_msg = await ctx.author.send(f"Here's your board!\n{self.format_for_discord(revealed_board)}") self.games[ctx.author.id] = Game( board=board, @@ -157,9 +157,9 @@ class Minesweeper(commands.Cog): """Update both playing boards.""" game = self.games[ctx.author.id] await game.dm_msg.delete() - game.dm_msg = await ctx.author.send(self.format_for_discord(game.revealed)) + game.dm_msg = await ctx.author.send(f"Here's your board!\n{self.format_for_discord(game.revealed)}") if game.activated_on_server: - await game.chat_msg.edit(content=self.format_for_discord(game.revealed)) + await game.chat_msg.edit(content=f"Here's there board!\n{self.format_for_discord(game.revealed)}") @commands.dm_only() @minesweeper_group.command(name="flag") -- cgit v1.2.3 From 55a82ee2ae326f1d3c666a97768592dc75da7a12 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 14 Aug 2019 16:31:03 +0200 Subject: fixed coordinate bug. --- bot/seasons/evergreen/minesweeper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 9333bb68..6f124c63 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -36,7 +36,11 @@ class CoordinateConverter(commands.Converter): if not 2 <= len(coordinate) <= 3: raise commands.BadArgument('Invalid co-ordinate provided') - digit, letter = sorted(coordinate.lower()) + if len(coordinate) == 3: + digit = "10" + letter = sorted(coordinate.lower())[-1] + else: + digit, letter = sorted(coordinate.lower()) if not digit.isdigit(): raise commands.BadArgument -- cgit v1.2.3 From 30836a5fa04e730b3d4f6dc821604b433bb939e2 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 14 Aug 2019 17:27:48 +0200 Subject: changed flag emoji to stay under max message lenght --- bot/seasons/evergreen/minesweeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 6f124c63..671439f7 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -22,7 +22,7 @@ MESSAGE_MAPPING = { 10: ":keycap_ten:", "bomb": ":bomb:", "hidden": ":grey_question:", - "flag": ":triangular_flag_on_post:" + "flag": ":pyflag:" } log = logging.getLogger(__name__) -- cgit v1.2.3 From 20b1cf384b39e15a6526a137920762bcc3a9331c Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 14 Aug 2019 17:47:17 +0200 Subject: Revert "changed flag emoji to stay under max message lenght" This reverts commit 30836a5f --- bot/seasons/evergreen/minesweeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 671439f7..6f124c63 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -22,7 +22,7 @@ MESSAGE_MAPPING = { 10: ":keycap_ten:", "bomb": ":bomb:", "hidden": ":grey_question:", - "flag": ":pyflag:" + "flag": ":triangular_flag_on_post:" } log = logging.getLogger(__name__) -- cgit v1.2.3 From 9cb378bbb2e9ec4dc42e13541e314e71bf180fb6 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 14 Aug 2019 17:52:33 +0200 Subject: changed flag emoji to stay under max message lenght --- bot/seasons/evergreen/minesweeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 9333bb68..9f6aff95 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -22,7 +22,7 @@ MESSAGE_MAPPING = { 10: ":keycap_ten:", "bomb": ":bomb:", "hidden": ":grey_question:", - "flag": ":triangular_flag_on_post:" + "flag": ":pyflag:" } log = logging.getLogger(__name__) -- cgit v1.2.3 From 5840276f97271926f09052890d6036314dad7f07 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 14 Aug 2019 19:34:00 +0200 Subject: added suggestion. --- bot/seasons/evergreen/minesweeper.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 6f124c63..bf9633b3 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -36,11 +36,13 @@ class CoordinateConverter(commands.Converter): if not 2 <= len(coordinate) <= 3: raise commands.BadArgument('Invalid co-ordinate provided') - if len(coordinate) == 3: - digit = "10" - letter = sorted(coordinate.lower())[-1] + coordinate = coordinate.lower() + if coordinate[0].isalpha(): + digit = coordinate[1:] + letter = coordinate[0] else: - digit, letter = sorted(coordinate.lower()) + digit = coordinate[:-1] + letter = coordinate[-1] if not digit.isdigit(): raise commands.BadArgument -- cgit v1.2.3 From 6adc479d8b7ca89ed5f138049f4e7893c3402638 Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Wed, 14 Aug 2019 17:46:32 -0400 Subject: add recommend_game.py and resource files --- bot/resources/evergreen/game_recs/chrono_trigger.json | 10 ++++++++++ bot/resources/evergreen/game_recs/digimon_world.json | 8 ++++++++ bot/resources/evergreen/game_recs/skyrim.json | 13 +++++++++++++ bot/seasons/evergreen/recommend_game.py | 0 4 files changed, 31 insertions(+) create mode 100644 bot/resources/evergreen/game_recs/chrono_trigger.json create mode 100644 bot/resources/evergreen/game_recs/digimon_world.json create mode 100644 bot/resources/evergreen/game_recs/skyrim.json create mode 100644 bot/seasons/evergreen/recommend_game.py (limited to 'bot') diff --git a/bot/resources/evergreen/game_recs/chrono_trigger.json b/bot/resources/evergreen/game_recs/chrono_trigger.json new file mode 100644 index 00000000..970f7899 --- /dev/null +++ b/bot/resources/evergreen/game_recs/chrono_trigger.json @@ -0,0 +1,10 @@ +{ + "title": "Chrono Trigger", + "recText": "My personal favorite game of all time. Square + Enix + Akira Toriyama = Pure Time-traveling gold.", + "console": [ + "snes", + "gba", + "pc" + ], + "image": "https://vignette.wikia.nocookie.net/chrono/images/2/24/Chrono_Trigger_cover.jpg" +} \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/digimon_world.json b/bot/resources/evergreen/game_recs/digimon_world.json new file mode 100644 index 00000000..e58dd2ed --- /dev/null +++ b/bot/resources/evergreen/game_recs/digimon_world.json @@ -0,0 +1,8 @@ +{ + "title": "Digimon World", + "recText": "A great mix of town-building and pet-raising set in the Digimon universe.", + "console": [ + "ps1" + ], + "image": "https://www.mobygames.com/images/covers/l/437308-digimon-world-playstation-front-cover.jpg" +} \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/skyrim.json b/bot/resources/evergreen/game_recs/skyrim.json new file mode 100644 index 00000000..e26ff988 --- /dev/null +++ b/bot/resources/evergreen/game_recs/skyrim.json @@ -0,0 +1,13 @@ +{ + "title": "Elder Scrolls V: Skyrim", + "recText": "The last mainline Elder Scrolls game offered a fantastic role-playing experience with untethered freedom and a great story", + "console": [ + "xbox 360", + "ps3", + "pc", + "nintendo switch", + "xbox one", + "ps4" + ], + "image": "https://upload.wikimedia.org/wikipedia/en/1/15/The_Elder_Scrolls_V_Skyrim_cover.png" +} \ No newline at end of file diff --git a/bot/seasons/evergreen/recommend_game.py b/bot/seasons/evergreen/recommend_game.py new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 61b44ccb430eaf785bc2c279f928e6c0a253c4d5 Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Wed, 14 Aug 2019 21:35:44 -0400 Subject: added wiki links and author to json files, changes to Embed --- .../evergreen/game_recs/chrono_trigger.json | 6 ++- .../evergreen/game_recs/digimon_world.json | 4 +- bot/resources/evergreen/game_recs/skyrim.json | 6 ++- bot/seasons/evergreen/recommend_game.py | 56 ++++++++++++++++++++++ 4 files changed, 67 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/resources/evergreen/game_recs/chrono_trigger.json b/bot/resources/evergreen/game_recs/chrono_trigger.json index 970f7899..cde74b70 100644 --- a/bot/resources/evergreen/game_recs/chrono_trigger.json +++ b/bot/resources/evergreen/game_recs/chrono_trigger.json @@ -1,10 +1,12 @@ { "title": "Chrono Trigger", - "recText": "My personal favorite game of all time. Square + Enix + Akira Toriyama = Pure Time-traveling gold.", + "recText": "One of the best games of all time. A brilliant story involving time-travel with loveable characters. It has a brilliant score by Yasonuri Mitsuda and artwork by Akira Toriyama. With over 20 endings and New Game+, there is a huge amount of replay value here.", "console": [ "snes", "gba", "pc" ], - "image": "https://vignette.wikia.nocookie.net/chrono/images/2/24/Chrono_Trigger_cover.jpg" + "wikiLink": "https://chrono.fandom.com/wiki/Chrono_Trigger", + "image": "https://vignette.wikia.nocookie.net/chrono/images/2/24/Chrono_Trigger_cover.jpg", + "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/digimon_world.json b/bot/resources/evergreen/game_recs/digimon_world.json index e58dd2ed..86883e44 100644 --- a/bot/resources/evergreen/game_recs/digimon_world.json +++ b/bot/resources/evergreen/game_recs/digimon_world.json @@ -4,5 +4,7 @@ "console": [ "ps1" ], - "image": "https://www.mobygames.com/images/covers/l/437308-digimon-world-playstation-front-cover.jpg" + "image": "https://www.mobygames.com/images/covers/l/437308-digimon-world-playstation-front-cover.jpg", + "wikiLink": "https://digimon.fandom.com/wiki/Digimon_World", + "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/skyrim.json b/bot/resources/evergreen/game_recs/skyrim.json index e26ff988..7a6ef117 100644 --- a/bot/resources/evergreen/game_recs/skyrim.json +++ b/bot/resources/evergreen/game_recs/skyrim.json @@ -1,6 +1,6 @@ { "title": "Elder Scrolls V: Skyrim", - "recText": "The last mainline Elder Scrolls game offered a fantastic role-playing experience with untethered freedom and a great story", + "recText": "The latest mainline Elder Scrolls game offered a fantastic role-playing experience with untethered freedom and a great story. Offering vast mod support, the game has endless customization and replay value.", "console": [ "xbox 360", "ps3", @@ -9,5 +9,7 @@ "xbox one", "ps4" ], - "image": "https://upload.wikimedia.org/wikipedia/en/1/15/The_Elder_Scrolls_V_Skyrim_cover.png" + "image": "https://upload.wikimedia.org/wikipedia/en/1/15/The_Elder_Scrolls_V_Skyrim_cover.png", + "wikiLink": "https://elderscrolls.fandom.com/wiki/Skyrim", + "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/seasons/evergreen/recommend_game.py b/bot/seasons/evergreen/recommend_game.py index e69de29b..e4ad5392 100644 --- a/bot/seasons/evergreen/recommend_game.py +++ b/bot/seasons/evergreen/recommend_game.py @@ -0,0 +1,56 @@ +import json +import logging +import os +from pathlib import Path +from random import shuffle + +import discord +from discord.ext import commands + +log = logging.getLogger(__name__) +DIR = "bot/resources/evergreen/game_recs" +game_recs = [] + + +class RecommendGame(commands.Cog): + """Commands related to recommending games.""" + + def __init__(self, bot): + self.bot = bot + self.populate_recs() + + @commands.command(name="recommend_game") + async def recommend_game(self, ctx): + """Sends an Embed of a random game recommendation.""" + if not game_recs: + self.populate_recs() + game = game_recs.pop() + else: + game = game_recs.pop() + + embed = discord.Embed( + title=game['title'], + url=game['wikiLink'], + color=discord.Colour.blue() + ) + + author = self.bot.get_user(int(game['author'])) + embed.set_author(name=author.name, icon_url=author.avatar_url) + embed.set_image(url=game['image']) + embed.add_field(name="Recommendation", value=game['recText']) + + await ctx.send(embed=embed) + + def populate_recs(self): + """Populates the game_recs list from resources.""" + for file_url in os.listdir(DIR): + with Path(DIR, file_url).open(encoding='utf-8') as file: + data = json.load(file) + game_recs.append(data) + shuffle(game_recs) + + +def setup(bot): + """Loads the RecommendGame cog.""" + bot.add_cog(RecommendGame(bot)) + log.info("Recommend_Game cog loaded") -- cgit v1.2.3 From fba48d5599cb362096913689c91fa333a3603848 Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Thu, 15 Aug 2019 17:48:41 -0400 Subject: edited json files, added new resource, small tweaks to Embed --- bot/resources/evergreen/game_recs/doom_2.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 bot/resources/evergreen/game_recs/doom_2.json (limited to 'bot') diff --git a/bot/resources/evergreen/game_recs/doom_2.json b/bot/resources/evergreen/game_recs/doom_2.json new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 6db0d45b7814379907d1cfa36fabf0c62c385cea Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Thu, 15 Aug 2019 17:49:04 -0400 Subject: edited json files, added new resource, small tweaks to Embed --- bot/resources/evergreen/game_recs/chrono_trigger.json | 7 +------ bot/resources/evergreen/game_recs/digimon_world.json | 7 ++----- bot/resources/evergreen/game_recs/doom_2.json | 7 +++++++ bot/resources/evergreen/game_recs/skyrim.json | 10 +--------- bot/seasons/evergreen/recommend_game.py | 14 +++++--------- 5 files changed, 16 insertions(+), 29 deletions(-) (limited to 'bot') diff --git a/bot/resources/evergreen/game_recs/chrono_trigger.json b/bot/resources/evergreen/game_recs/chrono_trigger.json index cde74b70..963da43c 100644 --- a/bot/resources/evergreen/game_recs/chrono_trigger.json +++ b/bot/resources/evergreen/game_recs/chrono_trigger.json @@ -1,12 +1,7 @@ { "title": "Chrono Trigger", "recText": "One of the best games of all time. A brilliant story involving time-travel with loveable characters. It has a brilliant score by Yasonuri Mitsuda and artwork by Akira Toriyama. With over 20 endings and New Game+, there is a huge amount of replay value here.", - "console": [ - "snes", - "gba", - "pc" - ], - "wikiLink": "https://chrono.fandom.com/wiki/Chrono_Trigger", + "wikiLink": "https://rawg.io/games/chrono-trigger-1995", "image": "https://vignette.wikia.nocookie.net/chrono/images/2/24/Chrono_Trigger_cover.jpg", "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/digimon_world.json b/bot/resources/evergreen/game_recs/digimon_world.json index 86883e44..c5d93560 100644 --- a/bot/resources/evergreen/game_recs/digimon_world.json +++ b/bot/resources/evergreen/game_recs/digimon_world.json @@ -1,10 +1,7 @@ { "title": "Digimon World", - "recText": "A great mix of town-building and pet-raising set in the Digimon universe.", - "console": [ - "ps1" - ], + "recText": "A great mix of town-building and pet-raising set in the Digimon universe. With plenty of Digimon to raise and recruit to the village, this charming game will keep you occupied for a long time.", "image": "https://www.mobygames.com/images/covers/l/437308-digimon-world-playstation-front-cover.jpg", - "wikiLink": "https://digimon.fandom.com/wiki/Digimon_World", + "wikiLink": "https://rawg.io/games/digimon-world", "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/doom_2.json b/bot/resources/evergreen/game_recs/doom_2.json index e69de29b..3d4ec8fa 100644 --- a/bot/resources/evergreen/game_recs/doom_2.json +++ b/bot/resources/evergreen/game_recs/doom_2.json @@ -0,0 +1,7 @@ +{ + "title": "Doom II", + "recText": "Doom 2 was one of the first FPS games that I truly enjoyed. It offered awesome weapons, terrifying demons to kill, and a great atmosphere to do it in.", + "image": "https://upload.wikimedia.org/wikipedia/en/thumb/2/29/Doom_II_-_Hell_on_Earth_Coverart.png/220px-Doom_II_-_Hell_on_Earth_Coverart.png", + "wikiLink": "https://rawg.io/games/doom-ii", + "author": "352635617709916161" +} \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/skyrim.json b/bot/resources/evergreen/game_recs/skyrim.json index 7a6ef117..aaf8ebf9 100644 --- a/bot/resources/evergreen/game_recs/skyrim.json +++ b/bot/resources/evergreen/game_recs/skyrim.json @@ -1,15 +1,7 @@ { "title": "Elder Scrolls V: Skyrim", "recText": "The latest mainline Elder Scrolls game offered a fantastic role-playing experience with untethered freedom and a great story. Offering vast mod support, the game has endless customization and replay value.", - "console": [ - "xbox 360", - "ps3", - "pc", - "nintendo switch", - "xbox one", - "ps4" - ], "image": "https://upload.wikimedia.org/wikipedia/en/1/15/The_Elder_Scrolls_V_Skyrim_cover.png", - "wikiLink": "https://elderscrolls.fandom.com/wiki/Skyrim", + "wikiLink": "https://rawg.io/games/the-elder-scrolls-v-skyrim", "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/seasons/evergreen/recommend_game.py b/bot/seasons/evergreen/recommend_game.py index e4ad5392..45864336 100644 --- a/bot/seasons/evergreen/recommend_game.py +++ b/bot/seasons/evergreen/recommend_game.py @@ -27,22 +27,18 @@ class RecommendGame(commands.Cog): game = game_recs.pop() else: game = game_recs.pop() - - embed = discord.Embed( - title=game['title'], - url=game['wikiLink'], - color=discord.Colour.blue() - ) - author = self.bot.get_user(int(game['author'])) + + # Creating and formatting Embed + embed = discord.Embed(color=discord.Colour.blue()) embed.set_author(name=author.name, icon_url=author.avatar_url) embed.set_image(url=game['image']) - embed.add_field(name="Recommendation", value=game['recText']) + embed.add_field(name='Recommendation: ' + game['title'] + '\n' + game['wikiLink'], value=game['recText']) await ctx.send(embed=embed) def populate_recs(self): - """Populates the game_recs list from resources.""" + """Populates the list `game_recs` from resources.""" for file_url in os.listdir(DIR): with Path(DIR, file_url).open(encoding='utf-8') as file: data = json.load(file) -- cgit v1.2.3 From d39f4b59474b2c67489c0e33a74a764894bd8a4c Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Thu, 15 Aug 2019 19:07:29 -0400 Subject: moved populating game_recs to module level, added an index to iterate game_recs, edit docstring, added aliases --- bot/seasons/evergreen/recommend_game.py | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/recommend_game.py b/bot/seasons/evergreen/recommend_game.py index 45864336..1c81ea39 100644 --- a/bot/seasons/evergreen/recommend_game.py +++ b/bot/seasons/evergreen/recommend_game.py @@ -1,6 +1,5 @@ import json import logging -import os from pathlib import Path from random import shuffle @@ -8,25 +7,36 @@ import discord from discord.ext import commands log = logging.getLogger(__name__) -DIR = "bot/resources/evergreen/game_recs" +DIR = Path("bot/resources/evergreen/game_recs") game_recs = [] +# Populate the list `game_recs` with resource files +for file_url in DIR.glob("*.json"): + with Path(file_url).open(encoding='utf-8') as file: + data = json.load(file) + game_recs.append(data) +shuffle(game_recs) + class RecommendGame(commands.Cog): """Commands related to recommending games.""" def __init__(self, bot): self.bot = bot - self.populate_recs() + self.index = 0 - @commands.command(name="recommend_game") + @commands.command(name="recommend_game", aliases=['gameRec', 'recommendGame']) async def recommend_game(self, ctx): """Sends an Embed of a random game recommendation.""" - if not game_recs: - self.populate_recs() - game = game_recs.pop() + if self.index < len(game_recs): + game = game_recs[self.index] + self.index += 1 else: - game = game_recs.pop() + self.index = 0 + shuffle(game_recs) + game = game_recs[self.index] + self.index += 1 + author = self.bot.get_user(int(game['author'])) # Creating and formatting Embed @@ -37,16 +47,8 @@ class RecommendGame(commands.Cog): await ctx.send(embed=embed) - def populate_recs(self): - """Populates the list `game_recs` from resources.""" - for file_url in os.listdir(DIR): - with Path(DIR, file_url).open(encoding='utf-8') as file: - data = json.load(file) - game_recs.append(data) - shuffle(game_recs) - def setup(bot): """Loads the RecommendGame cog.""" bot.add_cog(RecommendGame(bot)) - log.info("Recommend_Game cog loaded") + log.info("RecommendGame cog loaded") -- cgit v1.2.3 From 2481347db7e75408a508dab941a112fa3f686ea7 Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Thu, 15 Aug 2019 20:46:28 -0400 Subject: cleaned up code, changed json fieldnames, changed command name/alias --- bot/resources/evergreen/game_recs/chrono_trigger.json | 4 ++-- bot/resources/evergreen/game_recs/digimon_world.json | 4 ++-- bot/resources/evergreen/game_recs/doom_2.json | 4 ++-- bot/resources/evergreen/game_recs/skyrim.json | 4 ++-- bot/seasons/evergreen/recommend_game.py | 18 +++++++----------- 5 files changed, 15 insertions(+), 19 deletions(-) (limited to 'bot') diff --git a/bot/resources/evergreen/game_recs/chrono_trigger.json b/bot/resources/evergreen/game_recs/chrono_trigger.json index 963da43c..219c1e78 100644 --- a/bot/resources/evergreen/game_recs/chrono_trigger.json +++ b/bot/resources/evergreen/game_recs/chrono_trigger.json @@ -1,7 +1,7 @@ { "title": "Chrono Trigger", - "recText": "One of the best games of all time. A brilliant story involving time-travel with loveable characters. It has a brilliant score by Yasonuri Mitsuda and artwork by Akira Toriyama. With over 20 endings and New Game+, there is a huge amount of replay value here.", - "wikiLink": "https://rawg.io/games/chrono-trigger-1995", + "description": "One of the best games of all time. A brilliant story involving time-travel with loveable characters. It has a brilliant score by Yasonuri Mitsuda and artwork by Akira Toriyama. With over 20 endings and New Game+, there is a huge amount of replay value here.", + "link": "https://rawg.io/games/chrono-trigger-1995", "image": "https://vignette.wikia.nocookie.net/chrono/images/2/24/Chrono_Trigger_cover.jpg", "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/digimon_world.json b/bot/resources/evergreen/game_recs/digimon_world.json index c5d93560..a2820f8e 100644 --- a/bot/resources/evergreen/game_recs/digimon_world.json +++ b/bot/resources/evergreen/game_recs/digimon_world.json @@ -1,7 +1,7 @@ { "title": "Digimon World", - "recText": "A great mix of town-building and pet-raising set in the Digimon universe. With plenty of Digimon to raise and recruit to the village, this charming game will keep you occupied for a long time.", + "description": "A great mix of town-building and pet-raising set in the Digimon universe. With plenty of Digimon to raise and recruit to the village, this charming game will keep you occupied for a long time.", "image": "https://www.mobygames.com/images/covers/l/437308-digimon-world-playstation-front-cover.jpg", - "wikiLink": "https://rawg.io/games/digimon-world", + "link": "https://rawg.io/games/digimon-world", "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/doom_2.json b/bot/resources/evergreen/game_recs/doom_2.json index 3d4ec8fa..e228e2b1 100644 --- a/bot/resources/evergreen/game_recs/doom_2.json +++ b/bot/resources/evergreen/game_recs/doom_2.json @@ -1,7 +1,7 @@ { "title": "Doom II", - "recText": "Doom 2 was one of the first FPS games that I truly enjoyed. It offered awesome weapons, terrifying demons to kill, and a great atmosphere to do it in.", + "description": "Doom 2 was one of the first FPS games that I truly enjoyed. It offered awesome weapons, terrifying demons to kill, and a great atmosphere to do it in.", "image": "https://upload.wikimedia.org/wikipedia/en/thumb/2/29/Doom_II_-_Hell_on_Earth_Coverart.png/220px-Doom_II_-_Hell_on_Earth_Coverart.png", - "wikiLink": "https://rawg.io/games/doom-ii", + "link": "https://rawg.io/games/doom-ii", "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/resources/evergreen/game_recs/skyrim.json b/bot/resources/evergreen/game_recs/skyrim.json index aaf8ebf9..09f93563 100644 --- a/bot/resources/evergreen/game_recs/skyrim.json +++ b/bot/resources/evergreen/game_recs/skyrim.json @@ -1,7 +1,7 @@ { "title": "Elder Scrolls V: Skyrim", - "recText": "The latest mainline Elder Scrolls game offered a fantastic role-playing experience with untethered freedom and a great story. Offering vast mod support, the game has endless customization and replay value.", + "description": "The latest mainline Elder Scrolls game offered a fantastic role-playing experience with untethered freedom and a great story. Offering vast mod support, the game has endless customization and replay value.", "image": "https://upload.wikimedia.org/wikipedia/en/1/15/The_Elder_Scrolls_V_Skyrim_cover.png", - "wikiLink": "https://rawg.io/games/the-elder-scrolls-v-skyrim", + "link": "https://rawg.io/games/the-elder-scrolls-v-skyrim", "author": "352635617709916161" } \ No newline at end of file diff --git a/bot/seasons/evergreen/recommend_game.py b/bot/seasons/evergreen/recommend_game.py index 1c81ea39..30a538a5 100644 --- a/bot/seasons/evergreen/recommend_game.py +++ b/bot/seasons/evergreen/recommend_game.py @@ -7,12 +7,11 @@ import discord from discord.ext import commands log = logging.getLogger(__name__) -DIR = Path("bot/resources/evergreen/game_recs") game_recs = [] # Populate the list `game_recs` with resource files -for file_url in DIR.glob("*.json"): - with Path(file_url).open(encoding='utf-8') as file: +for rec_path in Path("bot/resources/evergreen/game_recs").glob("*.json"): + with rec_path.open(encoding='utf-8') as file: data = json.load(file) game_recs.append(data) shuffle(game_recs) @@ -25,17 +24,14 @@ class RecommendGame(commands.Cog): self.bot = bot self.index = 0 - @commands.command(name="recommend_game", aliases=['gameRec', 'recommendGame']) + @commands.command(name="recommendgame", aliases=['gamerec']) async def recommend_game(self, ctx): """Sends an Embed of a random game recommendation.""" - if self.index < len(game_recs): - game = game_recs[self.index] - self.index += 1 - else: + if self.index >= len(game_recs): self.index = 0 shuffle(game_recs) - game = game_recs[self.index] - self.index += 1 + game = game_recs[self.index] + self.index += 1 author = self.bot.get_user(int(game['author'])) @@ -43,7 +39,7 @@ class RecommendGame(commands.Cog): embed = discord.Embed(color=discord.Colour.blue()) embed.set_author(name=author.name, icon_url=author.avatar_url) embed.set_image(url=game['image']) - embed.add_field(name='Recommendation: ' + game['title'] + '\n' + game['wikiLink'], value=game['recText']) + embed.add_field(name='Recommendation: ' + game['title'] + '\n' + game['link'], value=game['description']) await ctx.send(embed=embed) -- cgit v1.2.3 From 8de2c6d6fd7c8131c5e7671878520f86d6219399 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Fri, 16 Aug 2019 18:37:20 +0200 Subject: changed flag again. --- bot/seasons/evergreen/minesweeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 9f6aff95..f09230a9 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -22,7 +22,7 @@ MESSAGE_MAPPING = { 10: ":keycap_ten:", "bomb": ":bomb:", "hidden": ":grey_question:", - "flag": ":pyflag:" + "flag": ":flag_black:" } log = logging.getLogger(__name__) -- cgit v1.2.3 From e4038b467898e6d78da7dda24f9fa7e1958ac448 Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Fri, 16 Aug 2019 15:49:34 -0400 Subject: added validation for Embed author formatting --- bot/seasons/evergreen/recommend_game.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/recommend_game.py b/bot/seasons/evergreen/recommend_game.py index 30a538a5..398da376 100644 --- a/bot/seasons/evergreen/recommend_game.py +++ b/bot/seasons/evergreen/recommend_game.py @@ -37,7 +37,8 @@ class RecommendGame(commands.Cog): # Creating and formatting Embed embed = discord.Embed(color=discord.Colour.blue()) - embed.set_author(name=author.name, icon_url=author.avatar_url) + if author is not None: + embed.set_author(name=author.name, icon_url=author.avatar_url) embed.set_image(url=game['image']) embed.add_field(name='Recommendation: ' + game['title'] + '\n' + game['link'], value=game['description']) -- cgit v1.2.3 From 5d97116f3f4ed038248688937cbaa0f34a6ebac0 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sat, 17 Aug 2019 21:11:23 +0800 Subject: Implement randomcase --- bot/seasons/evergreen/fun.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index ce3484f7..9aa2ddc5 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -2,6 +2,7 @@ import logging import random from discord.ext import commands +from discord.ext.commands import Context, MessageConverter from bot.constants import Emojis @@ -27,6 +28,36 @@ class Fun(commands.Cog): output += getattr(Emojis, terning, '') await ctx.send(output) + @commands.group(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",), invoke_without_command=True) + async def randomcase_group(self, ctx: Context, *, text: str) -> None: + """Commands for returning text in randomcase""" + await ctx.invoke(self.convert_randomcase, text=text) + + @randomcase_group.command(name="convert") + async def convert_randomcase(self, ctx: Context, *, text: str) -> None: + """Randomly converts the casing of a given `text`""" + text = await Fun.text_to_message(ctx, text) + converted = ( + char.upper() if round(random.random()) else char.lower() for char in text + ) + await ctx.send(f">>> {''.join(converted)}") + + @staticmethod + async def text_to_message(ctx: Context, text: str) -> str: + """ + Attempts to convert a given `text` to a discord Message, then return the contents. + + Returns `text` if the conversion fails. + """ + try: + message = await MessageConverter().convert(ctx, text) + except commands.BadArgument: + log.debug(f"Input {text} is not a valid Discord Message") + else: + text = message.content + finally: + return text + def setup(bot): """Fun Cog load.""" -- cgit v1.2.3 From 31fdda9006516251f424db5f49ff27515367a598 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sat, 17 Aug 2019 21:13:32 +0800 Subject: Update function annotations for existing methods --- bot/seasons/evergreen/fun.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 9aa2ddc5..0b68357b 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -2,21 +2,21 @@ import logging import random from discord.ext import commands -from discord.ext.commands import Context, MessageConverter +from discord.ext.commands import Bot, Cog, Context, MessageConverter from bot.constants import Emojis log = logging.getLogger(__name__) -class Fun(commands.Cog): +class Fun(Cog): """A collection of general commands for fun.""" - def __init__(self, bot): + def __init__(self, bot: Bot) -> None: self.bot = bot @commands.command() - async def roll(self, ctx, num_rolls: int = 1): + async def roll(self, ctx: Context, num_rolls: int = 1) -> None: """Outputs a number of random dice emotes (up to 6).""" output = "" if num_rolls > 6: -- cgit v1.2.3 From f799d03756e4dd61c9407baa552409adc482e212 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sun, 18 Aug 2019 10:05:54 +0800 Subject: Add utils function to replace multiple words in a given string --- bot/utils/__init__.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) (limited to 'bot') diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index ef18a1b9..ad019357 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -1,4 +1,6 @@ import asyncio +import re +import string from typing import List import discord @@ -77,3 +79,57 @@ async def disambiguate( return entries[index - 1] except IndexError: raise BadArgument('Invalid choice.') + + +def replace_many( + sentence: str, replacements: dict, *, ignore_case: bool = False, match_case: bool = False +) -> str: + """ + Replaces multiple substrings in a string given a mapping of strings. + + By default replaces long strings before short strings, and lowercase before uppercase. + Example: + var = replace_many("This is a sentence", {"is": "was", "This": "That"}) + assert var == "That was a sentence" + + If `ignore_case` is given, does a case insensitive match. + Example: + var = replace_many("THIS is a sentence", {"IS": "was", "tHiS": "That"}, ignore_case=True) + assert var == "That was a sentence" + + If `match_case` is given, matches the case of the replacement with the replaced word. + Example: + var = replace_many( + "This IS a sentence", {"is": "was", "this": "that"}, ignore_case=True, match_case=True + ) + assert var == "That WAS a sentence" + """ + if ignore_case: + replacements = dict( + (word.lower(), replacement) for word, replacement in replacements.items() + ) + + words_to_replace = sorted(replacements, key=lambda s: (-len(s), s)) + + # Join and compile words to replace into a regex + pattern = "|".join(re.escape(word) for word in words_to_replace) + regex = re.compile(pattern, re.I if ignore_case else 0) + + def _repl(match): + """Returns replacement depending on `ignore_case` and `match_case`""" + word = match.group(0) + replacement = replacements[word.lower() if ignore_case else word] + + if not match_case: + return replacement + + # Clean punctuation from word so string methods work + cleaned_word = word.translate(str.maketrans('', '', string.punctuation)) + if cleaned_word.isupper(): + return replacement.upper() + elif cleaned_word[0].isupper(): + return replacement.capitalize() + else: + return replacement.lower() + + return regex.sub(_repl, sentence) -- cgit v1.2.3 From af54aab6e37d6fb994f9a47d6243ebb7591b03f4 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sun, 18 Aug 2019 12:55:52 +0800 Subject: Implement uwu --- bot/seasons/evergreen/fun.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 0b68357b..2ee3f2cb 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -4,10 +4,22 @@ import random from discord.ext import commands from discord.ext.commands import Bot, Cog, Context, MessageConverter +from bot import utils from bot.constants import Emojis log = logging.getLogger(__name__) +UWU_WORDS = { + "fi": "fwi", + "l": "w", + "r": "w", + "th": "d", + "tho": "fo", + "you're": "yuw'we", + "your": "yur", + "you": "yuw", +} + class Fun(Cog): """A collection of general commands for fun.""" @@ -28,14 +40,26 @@ class Fun(Cog): output += getattr(Emojis, terning, '') await ctx.send(output) + @commands.group(name="uwu", aliases=("uwuwize", "uwuify",), invoke_without_command=True) + async def uwu_group(self, ctx: Context, *, text: str) -> None: + """Commands for making text uwu.""" + await ctx.invoke(self.convert_uwu, text=text) + + @uwu_group.command(name="convert") + async def convert_uwu(self, ctx: Context, *, text: str) -> None: + """Converts a given `text` into it's uwu equivalent.""" + text = await Fun.text_to_message(ctx, text) + converted = utils.replace_many(text, UWU_WORDS, ignore_case=True, match_case=True) + await ctx.send(f">>> {converted}") + @commands.group(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",), invoke_without_command=True) async def randomcase_group(self, ctx: Context, *, text: str) -> None: - """Commands for returning text in randomcase""" + """Commands for returning text in randomcase.""" await ctx.invoke(self.convert_randomcase, text=text) @randomcase_group.command(name="convert") async def convert_randomcase(self, ctx: Context, *, text: str) -> None: - """Randomly converts the casing of a given `text`""" + """Randomly converts the casing of a given `text`.""" text = await Fun.text_to_message(ctx, text) converted = ( char.upper() if round(random.random()) else char.lower() for char in text @@ -52,7 +76,7 @@ class Fun(Cog): try: message = await MessageConverter().convert(ctx, text) except commands.BadArgument: - log.debug(f"Input {text} is not a valid Discord Message") + log.debug(f"Input '{text:.20}...' is not a valid Discord Message") else: text = message.content finally: -- cgit v1.2.3 From 843e82533cbc5a79d4c3d31c0a204110270e68ee Mon Sep 17 00:00:00 2001 From: jakeHebert Date: Sun, 18 Aug 2019 06:10:51 -0400 Subject: added function annotations --- bot/seasons/evergreen/recommend_game.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/recommend_game.py b/bot/seasons/evergreen/recommend_game.py index 398da376..835a4e53 100644 --- a/bot/seasons/evergreen/recommend_game.py +++ b/bot/seasons/evergreen/recommend_game.py @@ -20,12 +20,12 @@ shuffle(game_recs) class RecommendGame(commands.Cog): """Commands related to recommending games.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot self.index = 0 @commands.command(name="recommendgame", aliases=['gamerec']) - async def recommend_game(self, ctx): + async def recommend_game(self, ctx: commands.Context) -> None: """Sends an Embed of a random game recommendation.""" if self.index >= len(game_recs): self.index = 0 @@ -45,7 +45,7 @@ class RecommendGame(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Loads the RecommendGame cog.""" bot.add_cog(RecommendGame(bot)) log.info("RecommendGame cog loaded") -- cgit v1.2.3 From fd6e975d6dfe587a534b081c7853f04201f22ed4 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 19 Aug 2019 20:10:48 +0800 Subject: Replace paramater and return value docstring documentation with an extended summary --- bot/pagination.py | 83 +++++++----------------------- bot/seasons/evergreen/snakes/snakes_cog.py | 18 ++----- bot/seasons/evergreen/snakes/utils.py | 16 +----- bot/seasons/halloween/monstersurvey.py | 17 ++---- bot/seasons/valentines/be_my_valentine.py | 5 +- bot/utils/__init__.py | 16 ++---- 6 files changed, 34 insertions(+), 121 deletions(-) (limited to 'bot') diff --git a/bot/pagination.py b/bot/pagination.py index e6cea41f..212f9f4e 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -22,26 +22,16 @@ class EmptyPaginatorEmbed(Exception): class LinePaginator(Paginator): - """ - A class that aids in paginating code blocks for Discord messages. - - Attributes - ----------- - prefix: :class:`str` - The prefix inserted to every page. e.g. three backticks. - suffix: :class:`str` - The suffix appended at the end of every page. e.g. three backticks. - max_size: :class:`int` - The maximum amount of codepoints allowed in a page. - max_lines: :class:`int` - The maximum amount of lines allowed in a page. - """ + """A class that aids in paginating code blocks for Discord messages.""" def __init__(self, prefix='```', suffix='```', max_size=2000, max_lines=None): """ Overrides the Paginator.__init__ from inside discord.ext.commands. - Allows for configuration of the maximum number of lines per page. + `prefix` and `suffix` will be prepended and appended respectively to every page. + + `max_size` and `max_lines` denote the maximum amount of codepoints and lines + allowed per page. """ self.prefix = prefix self.suffix = suffix @@ -56,22 +46,12 @@ class LinePaginator(Paginator): """ Adds a line to the current page. - If the line exceeds the `max_size` then an exception is raised. + If the line exceeds the `max_size` then a RuntimeError is raised. Overrides the Paginator.add_line from inside discord.ext.commands in order to allow configuration of the maximum number of lines per page. - Parameters - ----------- - line: str - The line to add. - empty: bool - Indicates if another empty line should be added. - - Raises - ------ - RuntimeError - The line was too big for the current `max_size`. + If `empty` is True, an empty line will be placed after the a given `line`. """ if len(line) > self.max_size - len(self.prefix) - 2: raise RuntimeError('Line exceeds maximum page size %s' % (self.max_size - len(self.prefix) - 2)) @@ -105,7 +85,11 @@ class LinePaginator(Paginator): When used, this will send a message using `ctx.send()` and apply a set of reactions to it. These reactions may be used to change page, or to remove pagination from the message. - Pagination will also be removed automatically if no reaction is added for five minutes (300 seconds). + + Pagination will also be removed automatically if no reaction is added for `timeout` seconds, + defaulting to five minutes (300 seconds). + + If `empty` is True, an empty line will be placed between each given line. >>> embed = Embed() >>> embed.set_author(name="Some Operation", url=url, icon_url=icon) @@ -113,20 +97,6 @@ class LinePaginator(Paginator): ... (line for line in lines), ... ctx, embed ... ) - - :param lines: The lines to be paginated - :param ctx: Current context object - :param embed: A pre-configured embed to be used as a template for each page - :param prefix: Text to place before each page - :param suffix: Text to place after each page - :param max_lines: The maximum number of lines on each page - :param max_size: The maximum number of characters on each page - :param empty: Whether to place an empty line between each given line - :param restrict_to_user: A user to lock pagination operations to for this message, if supplied - :param exception_on_empty_embed: Should there be an exception if the embed is empty? - :param url: the url to use for the embed headline - :param timeout: The amount of time in seconds to disable pagination of no reaction is added - :param footer_text: Text to prefix the page number in the footer with """ def event_check(reaction_: Reaction, user_: Member): """Make sure that this reaction is what we want to operate on.""" @@ -314,8 +284,7 @@ class ImagePaginator(Paginator): """ Adds a line to each page, usually just 1 line in this context. - :param line: str to be page content / title - :param empty: if there should be new lines between entries + If `empty` is True, an empty line will be placed after a given `line`. """ if line: self._count = len(line) @@ -326,9 +295,7 @@ class ImagePaginator(Paginator): def add_image(self, image: str = None) -> None: """ - Adds an image to a page. - - :param image: image url to be appended + Adds an image to a page given the url. """ self.images.append(image) @@ -339,33 +306,21 @@ class ImagePaginator(Paginator): """ Use a paginator and set of reactions to provide pagination over a set of title/image pairs. - The reactions are used to switch page, or to finish with pagination. + `pages` is a list of tuples of page title/image url pairs. + `prefix` and `suffix` will be prepended and appended respectively to the message. When used, this will send a message using `ctx.send()` and apply a set of reactions to it. These reactions may be used to change page, or to remove pagination from the message. - Note: Pagination will be removed automatically if no reaction is added for five minutes (300 seconds). + Note: Pagination will be removed automatically if no reaction is added for `timeout` seconds, + defaulting to five minutes (300 seconds). >>> embed = Embed() >>> embed.set_author(name="Some Operation", url=url, icon_url=icon) >>> await ImagePaginator.paginate(pages, ctx, embed) - - Parameters - ----------- - :param pages: An iterable of tuples with title for page, and img url - :param ctx: ctx for message - :param embed: base embed to modify - :param prefix: prefix of message - :param suffix: suffix of message - :param timeout: timeout for when reactions get auto-removed """ def check_event(reaction_: Reaction, member: Member) -> bool: - """ - Checks each reaction added, if it matches our conditions pass the wait_for. - - :param reaction_: reaction added - :param member: reaction added by member - """ + """Checks each reaction added, if it matches our conditions pass the wait_for.""" return all(( # Reaction is on the same message sent reaction_.message.id == message.id, diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py index 1d138aff..6924a552 100644 --- a/bot/seasons/evergreen/snakes/snakes_cog.py +++ b/bot/seasons/evergreen/snakes/snakes_cog.py @@ -303,9 +303,6 @@ class Snakes(Cog): Builds a dict that the .get() method can use. Created by Ava and eivl. - - :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 = {} @@ -631,14 +628,10 @@ class Snakes(Cog): @snakes_group.command(name='get') @bot_has_permissions(manage_messages=True) @locked() - async def get_command(self, ctx: Context, *, name: Snake = None): + async def get_command(self, ctx: Context, *, name: Snake = None) -> None: """ Fetches information about a snake from Wikipedia. - :param ctx: Context object passed from discord.py - :param name: Optional, the name of the snake to get information - for - omit for a random snake - Created by Ava and eivl. """ with ctx.typing(): @@ -1034,10 +1027,8 @@ class Snakes(Cog): """ How would I talk if I were a snake? - :param ctx: context - :param message: If this is passed, it will snakify the message. - If not, it will snakify a random message from - the users history. + If `message` is passed, the bot will snakify the message. + Otherwise, a random message from the user's history is snakified. Written by Momo and kel. Modified by lemon. @@ -1077,8 +1068,7 @@ class Snakes(Cog): """ Gets a YouTube video about snakes. - :param ctx: Context object passed from discord.py - :param search: Optional, a name of a snake. Used to search for videos with that name + If `search` is given, a snake with that name will be searched on Youtube. Written by Andrew and Prithaj. """ diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index 5d3b0dee..e8d2ee44 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -285,20 +285,8 @@ def create_snek_frame( """ Creates a single random snek frame using Perlin noise. - :param perlin_factory: the perlin noise factory used. Required. - :param perlin_lookup_vertical_shift: the Perlin noise shift in the Y-dimension for this frame - :param image_dimensions: the size of the output image. - :param image_margins: the margins to respect inside of the image. - :param snake_length: the length of the snake, in segments. - :param snake_color: the color of the snake. - :param bg_color: the background color. - :param segment_length_range: the range of the segment length. Values will be generated inside - this range, including the bounds. - :param snake_width: the width of the snek, in pixels. - :param text: the text to display with the snek. Set to None for no text. - :param text_position: the position of the text. - :param text_color: the color of the text. - :return: a PIL image, representing a single frame. + `perlin_lookup_vertical_shift` represents the Perlin noise shift in the Y-dimension for this frame. + If `text` is given, display the given text with the snek. """ 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]) diff --git a/bot/seasons/halloween/monstersurvey.py b/bot/seasons/halloween/monstersurvey.py index 4e967cca..05e86628 100644 --- a/bot/seasons/halloween/monstersurvey.py +++ b/bot/seasons/halloween/monstersurvey.py @@ -36,15 +36,11 @@ class MonsterSurvey(Cog): with open(self.registry_location, 'w') as jason: json.dump(self.voter_registry, jason, indent=2) - def cast_vote(self, id: int, monster: str): + def cast_vote(self, id: int, monster: str) -> None: """ Cast a user's vote for the specified monster. If the user has already voted, their existing vote is removed. - - :param id: The id of the person voting - :param monster: the string key of the json that represents a monster - :return: None """ vr = self.voter_registry for m in vr.keys(): @@ -147,13 +143,9 @@ class MonsterSurvey(Cog): @monster_group.command( name='show' ) - async def monster_show(self, ctx: Context, name=None): + async def monster_show(self, ctx: Context, name=None) -> None: """ Shows the named monster. If one is not named, it sends the default voting embed instead. - - :param ctx: - :param name: - :return: """ if name is None: await ctx.invoke(self.monster_leaderboard) @@ -184,12 +176,9 @@ class MonsterSurvey(Cog): name='leaderboard', aliases=('lb',) ) - async def monster_leaderboard(self, ctx: Context): + async def monster_leaderboard(self, ctx: Context) -> None: """ Shows the current standings. - - :param ctx: - :return: """ async with ctx.typing(): vr = self.voter_registry diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index ac140896..c4acf17a 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -184,14 +184,11 @@ class BeMyValentine(commands.Cog): return valentine, title @staticmethod - def random_user(author, members): + def random_user(author: discord.Member, members: discord.Member): """ Picks a random member from the list provided in `members`. The invoking author is ignored. - - :param author: member who invoked the command - :param members: list of discord.Member objects """ if author in members: members.remove(author) diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index ef18a1b9..15c4b5db 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -9,21 +9,15 @@ from bot.pagination import LinePaginator async def disambiguate( ctx: Context, entries: List[str], *, timeout: float = 30, - per_page: int = 20, empty: bool = False, embed: discord.Embed = None -): + entries_per_page: int = 20, empty: bool = False, embed: discord.Embed = None +) -> str: """ Has the user choose between multiple entries in case one could not be chosen automatically. + Disambiguation will be canceled after `timeout` seconds. + This will raise a BadArgument if entries is empty, if the disambiguation event times out, or if the user makes an invalid choice. - - :param ctx: Context object from discord.py - :param entries: List of items for user to choose from - :param timeout: Number of seconds to wait before canceling disambiguation - :param per_page: Entries per embed page - :param empty: Whether the paginator should have an extra line between items - :param embed: The embed that the paginator will use. - :return: Users choice for correct entry. """ if len(entries) == 0: raise BadArgument('No matches found.') @@ -43,7 +37,7 @@ async def disambiguate( embed = discord.Embed() coro1 = ctx.bot.wait_for('message', check=check, timeout=timeout) - coro2 = LinePaginator.paginate(choices, ctx, embed=embed, max_lines=per_page, + coro2 = LinePaginator.paginate(choices, ctx, embed=embed, max_lines=entries_per_page, empty=empty, max_size=6000, timeout=9000) # wait_for timeout will go to except instead of the wait_for thing as I expected -- cgit v1.2.3 From cbb1cbe3d61fd83412d20a3ec681879d8a3f2462 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Tue, 20 Aug 2019 13:13:56 +0800 Subject: Fix lint errors --- bot/pagination.py | 4 +--- bot/seasons/halloween/monstersurvey.py | 8 ++------ 2 files changed, 3 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/pagination.py b/bot/pagination.py index 212f9f4e..c12b6233 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -294,9 +294,7 @@ class ImagePaginator(Paginator): self.close_page() def add_image(self, image: str = None) -> None: - """ - Adds an image to a page given the url. - """ + """Adds an image to a page given the url.""" self.images.append(image) @classmethod diff --git a/bot/seasons/halloween/monstersurvey.py b/bot/seasons/halloween/monstersurvey.py index 05e86628..173ce8eb 100644 --- a/bot/seasons/halloween/monstersurvey.py +++ b/bot/seasons/halloween/monstersurvey.py @@ -144,9 +144,7 @@ class MonsterSurvey(Cog): name='show' ) async def monster_show(self, ctx: Context, name=None) -> None: - """ - Shows the named monster. If one is not named, it sends the default voting embed instead. - """ + """Shows the named monster. If one is not named, it sends the default voting embed instead.""" if name is None: await ctx.invoke(self.monster_leaderboard) return @@ -177,9 +175,7 @@ class MonsterSurvey(Cog): aliases=('lb',) ) async def monster_leaderboard(self, ctx: Context) -> None: - """ - Shows the current standings. - """ + """Shows the current standings.""" async with ctx.typing(): vr = self.voter_registry top = sorted(vr, key=lambda k: len(vr[k]['votes']), reverse=True) -- cgit v1.2.3 From fd072edee2a8ec0983ddfca1963c3105e0aa0624 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Tue, 20 Aug 2019 18:30:14 +0800 Subject: Remove missed return doc --- bot/seasons/evergreen/snakes/snakes_cog.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py index 6924a552..38878706 100644 --- a/bot/seasons/evergreen/snakes/snakes_cog.py +++ b/bot/seasons/evergreen/snakes/snakes_cog.py @@ -400,11 +400,7 @@ class Snakes(Cog): return snake_info async def _get_snake_name(self) -> Dict[str, str]: - """ - Gets a random snake name. - - :return: A random snake name, as a string. - """ + """Gets a random snake name.""" return random.choice(self.snake_names) async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list): -- cgit v1.2.3 From 0be246e127df91f7e23ac41ef285b39fa8f52353 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 21 Aug 2019 19:00:59 +0200 Subject: making sure a game is always has a way to win no matter how hard it is. --- bot/seasons/evergreen/minesweeper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index c99cefba..5c88be0d 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -1,7 +1,7 @@ import logging import typing from dataclasses import dataclass -from random import random +from random import random, randint import discord from discord.ext import commands @@ -99,6 +99,10 @@ class Minesweeper(commands.Cog): for _ in range(10) ] for _ in range(10) ] + + # make sure there is always a free cell + board[randint(0, 9)][randint(0, 9)] = "number" + for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "number": -- cgit v1.2.3 From 575ecfdbc1a96c42f0bfd0941bb5a8dd3bf83e5e Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Wed, 21 Aug 2019 19:09:07 +0200 Subject: fixed import order. --- bot/seasons/evergreen/minesweeper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 5c88be0d..f6fa9c47 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -1,7 +1,7 @@ import logging import typing from dataclasses import dataclass -from random import random, randint +from random import randint, random import discord from discord.ext import commands -- cgit v1.2.3 From 502250b60827f2e60a815ad53bbeb13ede0a92f5 Mon Sep 17 00:00:00 2001 From: vivax3794 <51753506+vivax3794@users.noreply.github.com> Date: Sat, 24 Aug 2019 16:48:05 +0200 Subject: changed board revealing at end of games. --- bot/seasons/evergreen/minesweeper.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index f6fa9c47..cb859ea9 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -22,7 +22,8 @@ MESSAGE_MAPPING = { 10: ":keycap_ten:", "bomb": ":bomb:", "hidden": ":grey_question:", - "flag": ":flag_black:" + "flag": ":flag_black:", + "x": ":x:" } log = logging.getLogger(__name__) @@ -182,10 +183,18 @@ class Minesweeper(commands.Cog): await self.update_boards(ctx) + @staticmethod + def reveal_bombs(revealed: GameBoard, board: GameBoard) -> None: + """Reveals all the bombs""" + for y, row in enumerate(board): + for x, cell in enumerate(row): + if cell == "bomb": + revealed[y][x] = cell + async def lost(self, ctx: commands.Context) -> None: """The player lost the game""" game = self.games[ctx.author.id] - game.revealed = game.board + self.reveal_bombs(game.revealed, game.board) await ctx.author.send(":fire: You lost! :fire:") if game.activated_on_server: await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:") @@ -193,7 +202,6 @@ class Minesweeper(commands.Cog): async def won(self, ctx: commands.Context) -> None: """The player won the game""" game = self.games[ctx.author.id] - game.revealed = game.board await ctx.author.send(":tada: You won! :tada:") if game.activated_on_server: await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won Minesweeper! :tada:") @@ -235,6 +243,7 @@ class Minesweeper(commands.Cog): revealed[y][x] = board[y][x] if board[y][x] == "bomb": await self.lost(ctx) + revealed[y][x] = "x" # mark bomb that made you lose with a x return True elif board[y][x] == 0: self.reveal_zeros(revealed, board, x, y) -- cgit v1.2.3 From 116cb5cf925c60dfc9cb86335133593a6ea3aba6 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 29 Aug 2019 18:35:39 +0800 Subject: Remove redundant command group --- bot/seasons/evergreen/fun.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 2ee3f2cb..d2ebb444 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -14,6 +14,7 @@ UWU_WORDS = { "l": "w", "r": "w", "th": "d", + "thing": "fing", "tho": "fo", "you're": "yuw'we", "your": "yur", @@ -40,25 +41,15 @@ class Fun(Cog): output += getattr(Emojis, terning, '') await ctx.send(output) - @commands.group(name="uwu", aliases=("uwuwize", "uwuify",), invoke_without_command=True) - async def uwu_group(self, ctx: Context, *, text: str) -> None: - """Commands for making text uwu.""" - await ctx.invoke(self.convert_uwu, text=text) - - @uwu_group.command(name="convert") - async def convert_uwu(self, ctx: Context, *, text: str) -> None: + @commands.command(name="uwu", aliases=("uwuwize", "uwuify",)) + async def uwu_command(self, ctx: Context, *, text: str) -> None: """Converts a given `text` into it's uwu equivalent.""" text = await Fun.text_to_message(ctx, text) converted = utils.replace_many(text, UWU_WORDS, ignore_case=True, match_case=True) await ctx.send(f">>> {converted}") - @commands.group(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",), invoke_without_command=True) - async def randomcase_group(self, ctx: Context, *, text: str) -> None: - """Commands for returning text in randomcase.""" - await ctx.invoke(self.convert_randomcase, text=text) - - @randomcase_group.command(name="convert") - async def convert_randomcase(self, ctx: Context, *, text: str) -> None: + @commands.command(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",)) + async def randomcase_command(self, ctx: Context, *, text: str) -> None: """Randomly converts the casing of a given `text`.""" text = await Fun.text_to_message(ctx, text) converted = ( @@ -83,7 +74,7 @@ class Fun(Cog): return text -def setup(bot): +def setup(bot) -> None: """Fun Cog load.""" bot.add_cog(Fun(bot)) log.info("Fun cog loaded") -- cgit v1.2.3 From 1cdbfef7d1a6425cde555d77f47a4b786b947755 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 29 Aug 2019 18:42:58 +0800 Subject: Improve wording of message conversion function --- bot/seasons/evergreen/fun.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index d2ebb444..87077f36 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -44,23 +44,26 @@ class Fun(Cog): @commands.command(name="uwu", aliases=("uwuwize", "uwuify",)) async def uwu_command(self, ctx: Context, *, text: str) -> None: """Converts a given `text` into it's uwu equivalent.""" - text = await Fun.text_to_message(ctx, text) + text = await Fun.get_discord_message(ctx, text) converted = utils.replace_many(text, UWU_WORDS, ignore_case=True, match_case=True) await ctx.send(f">>> {converted}") @commands.command(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",)) async def randomcase_command(self, ctx: Context, *, text: str) -> None: """Randomly converts the casing of a given `text`.""" - text = await Fun.text_to_message(ctx, text) + text = await Fun.get_discord_message(ctx, text) converted = ( char.upper() if round(random.random()) else char.lower() for char in text ) await ctx.send(f">>> {''.join(converted)}") @staticmethod - async def text_to_message(ctx: Context, text: str) -> str: + async def get_discord_message(ctx: Context, text: str) -> str: """ - Attempts to convert a given `text` to a discord Message, then return the contents. + Attempts to convert a given `text` to a discord Message object, then return the contents. + + Useful if the user enters a link or an id to a valid Discord message, because the contents + of the message get returned. Returns `text` if the conversion fails. """ -- cgit v1.2.3 From db75c8c7906c3d079d3283a68e77c929603b0ba4 Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 4 Sep 2019 12:13:47 -0400 Subject: Resolve alias conflict between minsweeper & monster survey --- bot/seasons/halloween/monstersurvey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/halloween/monstersurvey.py b/bot/seasons/halloween/monstersurvey.py index 173ce8eb..6e71a007 100644 --- a/bot/seasons/halloween/monstersurvey.py +++ b/bot/seasons/halloween/monstersurvey.py @@ -60,7 +60,7 @@ class MonsterSurvey(Cog): @commands.group( name='monster', - aliases=('ms',) + aliases=('mon',) ) async def monster_group(self, ctx: Context): """The base voting command. If nothing is called, then it will return an embed.""" -- cgit v1.2.3 From 50639c94922a65b5a1509200a46c6a6b61e8058a Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 4 Sep 2019 16:42:28 -0400 Subject: Hacktober cog cleanup * Clean up type hinting * Fix command typo in account linking helper message * Add constant & generic handling for number of PRs to shirt * Add constant & generic handling for event year in GH API query --- bot/seasons/halloween/hacktoberstats.py | 49 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 25 deletions(-) (limited to 'bot') diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index b6b5a900..5687a5c7 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -1,10 +1,10 @@ import json import logging import re -import typing from collections import Counter from datetime import datetime from pathlib import Path +from typing import List, Tuple import aiohttp import discord @@ -12,6 +12,9 @@ from discord.ext import commands log = logging.getLogger(__name__) +CURRENT_YEAR = datetime.now().year # Used to construct GH API query +PRS_FOR_SHIRT = 4 # Minimum number of PRs before a shirt is awarded + class HacktoberStats(commands.Cog): """Hacktoberfest statistics Cog.""" @@ -21,12 +24,8 @@ class HacktoberStats(commands.Cog): self.link_json = Path("bot/resources/github_links.json") self.linked_accounts = self.load_linked_users() - @commands.group( - name='hacktoberstats', - aliases=('hackstats',), - invoke_without_command=True - ) - async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None): + @commands.group(name="hacktoberstats", aliases=("hackstats",), invoke_without_command=True) + async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None) -> None: """ Display an embed for a user's Hacktoberfest contributions. @@ -43,8 +42,8 @@ class HacktoberStats(commands.Cog): else: msg = ( f"{author_mention}, you have not linked a GitHub account\n\n" - f"You can link your GitHub account using:\n```{ctx.prefix}stats link github_username```\n" - f"Or query GitHub stats directly using:\n```{ctx.prefix}stats github_username```" + f"You can link your GitHub account using:\n```{ctx.prefix}hackstats link github_username```\n" + f"Or query GitHub stats directly using:\n```{ctx.prefix}hackstats github_username```" ) await ctx.send(msg) return @@ -52,7 +51,7 @@ class HacktoberStats(commands.Cog): await self.get_stats(ctx, github_username) @hacktoberstats_group.command(name="link") - async def link_user(self, ctx: commands.Context, github_username: str = None): + async def link_user(self, ctx: commands.Context, github_username: str = None) -> None: """ Link the invoking user's Github github_username to their Discord ID. @@ -85,7 +84,7 @@ class HacktoberStats(commands.Cog): await ctx.send(f"{author_mention}, a GitHub username is required to link your account") @hacktoberstats_group.command(name="unlink") - async def unlink_user(self, ctx: commands.Context): + async def unlink_user(self, ctx: commands.Context) -> None: """Remove the invoking user's account link from the log.""" author_id, author_mention = HacktoberStats._author_mention_from_context(ctx) @@ -99,7 +98,7 @@ class HacktoberStats(commands.Cog): self.save_linked_users() - def load_linked_users(self) -> typing.Dict: + def load_linked_users(self) -> dict: """ Load list of linked users from local JSON file. @@ -122,7 +121,7 @@ class HacktoberStats(commands.Cog): logging.info(f"Linked account log: '{self.link_json}' does not exist") return {} - def save_linked_users(self): + def save_linked_users(self) -> None: """ Save list of linked users to local JSON file. @@ -139,7 +138,7 @@ class HacktoberStats(commands.Cog): json.dump(self.linked_accounts, fID, default=str) logging.info(f"linked_accounts saved to '{self.link_json}'") - async def get_stats(self, ctx: commands.Context, github_username: str): + async def get_stats(self, ctx: commands.Context, github_username: str) -> None: """ Query GitHub's API for PRs created by a GitHub user during the month of October. @@ -158,18 +157,18 @@ class HacktoberStats(commands.Cog): else: await ctx.send(f"No October GitHub contributions found for '{github_username}'") - def build_embed(self, github_username: str, prs: typing.List[dict]) -> discord.Embed: + def build_embed(self, github_username: str, prs: List[dict]) -> discord.Embed: """Return a stats embed built from github_username's PRs.""" logging.info(f"Building Hacktoberfest embed for GitHub user: '{github_username}'") pr_stats = self._summarize_prs(prs) n = pr_stats['n_prs'] - if n >= 5: + if n >= PRS_FOR_SHIRT: shirtstr = f"**{github_username} has earned a tshirt!**" - elif n == 4: + elif n == PRS_FOR_SHIRT - 1: shirtstr = f"**{github_username} is 1 PR away from a tshirt!**" else: - shirtstr = f"**{github_username} is {5 - n} PRs away from a tshirt!**" + shirtstr = f"**{github_username} is {PRS_FOR_SHIRT - n} PRs away from a tshirt!**" stats_embed = discord.Embed( title=f"{github_username}'s Hacktoberfest", @@ -186,7 +185,7 @@ class HacktoberStats(commands.Cog): stats_embed.set_author( name="Hacktoberfest", url="https://hacktoberfest.digitalocean.com", - icon_url="https://hacktoberfest.digitalocean.com/assets/logo-hacktoberfest.png" + icon_url="https://hacktoberfest.digitalocean.com/pretty_logo.png" ) stats_embed.add_field( name="Top 5 Repositories:", @@ -197,7 +196,7 @@ class HacktoberStats(commands.Cog): return stats_embed @staticmethod - async def get_october_prs(github_username: str) -> typing.List[dict]: + async def get_october_prs(github_username: str) -> List[dict]: """ Query GitHub's API for PRs created during the month of October by github_username. @@ -219,7 +218,7 @@ class HacktoberStats(commands.Cog): not_label = "invalid" action_type = "pr" is_query = f"public+author:{github_username}" - date_range = "2018-10-01..2018-10-31" + date_range = f"{CURRENT_YEAR}-10-01..{CURRENT_YEAR}-10-31" per_page = "300" query_url = ( f"{base_url}" @@ -274,7 +273,7 @@ class HacktoberStats(commands.Cog): return re.findall(exp, in_url)[0] @staticmethod - def _summarize_prs(prs: typing.List[dict]) -> typing.Dict: + def _summarize_prs(prs: List[dict]) -> dict: """ Generate statistics from an input list of PR dictionaries, as output by get_october_prs. @@ -288,7 +287,7 @@ class HacktoberStats(commands.Cog): return {"n_prs": len(prs), "top5": Counter(contributed_repos).most_common(5)} @staticmethod - def _build_top5str(stats: typing.List[tuple]) -> str: + def _build_top5str(stats: List[tuple]) -> str: """ Build a string from the Top 5 contributions that is compatible with a discord.Embed field. @@ -316,7 +315,7 @@ class HacktoberStats(commands.Cog): return "contributions" @staticmethod - def _author_mention_from_context(ctx: commands.Context) -> typing.Tuple: + def _author_mention_from_context(ctx: commands.Context) -> Tuple: """Return stringified Message author ID and mentionable string from commands.Context.""" author_id = str(ctx.message.author.id) author_mention = ctx.message.author.mention @@ -324,7 +323,7 @@ class HacktoberStats(commands.Cog): return author_id, author_mention -def setup(bot): +def setup(bot): # Noqa """Hacktoberstats Cog load.""" bot.add_cog(HacktoberStats(bot)) log.info("HacktoberStats cog loaded") -- cgit v1.2.3 From 26361e1a162a29a0cda8a8c5f3ea99c784fe8984 Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 4 Sep 2019 18:43:27 -0400 Subject: Update Snakes and Ladders * Fix missing await preventing a non-started game from cancelling when timing out when waiting for the game to start * Remove restriction on minimum number of players required for the game to run. This applies to both starting the game and for ending the game if players leave; the game can now be played solo. * Prevent cancellation of the game if the player who initiated the game leaves but there are still people playing * Allow Moderation staff to cancel a game they are not part of * Fix issue where a game is not being properly ended if all the players leave --- bot/seasons/evergreen/snakes/utils.py | 80 +++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 37 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index e8d2ee44..f6b5ea16 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -13,6 +13,8 @@ from PIL.ImageDraw import ImageDraw from discord import File, Member, Reaction from discord.ext.commands import Context +from bot.constants import Roles + SNAKE_RESOURCES = Path("bot/resources/snakes").absolute() h1 = r'''``` @@ -412,7 +414,6 @@ class SnakeAndLaddersGame: "**Snakes and Ladders**: A new game is about to start!", file=File( str(SNAKE_RESOURCES / "snakes_and_ladders" / "banner.jpg"), - # os.path.join("bot", "resources", "snakes", "snakes_and_ladders", "banner.jpg"), filename='Snakes and Ladders.jpg' ) ) @@ -435,8 +436,9 @@ class SnakeAndLaddersGame: if reaction.emoji == JOIN_EMOJI: await self.player_join(user) elif reaction.emoji == CANCEL_EMOJI: - if self.ctx.author == user: - await self.cancel_game(user) + if user == self.author or not self._mod_in_game_check(user): + # Allow game author or non-playing moderation staff to cancel a waiting game + await self.cancel_game() return else: await self.player_leave(user) @@ -451,7 +453,7 @@ class SnakeAndLaddersGame: except asyncio.TimeoutError: log.debug("Snakes and Ladders timed out waiting for a reaction") - self.cancel_game(self.author) + await self.cancel_game() return # We're done, no reactions for the last 5 minutes async def _add_player(self, user: Member): @@ -488,20 +490,16 @@ class SnakeAndLaddersGame: delete_after=10 ) - async def player_leave(self, user: Member): + async def player_leave(self, user: Member) -> bool: """ Handle players leaving the game. - Leaving is prevented if the user initiated the game or if they weren't part of it in the - first place. + Leaving is prevented if the user wesn't part of the game. + + If the number of players reaches 0, the game is terminated. In this case, a sentinel boolean + is returned True to prevent a game from continuing after it's destroyed. """ - if user == self.author: - await self.channel.send( - user.mention + " You are the author, and cannot leave the game. Execute " - "`sal cancel` to cancel the game.", - delete_after=10 - ) - return + is_surrendered = False # Sentinel value to assist with stopping a surrendered game for p in self.players: if user == p: self.players.remove(p) @@ -512,17 +510,18 @@ class SnakeAndLaddersGame: delete_after=10 ) - if self.state != 'waiting' and len(self.players) == 1: + if self.state != 'waiting' and len(self.players) == 0: await self.channel.send("**Snakes and Ladders**: The game has been surrendered!") + is_surrendered = True self._destruct() - return - await self.channel.send(user.mention + " You are not in the match.", delete_after=10) - 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 + return is_surrendered + else: + await self.channel.send(user.mention + " You are not in the match.", delete_after=10) + return is_surrendered + + async def cancel_game(self): + """Cancel the running game.""" await self.channel.send("**Snakes and Ladders**: Game has been canceled.") self._destruct() @@ -530,21 +529,16 @@ class SnakeAndLaddersGame: """ Allow the game author to begin the game. - The game cannot be started if there aren't enough players joined or if the game is in a - waiting state. + The game cannot be started 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 - if len(self.players) < 1: - await self.channel.send( - user.mention + " A minimum of 2 players is required to start the game.", - delete_after=10 - ) - return + if not self.state == 'waiting': await self.channel.send(user.mention + " The game cannot be started at this time.", delete_after=10) return + self.state = 'starting' player_list = ', '.join(user.mention for user in self.players) await self.channel.send("**Snakes and Ladders**: The game is starting!\nPlayers: " + player_list) @@ -565,8 +559,6 @@ class SnakeAndLaddersGame: self.state = 'roll' for user in self.players: self.round_has_rolled[user.id] = False - # board_img = Image.open(os.path.join( - # "bot", "resources", "snakes", "snakes_and_ladders", "board.jpg")) board_img = Image.open(str(SNAKE_RESOURCES / "snakes_and_ladders" / "board.jpg")) player_row_size = math.ceil(MAX_PLAYERS / 2) @@ -612,6 +604,7 @@ class SnakeAndLaddersGame: for emoji in GAME_SCREEN_EMOJI: await self.positions.add_reaction(emoji) + is_surrendered = False while True: try: reaction, user = await self.ctx.bot.wait_for( @@ -623,11 +616,12 @@ class SnakeAndLaddersGame: if reaction.emoji == ROLL_EMOJI: await self.player_roll(user) elif reaction.emoji == CANCEL_EMOJI: - if self.ctx.author == user: - await self.cancel_game(user) + if self._is_moderator(user) and not self._mod_in_game_check(user): + # Only allow non-playing moderation staff to cancel a running game + await self.cancel_game() return else: - await self.player_leave(user) + is_surrendered = await self.player_leave(user) await self.positions.remove_reaction(reaction.emoji, user) @@ -636,11 +630,14 @@ class SnakeAndLaddersGame: except asyncio.TimeoutError: log.debug("Snakes and Ladders timed out waiting for a reaction") - await self.cancel_game(self.author) + await self.cancel_game() return # We're done, no reactions for the last 5 minutes # Round completed - await self._complete_round() + # Check to see if the game was surrendered before completing the round, without this + # sentinel, the game object would be deleted but the next round still posted into purgatory + if not is_surrendered: + await self._complete_round() async def player_roll(self, user: Member): """Handle the player's roll.""" @@ -708,3 +705,12 @@ class SnakeAndLaddersGame: if is_reversed: x_level = 9 - x_level return x_level, y_level + + def _mod_in_game_check(self, user: Member) -> bool: + """Return True if user is a Moderator and playing the currently running game.""" + return all((self._is_moderator(user), user in self.players)) + + @staticmethod + def _is_moderator(user: Member) -> bool: + """Return True if the user is a Moderator.""" + return Roles.moderator in [role.id for role in user.roles] -- cgit v1.2.3 From d982254a79ca3e34c9f50e072a925fe6b8a12ed1 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Thu, 5 Sep 2019 18:35:04 -0400 Subject: Fix misconfigured flake8 so docstrings are properly linted Relint --- bot/seasons/christmas/adventofcode.py | 2 +- bot/seasons/easter/avatar_easterifier.py | 2 +- bot/seasons/easter/bunny_name_generator.py | 10 +++++----- bot/seasons/easter/easter_riddle.py | 2 +- bot/seasons/easter/egghead_quiz.py | 6 +++--- bot/seasons/easter/traditions.py | 2 +- bot/seasons/evergreen/8bitify.py | 6 +++--- bot/seasons/evergreen/minesweeper.py | 18 +++++++++--------- bot/seasons/evergreen/showprojects.py | 6 +++--- bot/seasons/evergreen/speedrun.py | 2 +- bot/seasons/halloween/spookyrating.py | 2 +- bot/utils/__init__.py | 2 +- tox.ini | 5 +++-- 13 files changed, 33 insertions(+), 32 deletions(-) (limited to 'bot') diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index a9e72805..d2894ec4 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -359,7 +359,7 @@ class AdventOfCode(commands.Cog): ) async def _check_n_entries(self, ctx: commands.Context, number_of_people_to_display: int) -> int: - """Check for n > max_entries and n <= 0""" + """Check for n > max_entries and n <= 0.""" max_entries = AocConfig.leaderboard_max_displayed_members author = ctx.message.author if not 0 <= number_of_people_to_display <= max_entries: diff --git a/bot/seasons/easter/avatar_easterifier.py b/bot/seasons/easter/avatar_easterifier.py index ad8b5473..98e15982 100644 --- a/bot/seasons/easter/avatar_easterifier.py +++ b/bot/seasons/easter/avatar_easterifier.py @@ -34,7 +34,7 @@ class AvatarEasterifier(commands.Cog): r1, g1, b1 = x def distance(point): - """Finds the difference between a pastel colour and the original pixel colour""" + """Finds the difference between a pastel colour and the original pixel colour.""" r2, g2, b2 = point return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 76d5c478..3ceaeb9e 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -28,8 +28,8 @@ class BunnyNameGenerator(commands.Cog): """ Finds vowels in the user's display name. - If the Discord name contains a vowel and the letter y, - it will match one or more of these patterns. + If the Discord name contains a vowel and the letter y, it will match one or more of these patterns. + Only the most recently matched pattern will apply the changes. """ expressions = [ @@ -46,7 +46,7 @@ class BunnyNameGenerator(commands.Cog): return new_name def append_name(self, displayname): - """Adds a suffix to the end of the Discord name""" + """Adds a suffix to the end of the Discord name.""" extensions = ['foot', 'ear', 'nose', 'tail'] suffix = random.choice(extensions) appended_name = displayname + suffix @@ -55,12 +55,12 @@ class BunnyNameGenerator(commands.Cog): @commands.command() async def bunnyname(self, ctx): - """Picks a random bunny name from a JSON file""" + """Picks a random bunny name from a JSON file.""" await ctx.send(random.choice(BUNNY_NAMES["names"])) @commands.command() async def bunnifyme(self, ctx): - """Gets your Discord username and bunnifies it""" + """Gets your Discord username and bunnifies it.""" username = ctx.message.author.display_name # If name contains spaces or other separators, get the individual words to randomly bunnify diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 56555586..b612f8b9 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -84,7 +84,7 @@ class EasterRiddle(commands.Cog): @commands.Cog.listener() async def on_message(self, message): - """If a non-bot user enters a correct answer, their username gets added to self.winners""" + """If a non-bot user enters a correct answer, their username gets added to self.winners.""" if self.current_channel != message.channel: return diff --git a/bot/seasons/easter/egghead_quiz.py b/bot/seasons/easter/egghead_quiz.py index 3e0cc598..b3841993 100644 --- a/bot/seasons/easter/egghead_quiz.py +++ b/bot/seasons/easter/egghead_quiz.py @@ -37,7 +37,7 @@ class EggheadQuiz(commands.Cog): @commands.command(aliases=["eggheadquiz", "easterquiz"]) async def eggquiz(self, ctx): """ - Gives a random quiz question, waits 30 seconds and then outputs the answer + Gives a random quiz question, waits 30 seconds and then outputs the answer. Also informs of the percentages and votes of each option """ @@ -96,13 +96,13 @@ class EggheadQuiz(commands.Cog): @staticmethod async def already_reacted(message, user): - """Returns whether a given user has reacted more than once to a given message""" + """Returns whether a given user has reacted more than once to a given message.""" users = [u.id for reaction in [await r.users().flatten() for r in message.reactions] for u in reaction] return users.count(user.id) > 1 # Old reaction plus new reaction @commands.Cog.listener() async def on_reaction_add(self, reaction, user): - """Listener to listen specifically for reactions of quiz messages""" + """Listener to listen specifically for reactions of quiz messages.""" if user.bot: return if reaction.message.id not in self.quiz_messages: diff --git a/bot/seasons/easter/traditions.py b/bot/seasons/easter/traditions.py index f04b8828..b0bf04d7 100644 --- a/bot/seasons/easter/traditions.py +++ b/bot/seasons/easter/traditions.py @@ -19,7 +19,7 @@ class Traditions(commands.Cog): @commands.command(aliases=('eastercustoms',)) async def easter_tradition(self, ctx): - """Responds with a random tradition or custom""" + """Responds with a random tradition or custom.""" random_country = random.choice(list(traditions)) await ctx.send(f"{random_country}:\n{traditions[random_country]}") diff --git a/bot/seasons/evergreen/8bitify.py b/bot/seasons/evergreen/8bitify.py index 54db71db..60062fc1 100644 --- a/bot/seasons/evergreen/8bitify.py +++ b/bot/seasons/evergreen/8bitify.py @@ -13,17 +13,17 @@ class EightBitify(commands.Cog): @staticmethod def pixelate(image: Image) -> Image: - """Takes an image and pixelates it""" + """Takes an image and pixelates it.""" return image.resize((32, 32)).resize((1024, 1024)) @staticmethod def quantize(image: Image) -> Image: - """Reduces colour palette to 256 colours""" + """Reduces colour palette to 256 colours.""" return image.quantize(colors=32) @commands.command(name="8bitify") async def eightbit_command(self, ctx: commands.Context) -> None: - """Pixelates your avatar and changes the palette to an 8bit one""" + """Pixelates your avatar and changes the palette to an 8bit one.""" async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() avatar = Image.open(BytesIO(image_bytes)) diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index cb859ea9..3eee92ca 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -33,7 +33,7 @@ class CoordinateConverter(commands.Converter): """Converter for Coordinates.""" async def convert(self, ctx, coordinate: str) -> typing.Tuple[int, int]: - """Take in a coordinate string and turn it into x, y""" + """Take in a coordinate string and turn it into an (x, y) tuple.""" if not 2 <= len(coordinate) <= 3: raise commands.BadArgument('Invalid co-ordinate provided') @@ -81,7 +81,7 @@ class Minesweeper(commands.Cog): @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) async def minesweeper_group(self, ctx: commands.Context): - """Commands for Playing Minesweeper""" + """Commands for Playing Minesweeper.""" await ctx.send_help(ctx.command) @staticmethod @@ -175,7 +175,7 @@ class Minesweeper(commands.Cog): @commands.dm_only() @minesweeper_group.command(name="flag") async def flag_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: - """Place multiple flags on the board""" + """Place multiple flags on the board.""" board: GameBoard = self.games[ctx.author.id].revealed for x, y in coordinates: if board[y][x] == "hidden": @@ -185,14 +185,14 @@ class Minesweeper(commands.Cog): @staticmethod def reveal_bombs(revealed: GameBoard, board: GameBoard) -> None: - """Reveals all the bombs""" + """Reveals all the bombs.""" for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "bomb": revealed[y][x] = cell async def lost(self, ctx: commands.Context) -> None: - """The player lost the game""" + """The player lost the game.""" game = self.games[ctx.author.id] self.reveal_bombs(game.revealed, game.board) await ctx.author.send(":fire: You lost! :fire:") @@ -200,7 +200,7 @@ class Minesweeper(commands.Cog): await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:") async def won(self, ctx: commands.Context) -> None: - """The player won the game""" + """The player won the game.""" game = self.games[ctx.author.id] await ctx.author.send(":tada: You won! :tada:") if game.activated_on_server: @@ -216,7 +216,7 @@ class Minesweeper(commands.Cog): self.reveal_zeros(revealed, board, x_, y_) async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool: - """Checks if a player has won""" + """Checks if a player has won.""" if any( revealed[y][x] in ["hidden", "flag"] and board[y][x] != "bomb" for x in range(10) @@ -252,7 +252,7 @@ class Minesweeper(commands.Cog): @commands.dm_only() @minesweeper_group.command(name="reveal") async def reveal_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: - """Reveal multiple cells""" + """Reveal multiple cells.""" game = self.games[ctx.author.id] revealed: GameBoard = game.revealed board: GameBoard = game.board @@ -268,7 +268,7 @@ class Minesweeper(commands.Cog): @minesweeper_group.command(name="end") async def end_command(self, ctx: commands.Context): - """End your current game""" + """End your current game.""" game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) diff --git a/bot/seasons/evergreen/showprojects.py b/bot/seasons/evergreen/showprojects.py index 37809b33..5dea78a5 100644 --- a/bot/seasons/evergreen/showprojects.py +++ b/bot/seasons/evergreen/showprojects.py @@ -8,7 +8,7 @@ log = logging.getLogger(__name__) class ShowProjects(commands.Cog): - """Cog that reacts to posts in the #show-your-projects""" + """Cog that reacts to posts in the #show-your-projects.""" def __init__(self, bot): self.bot = bot @@ -16,7 +16,7 @@ class ShowProjects(commands.Cog): @commands.Cog.listener() async def on_message(self, message): - """Adds reactions to posts in #show-your-projects""" + """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 @@ -28,6 +28,6 @@ class ShowProjects(commands.Cog): def setup(bot): - """Show Projects Reaction Cog""" + """Show Projects Reaction Cog.""" bot.add_cog(ShowProjects(bot)) log.info("ShowProjects cog loaded") diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index f6a43a63..5e3d38a0 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -23,6 +23,6 @@ class Speedrun(commands.Cog): def setup(bot): - """Load the Speedrun cog""" + """Load the Speedrun cog.""" bot.add_cog(Speedrun(bot)) log.info("Speedrun cog loaded") diff --git a/bot/seasons/halloween/spookyrating.py b/bot/seasons/halloween/spookyrating.py index 08c17a27..a436e39d 100644 --- a/bot/seasons/halloween/spookyrating.py +++ b/bot/seasons/halloween/spookyrating.py @@ -17,7 +17,7 @@ with Path("bot/resources/halloween/spooky_rating.json").open() as file: class SpookyRating(commands.Cog): - """A cog for calculating one's spooky rating""" + """A cog for calculating one's spooky rating.""" def __init__(self, bot): self.bot = bot diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 3249a9cf..72a681a3 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -110,7 +110,7 @@ def replace_many( regex = re.compile(pattern, re.I if ignore_case else 0) def _repl(match): - """Returns replacement depending on `ignore_case` and `match_case`""" + """Returns replacement depending on `ignore_case` and `match_case`.""" word = match.group(0) replacement = replacements[word.lower() if ignore_case else word] diff --git a/tox.ini b/tox.ini index 3e5db0a5..34a4d498 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,17 @@ [flake8] max-line-length=120 application_import_names=bot +docstring-convention=all ignore= P102,B311,W503,E226,S311, # Missing Docstrings - D100,D104,D107, + D100,D104,D105,D107, # Docstring Whitespace D203,D212,D214,D215, # Docstring Quotes D301,D302, # Docstring Content - D400,D401,D402,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414 + D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417 exclude= __pycache__,.cache, venv,.venv, -- cgit v1.2.3 From b42eca3ff41c3848ef85dd085ecd57fce9fc810a Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Fri, 6 Sep 2019 12:34:01 -0400 Subject: Apply suggestions from code review Comprehension cleanup Co-Authored-By: Mark --- bot/seasons/evergreen/snakes/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index f6b5ea16..77b13f1b 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -494,7 +494,7 @@ class SnakeAndLaddersGame: """ Handle players leaving the game. - Leaving is prevented if the user wesn't part of the game. + Leaving is prevented if the user wasn't part of the game. If the number of players reaches 0, the game is terminated. In this case, a sentinel boolean is returned True to prevent a game from continuing after it's destroyed. @@ -616,7 +616,7 @@ class SnakeAndLaddersGame: if reaction.emoji == ROLL_EMOJI: await self.player_roll(user) elif reaction.emoji == CANCEL_EMOJI: - if self._is_moderator(user) and not self._mod_in_game_check(user): + if self._is_moderator(user) and user not in self.players: # Only allow non-playing moderation staff to cancel a running game await self.cancel_game() return @@ -713,4 +713,4 @@ class SnakeAndLaddersGame: @staticmethod def _is_moderator(user: Member) -> bool: """Return True if the user is a Moderator.""" - return Roles.moderator in [role.id for role in user.roles] + return any(Roles.moderator == role.id for role in user.roles) -- cgit v1.2.3 From 69cbed90b6c1b79f607a1c894998d73ea9c771f7 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Fri, 6 Sep 2019 15:03:23 -0400 Subject: Fix broken cancellation logic for waiting SaL game Previous logic allowed for any non-playing member to cancel the game --- bot/seasons/evergreen/snakes/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index 77b13f1b..9edc86ca 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -436,7 +436,7 @@ class SnakeAndLaddersGame: if reaction.emoji == JOIN_EMOJI: await self.player_join(user) elif reaction.emoji == CANCEL_EMOJI: - if user == self.author or not self._mod_in_game_check(user): + if user == self.author or all((self._is_moderator(user), user not in self.players)): # Allow game author or non-playing moderation staff to cancel a waiting game await self.cancel_game() return @@ -706,10 +706,6 @@ class SnakeAndLaddersGame: x_level = 9 - x_level return x_level, y_level - def _mod_in_game_check(self, user: Member) -> bool: - """Return True if user is a Moderator and playing the currently running game.""" - return all((self._is_moderator(user), user in self.players)) - @staticmethod def _is_moderator(user: Member) -> bool: """Return True if the user is a Moderator.""" -- cgit v1.2.3 From 9e22147e61b61d8ed489df970fe08440424337d9 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Fri, 6 Sep 2019 15:11:42 -0400 Subject: Fix bug in Candy Collection Cog When the ten_recent_msg helper coro is called, it grabs the most recent message by ID, but some of the following lines assume this object is a Message ID, not an integer. --- bot/seasons/halloween/candy_collection.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/halloween/candy_collection.py b/bot/seasons/halloween/candy_collection.py index d35cbee5..a7232e0d 100644 --- a/bot/seasons/halloween/candy_collection.py +++ b/bot/seasons/halloween/candy_collection.py @@ -121,15 +121,16 @@ class CandyCollection(commands.Cog): async def ten_recent_msg(self): """Get the last 10 messages sent in the channel.""" ten_recent = [] - recent_msg = max(message.id for message - in self.bot._connection._messages - if message.channel.id == Channels.seasonalbot_chat) + recent_msg_id = max( + message.id for message in self.bot._connection._messages + if message.channel.id == Channels.seasonalbot_chat + ) channel = await self.hacktober_channel() - ten_recent.append(recent_msg.id) + ten_recent.append(recent_msg_id) for i in range(9): - o = discord.Object(id=recent_msg.id + i) + o = discord.Object(id=recent_msg_id + i) msg = await next(channel.history(limit=1, before=o)) ten_recent.append(msg.id) -- cgit v1.2.3 From 8a8a60b6042146f5e2ac1a0e46b9326c4e61ba8b Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Fri, 6 Sep 2019 16:50:02 -0400 Subject: Update bot/seasons/evergreen/snakes/utils.py Co-Authored-By: Mark --- bot/seasons/evergreen/snakes/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index 9edc86ca..b1d5048a 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -436,7 +436,7 @@ class SnakeAndLaddersGame: if reaction.emoji == JOIN_EMOJI: await self.player_join(user) elif reaction.emoji == CANCEL_EMOJI: - if user == self.author or all((self._is_moderator(user), user not in self.players)): + if user == self.author or (self._is_moderator(user) and user not in self.players): # Allow game author or non-playing moderation staff to cancel a waiting game await self.cancel_game() return -- cgit v1.2.3 From bd03db75805d02da2088ec5067993aa5f23184ae Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Mon, 9 Sep 2019 13:22:09 -0400 Subject: Initial linting pass Bot root, seasons cog, easter cogs, evergreen cogs, halloween cogs --- bot/__init__.py | 2 +- bot/bot.py | 8 ++--- bot/decorators.py | 12 +++---- bot/pagination.py | 8 ++--- bot/seasons/__init__.py | 4 ++- bot/seasons/christmas/adventofcode.py | 26 ++++++++-------- bot/seasons/christmas/hanukkah_embed.py | 11 ++++--- bot/seasons/easter/april_fools_vids.py | 8 ++--- bot/seasons/easter/avatar_easterifier.py | 12 +++---- bot/seasons/easter/bunny_name_generator.py | 15 ++++----- bot/seasons/easter/conversationstarters.py | 6 ++-- bot/seasons/easter/easter_riddle.py | 8 ++--- bot/seasons/easter/egg_decorating.py | 10 +++--- bot/seasons/easter/egg_facts.py | 12 +++---- bot/seasons/easter/egg_hunt/__init__.py | 4 ++- bot/seasons/easter/egg_hunt/cog.py | 32 +++++++++---------- bot/seasons/easter/egghead_quiz.py | 11 ++++--- bot/seasons/easter/traditions.py | 6 ++-- bot/seasons/evergreen/error_handler.py | 10 +++--- bot/seasons/evergreen/fun.py | 2 +- bot/seasons/evergreen/issues.py | 8 +++-- bot/seasons/evergreen/magic_8ball.py | 6 ++-- bot/seasons/evergreen/minesweeper.py | 8 ++--- bot/seasons/evergreen/showprojects.py | 7 +++-- bot/seasons/evergreen/snakes/__init__.py | 4 ++- bot/seasons/evergreen/snakes/converter.py | 11 ++++--- bot/seasons/evergreen/snakes/snakes_cog.py | 50 +++++++++++++++--------------- bot/seasons/evergreen/snakes/utils.py | 46 +++++++++++++-------------- bot/seasons/evergreen/speedrun.py | 6 ++-- bot/seasons/evergreen/uptime.py | 6 ++-- bot/seasons/halloween/8ball.py | 6 ++-- bot/seasons/halloween/candy_collection.py | 25 ++++++++------- bot/seasons/halloween/halloween_facts.py | 13 ++++---- bot/seasons/halloween/halloweenify.py | 6 ++-- bot/seasons/halloween/monstersurvey.py | 12 +++---- bot/seasons/halloween/scarymovie.py | 10 +++--- bot/seasons/halloween/spookyavatar.py | 8 ++--- bot/seasons/halloween/spookygif.py | 6 ++-- bot/seasons/halloween/spookyrating.py | 6 ++-- bot/seasons/halloween/spookyreact.py | 8 ++--- bot/seasons/halloween/spookysound.py | 8 ++--- bot/seasons/halloween/timeleft.py | 11 ++++--- bot/seasons/season.py | 26 ++++++++-------- 43 files changed, 261 insertions(+), 243 deletions(-) (limited to 'bot') diff --git a/bot/__init__.py b/bot/__init__.py index 9e0290a7..8950423f 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -13,7 +13,7 @@ logging.TRACE = 5 logging.addLevelName(logging.TRACE, "TRACE") -def monkeypatch_trace(self, msg, *args, **kwargs): +def monkeypatch_trace(self, msg: str, *args, **kwargs) -> None: """ Log 'msg % args' with severity 'TRACE'. diff --git a/bot/bot.py b/bot/bot.py index 86028838..2a723021 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -4,7 +4,7 @@ from traceback import format_exc from typing import List from aiohttp import AsyncResolver, ClientSession, TCPConnector -from discord import Embed +from discord import DiscordException, Embed from discord.ext import commands from bot.constants import Channels, Client @@ -23,7 +23,7 @@ class SeasonalBot(commands.Bot): connector=TCPConnector(resolver=AsyncResolver(), family=socket.AF_INET) ) - def load_extensions(self, exts: List[str]): + def load_extensions(self, exts: List[str]) -> None: """Unload all current extensions, then load the given extensions.""" # Unload all cogs extensions = list(self.extensions.keys()) @@ -40,7 +40,7 @@ class SeasonalBot(commands.Bot): except Exception as e: log.error(f'Failed to load extension {cog}: {repr(e)} {format_exc()}') - async def send_log(self, title: str, details: str = None, *, icon: str = None): + async def send_log(self, title: str, details: str = None, *, icon: str = None) -> None: """Send an embed message to the devlog channel.""" devlog = self.get_channel(Channels.devlog) @@ -56,7 +56,7 @@ class SeasonalBot(commands.Bot): await devlog.send(embed=embed) - async def on_command_error(self, context, exception): + async def on_command_error(self, context: commands.Context, exception: DiscordException) -> None: """Check command errors for UserInputError and reset the cooldown if thrown.""" if isinstance(exception, commands.UserInputError): context.command.reset_cooldown(context) diff --git a/bot/decorators.py b/bot/decorators.py index 02cf4b8a..e12c3f34 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -20,9 +20,9 @@ class InChannelCheckFailure(CheckFailure): pass -def with_role(*role_ids: int): +def with_role(*role_ids: int) -> bool: """Check to see whether the invoking user has any of the roles specified in role_ids.""" - async def predicate(ctx: Context): + async def predicate(ctx: Context) -> bool: if not ctx.guild: # Return False in a DM log.debug( f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " @@ -43,9 +43,9 @@ def with_role(*role_ids: int): return commands.check(predicate) -def without_role(*role_ids: int): +def without_role(*role_ids: int) -> bool: """Check whether the invoking user does not have all of the roles specified in role_ids.""" - async def predicate(ctx: Context): + async def predicate(ctx: Context) -> bool: if not ctx.guild: # Return False in a DM log.debug( f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " @@ -125,11 +125,11 @@ def locked(): This decorator has to go before (below) the `command` decorator. """ - def wrap(func): + def wrap(func: typing.Callable): func.__locks = WeakValueDictionary() @wraps(func) - async def inner(self, ctx, *args, **kwargs): + async def inner(self, ctx: Context, *args, **kwargs): lock = func.__locks.setdefault(ctx.author.id, Lock()) if lock.locked(): embed = Embed() diff --git a/bot/pagination.py b/bot/pagination.py index c12b6233..f1233482 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -24,7 +24,7 @@ class EmptyPaginatorEmbed(Exception): class LinePaginator(Paginator): """A class that aids in paginating code blocks for Discord messages.""" - def __init__(self, prefix='```', suffix='```', max_size=2000, max_lines=None): + def __init__(self, prefix: str = '```', suffix: str = '```', max_size: int = 2000, max_lines: int = None): """ Overrides the Paginator.__init__ from inside discord.ext.commands. @@ -42,7 +42,7 @@ class LinePaginator(Paginator): self._count = len(prefix) + 1 # prefix + newline self._pages = [] - def add_line(self, line='', *, empty=False): + def add_line(self, line: str = '', *, empty: bool = False) -> None: """ Adds a line to the current page. @@ -98,7 +98,7 @@ class LinePaginator(Paginator): ... ctx, embed ... ) """ - def event_check(reaction_: Reaction, user_: Member): + def event_check(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" no_restrictions = ( # Pagination is not restricted @@ -274,7 +274,7 @@ class ImagePaginator(Paginator): Refer to ImagePaginator.paginate for documentation on how to use. """ - def __init__(self, prefix="", suffix=""): + def __init__(self, prefix: str = "", suffix: str = ""): super().__init__(prefix, suffix) self._current_page = [prefix] self.images = [] diff --git a/bot/seasons/__init__.py b/bot/seasons/__init__.py index 1512fae2..7faf9164 100644 --- a/bot/seasons/__init__.py +++ b/bot/seasons/__init__.py @@ -1,5 +1,7 @@ import logging +from discord.ext import commands + from bot.seasons.season import SeasonBase, SeasonManager, get_season __all__ = ("SeasonBase", "get_season") @@ -7,6 +9,6 @@ __all__ = ("SeasonBase", "get_season") log = logging.getLogger(__name__) -def setup(bot): +def setup(bot: commands.Bot) -> None: bot.add_cog(SeasonManager(bot)) log.info("SeasonManager cog loaded") diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index a9e72805..5938ccbe 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -46,7 +46,7 @@ def time_left_to_aoc_midnight() -> Tuple[datetime, timedelta]: return tomorrow, tomorrow - datetime.now(EST) -async def countdown_status(bot: commands.Bot): +async def countdown_status(bot: commands.Bot) -> None: """Set the playing status of the bot to the minutes & hours left until the next day's challenge.""" while is_in_advent(): _, time_left = time_left_to_aoc_midnight() @@ -73,7 +73,7 @@ async def countdown_status(bot: commands.Bot): await asyncio.sleep(delay) -async def day_countdown(bot: commands.Bot): +async def day_countdown(bot: commands.Bot) -> None: """ Calculate the number of seconds left until the next day of Advent. @@ -127,7 +127,7 @@ class AdventOfCode(commands.Cog): @commands.group(name="adventofcode", aliases=("aoc",), invoke_without_command=True) @override_in_channel - async def adventofcode_group(self, ctx: commands.Context): + async def adventofcode_group(self, ctx: commands.Context) -> None: """All of the Advent of Code commands.""" await ctx.send_help(ctx.command) @@ -136,7 +136,7 @@ class AdventOfCode(commands.Cog): aliases=("sub", "notifications", "notify", "notifs"), brief="Notifications for new days" ) - async def aoc_subscribe(self, ctx: commands.Context): + async def aoc_subscribe(self, ctx: commands.Context) -> None: """Assign the role for notifications about new days being ready.""" role = ctx.guild.get_role(AocConfig.role_id) unsubscribe_command = f"{ctx.prefix}{ctx.command.root_parent} unsubscribe" @@ -150,7 +150,7 @@ class AdventOfCode(commands.Cog): f"If you don't want them any more, run `{unsubscribe_command}` instead.") @adventofcode_group.command(name="unsubscribe", aliases=("unsub",), brief="Notifications for new days") - async def aoc_unsubscribe(self, ctx: commands.Context): + async def aoc_unsubscribe(self, ctx: commands.Context) -> None: """Remove the role for notifications about new days being ready.""" role = ctx.guild.get_role(AocConfig.role_id) @@ -161,7 +161,7 @@ class AdventOfCode(commands.Cog): await ctx.send("Hey, you don't even get any notifications about new Advent of Code tasks currently anyway.") @adventofcode_group.command(name="countdown", aliases=("count", "c"), brief="Return time left until next day") - async def aoc_countdown(self, ctx: commands.Context): + async def aoc_countdown(self, ctx: commands.Context) -> None: """Return time left until next day.""" if not is_in_advent(): datetime_now = datetime.now(EST) @@ -178,12 +178,12 @@ class AdventOfCode(commands.Cog): await ctx.send(f"There are {hours} hours and {minutes} minutes left until day {tomorrow.day}.") @adventofcode_group.command(name="about", aliases=("ab", "info"), brief="Learn about Advent of Code") - async def about_aoc(self, ctx: commands.Context): + async def about_aoc(self, ctx: commands.Context) -> None: """Respond with an explanation of all things Advent of Code.""" await ctx.send("", embed=self.cached_about_aoc) @adventofcode_group.command(name="join", aliases=("j",), brief="Learn how to join PyDis' private AoC leaderboard") - async def join_leaderboard(self, ctx: commands.Context): + async def join_leaderboard(self, ctx: commands.Context) -> None: """DM the user the information for joining the PyDis AoC private leaderboard.""" author = ctx.message.author log.info(f"{author.name} ({author.id}) has requested the PyDis AoC leaderboard code") @@ -203,7 +203,7 @@ class AdventOfCode(commands.Cog): aliases=("board", "lb"), brief="Get a snapshot of the PyDis private AoC leaderboard", ) - async def aoc_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10): + async def aoc_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10) -> None: """ Pull the top number_of_people_to_display members from the PyDis leaderboard and post an embed. @@ -244,7 +244,7 @@ class AdventOfCode(commands.Cog): aliases=("dailystats", "ds"), brief="Get daily statistics for the PyDis private leaderboard" ) - async def private_leaderboard_daily_stats(self, ctx: commands.Context): + async def private_leaderboard_daily_stats(self, ctx: commands.Context) -> None: """ Respond with a table of the daily completion statistics for the PyDis private leaderboard. @@ -287,7 +287,7 @@ class AdventOfCode(commands.Cog): aliases=("globalboard", "gb"), brief="Get a snapshot of the global AoC leaderboard", ) - async def global_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10): + async def global_leaderboard(self, ctx: commands.Context, number_of_people_to_display: int = 10) -> None: """ Pull the top number_of_people_to_display members from the global AoC leaderboard and post an embed. @@ -319,7 +319,7 @@ class AdventOfCode(commands.Cog): embed=aoc_embed, ) - async def _check_leaderboard_cache(self, ctx, global_board: bool = False): + async def _check_leaderboard_cache(self, ctx: commands.Context, global_board: bool = False) -> None: """ Check age of current leaderboard & pull a new one if the board is too old. @@ -390,7 +390,7 @@ class AdventOfCode(commands.Cog): return about_embed - async def _boardgetter(self, global_board: bool): + async def _boardgetter(self, global_board: bool) -> None: """Invoke the proper leaderboard getter based on the global_board boolean.""" if global_board: self.cached_global_leaderboard = await AocGlobalLeaderboard.from_url() diff --git a/bot/seasons/christmas/hanukkah_embed.py b/bot/seasons/christmas/hanukkah_embed.py index 652a1f35..aaa02b27 100644 --- a/bot/seasons/christmas/hanukkah_embed.py +++ b/bot/seasons/christmas/hanukkah_embed.py @@ -1,5 +1,6 @@ import datetime import logging +from typing import List from discord import Embed from discord.ext import commands @@ -13,7 +14,7 @@ log = logging.getLogger(__name__) class HanukkahEmbed(commands.Cog): """A cog that returns information about Hanukkah festival.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.url = ("https://www.hebcal.com/hebcal/?v=1&cfg=json&maj=on&min=on&mod=on&nx=on&" "year=now&month=x&ss=on&mf=on&c=on&geo=geoname&geonameid=3448439&m=50&s=on") @@ -21,7 +22,7 @@ class HanukkahEmbed(commands.Cog): self.hanukkah_months = [] self.hanukkah_years = [] - async def get_hanukkah_dates(self): + async def get_hanukkah_dates(self) -> List[str]: """Gets the dates for hanukkah festival.""" hanukkah_dates = [] async with self.bot.http_session.get(self.url) as response: @@ -34,7 +35,7 @@ class HanukkahEmbed(commands.Cog): return hanukkah_dates @commands.command(name='hanukkah', aliases=['chanukah']) - async def hanukkah_festival(self, ctx): + async def hanukkah_festival(self, ctx: commands.Context) -> None: """Tells you about the Hanukkah Festivaltime of festival, festival day, etc).""" hanukkah_dates = await self.get_hanukkah_dates() self.hanukkah_dates_split(hanukkah_dates) @@ -98,7 +99,7 @@ class HanukkahEmbed(commands.Cog): await ctx.send(embed=embed) - def hanukkah_dates_split(self, hanukkah_dates): + def hanukkah_dates_split(self, hanukkah_dates: List[str]) -> None: """We are splitting the dates for hanukkah into days, months and years.""" for date in hanukkah_dates: self.hanukkah_days.append(date[8:10]) @@ -106,7 +107,7 @@ class HanukkahEmbed(commands.Cog): self.hanukkah_years.append(date[0:4]) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog load.""" bot.add_cog(HanukkahEmbed(bot)) log.info("Hanukkah embed cog loaded") diff --git a/bot/seasons/easter/april_fools_vids.py b/bot/seasons/easter/april_fools_vids.py index d921d07c..4869f510 100644 --- a/bot/seasons/easter/april_fools_vids.py +++ b/bot/seasons/easter/april_fools_vids.py @@ -11,13 +11,13 @@ log = logging.getLogger(__name__) class AprilFoolVideos(commands.Cog): """A cog for April Fools' that gets a random April Fools' video from Youtube.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.yt_vids = self.load_json() self.youtubers = ['google'] # will add more in future @staticmethod - def load_json(): + def load_json() -> dict: """A function to load JSON data.""" p = Path('bot/resources/easter/april_fools_vids.json') with p.open() as json_file: @@ -25,7 +25,7 @@ class AprilFoolVideos(commands.Cog): return all_vids @commands.command(name='fool') - async def aprial_fools(self, ctx): + async def april_fools(self, ctx: commands.Context) -> None: """Get a random April Fools' video from Youtube.""" random_youtuber = random.choice(self.youtubers) category = self.yt_vids[random_youtuber] @@ -33,7 +33,7 @@ class AprilFoolVideos(commands.Cog): await ctx.send(f"Check out this April Fools' video by {random_youtuber}.\n\n{random_vid['link']}") -def setup(bot): +def setup(bot: commands.Bot) -> None: """April Fools' Cog load.""" bot.add_cog(AprilFoolVideos(bot)) log.info('April Fools videos cog loaded!') diff --git a/bot/seasons/easter/avatar_easterifier.py b/bot/seasons/easter/avatar_easterifier.py index ad8b5473..f056068e 100644 --- a/bot/seasons/easter/avatar_easterifier.py +++ b/bot/seasons/easter/avatar_easterifier.py @@ -2,7 +2,7 @@ import asyncio import logging from io import BytesIO from pathlib import Path -from typing import Union +from typing import Tuple, Union import discord from PIL import Image @@ -21,11 +21,11 @@ COLOURS = [ class AvatarEasterifier(commands.Cog): """Put an Easter spin on your avatar or image!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - def closest(x): + def closest(x: Tuple[int, int, int]) -> Tuple[int, int, int]: """ Finds the closest easter colour to a given pixel. @@ -33,7 +33,7 @@ class AvatarEasterifier(commands.Cog): """ r1, g1, b1 = x - def distance(point): + def distance(point: Tuple[int, int, int]) -> Tuple[int, int, int]: """Finds the difference between a pastel colour and the original pixel colour""" r2, g2, b2 = point return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) @@ -47,7 +47,7 @@ class AvatarEasterifier(commands.Cog): return (r, g, b) @commands.command(pass_context=True, aliases=["easterify"]) - async def avatareasterify(self, ctx, *colours: Union[discord.Colour, str]): + async def avatareasterify(self, ctx: commands.Context, *colours: Union[discord.Colour, str]) -> None: """ This "Easterifies" the user's avatar. @@ -123,7 +123,7 @@ class AvatarEasterifier(commands.Cog): await ctx.send(file=file, embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Avatar Easterifier Cog load.""" bot.add_cog(AvatarEasterifier(bot)) log.info("AvatarEasterifier cog loaded") diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 76d5c478..b068ac2c 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -3,6 +3,7 @@ import logging import random import re from pathlib import Path +from typing import List, Union from discord.ext import commands @@ -15,16 +16,16 @@ with Path("bot/resources/easter/bunny_names.json").open("r", encoding="utf8") as class BunnyNameGenerator(commands.Cog): """Generate a random bunny name, or bunnify your Discord username!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot - def find_separators(self, displayname): + def find_separators(self, displayname: str) -> Union[List[str], None]: """Check if Discord name contains spaces so we can bunnify an individual word in the name.""" new_name = re.split(r'[_.\s]', displayname) if displayname not in new_name: return new_name - def find_vowels(self, displayname): + def find_vowels(self, displayname: str) -> str: """ Finds vowels in the user's display name. @@ -45,7 +46,7 @@ class BunnyNameGenerator(commands.Cog): if new_name != displayname: return new_name - def append_name(self, displayname): + def append_name(self, displayname: str) -> str: """Adds a suffix to the end of the Discord name""" extensions = ['foot', 'ear', 'nose', 'tail'] suffix = random.choice(extensions) @@ -54,12 +55,12 @@ class BunnyNameGenerator(commands.Cog): return appended_name @commands.command() - async def bunnyname(self, ctx): + async def bunnyname(self, ctx: commands.Context) -> None: """Picks a random bunny name from a JSON file""" await ctx.send(random.choice(BUNNY_NAMES["names"])) @commands.command() - async def bunnifyme(self, ctx): + async def bunnifyme(self, ctx: commands.Context) -> None: """Gets your Discord username and bunnifies it""" username = ctx.message.author.display_name @@ -86,7 +87,7 @@ class BunnyNameGenerator(commands.Cog): await ctx.send(bunnified_name) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Bunny Name Generator Cog load.""" bot.add_cog(BunnyNameGenerator(bot)) log.info("BunnyNameGenerator cog loaded.") diff --git a/bot/seasons/easter/conversationstarters.py b/bot/seasons/easter/conversationstarters.py index c2cdf26c..3f38ae82 100644 --- a/bot/seasons/easter/conversationstarters.py +++ b/bot/seasons/easter/conversationstarters.py @@ -14,16 +14,16 @@ with open(Path("bot/resources/easter/starter.json"), "r", encoding="utf8") as f: class ConvoStarters(commands.Cog): """Easter conversation topics.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command() - async def topic(self, ctx): + async def topic(self, ctx: commands.Context) -> None: """Responds with a random topic to start a conversation.""" await ctx.send(random.choice(starters['starters'])) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Conversation starters Cog load.""" bot.add_cog(ConvoStarters(bot)) log.info("ConvoStarters cog loaded") diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index 56555586..c3f19055 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -20,14 +20,14 @@ TIMELIMIT = 10 class EasterRiddle(commands.Cog): """This cog contains the command for the Easter quiz!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.winners = [] self.correct = "" self.current_channel = None @commands.command(aliases=["riddlemethis", "riddleme"]) - async def riddle(self, ctx): + async def riddle(self, ctx: commands.Context) -> None: """ Gives a random riddle, then provides 2 hints at certain intervals before revealing the answer. @@ -83,7 +83,7 @@ class EasterRiddle(commands.Cog): self.current_channel = None @commands.Cog.listener() - async def on_message(self, message): + async def on_message(self, message: discord.Messaged) -> None: """If a non-bot user enters a correct answer, their username gets added to self.winners""" if self.current_channel != message.channel: return @@ -95,7 +95,7 @@ class EasterRiddle(commands.Cog): self.winners.append(message.author.mention) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Easter Riddle Cog load.""" bot.add_cog(EasterRiddle(bot)) log.info("Easter Riddle bot loaded") diff --git a/bot/seasons/easter/egg_decorating.py b/bot/seasons/easter/egg_decorating.py index ee8a80e5..51f52264 100644 --- a/bot/seasons/easter/egg_decorating.py +++ b/bot/seasons/easter/egg_decorating.py @@ -31,11 +31,11 @@ IRREPLACEABLE = [ class EggDecorating(commands.Cog): """Decorate some easter eggs!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot @staticmethod - def replace_invalid(colour: str): + def replace_invalid(colour: str) -> Union[int, None]: """Attempts to match with HTML or XKCD colour names, returning the int value.""" with suppress(KeyError): return int(HTML_COLOURS[colour], 16) @@ -44,7 +44,9 @@ class EggDecorating(commands.Cog): return None @commands.command(aliases=["decorateegg"]) - async def eggdecorate(self, ctx, *colours: Union[discord.Colour, str]): + async def eggdecorate( + self, ctx: commands.Context, *colours: Union[discord.Colour, str] + ) -> Union[Image, discord.Message]: """ Picks a random egg design and decorates it using the given colours. @@ -111,7 +113,7 @@ class EggDecorating(commands.Cog): return new_im -def setup(bot): +def setup(bot: commands.bot) -> None: """Egg decorating Cog load.""" bot.add_cog(EggDecorating(bot)) log.info("EggDecorating cog loaded.") diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index ae08ccd4..9e6fb1cb 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -21,18 +21,18 @@ class EasterFacts(commands.Cog): It also contains a background task which sends an easter egg fact in the event channel everyday. """ - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.facts = self.load_json() @staticmethod - def load_json(): + def load_json() -> dict: """Load a list of easter egg facts from the resource JSON file.""" p = Path("bot/resources/easter/easter_egg_facts.json") with p.open(encoding="utf8") as f: return load(f) - async def send_egg_fact_daily(self): + async def send_egg_fact_daily(self) -> None: """A background task that sends an easter egg fact in the event channel everyday.""" channel = self.bot.get_channel(Channels.seasonalbot_chat) while True: @@ -41,12 +41,12 @@ class EasterFacts(commands.Cog): await asyncio.sleep(24 * 60 * 60) @commands.command(name='eggfact', aliases=['fact']) - async def easter_facts(self, ctx): + async def easter_facts(self, ctx: commands.Context) -> None: """Get easter egg facts.""" embed = self.make_embed() await ctx.send(embed=embed) - def make_embed(self): + def make_embed(self) -> discord.Embed: """Makes a nice embed for the message to be sent.""" return discord.Embed( colour=Colours.soft_red, @@ -55,7 +55,7 @@ class EasterFacts(commands.Cog): ) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Easter Egg facts cog load.""" bot.loop.create_task(EasterFacts(bot).send_egg_fact_daily()) bot.add_cog(EasterFacts(bot)) diff --git a/bot/seasons/easter/egg_hunt/__init__.py b/bot/seasons/easter/egg_hunt/__init__.py index 0e4b9e16..e7e71ccb 100644 --- a/bot/seasons/easter/egg_hunt/__init__.py +++ b/bot/seasons/easter/egg_hunt/__init__.py @@ -1,11 +1,13 @@ import logging +from discord.ext import commands + from .cog import EggHunt log = logging.getLogger(__name__) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Easter Egg Hunt Cog load.""" bot.add_cog(EggHunt()) log.info("EggHunt cog loaded") diff --git a/bot/seasons/easter/egg_hunt/cog.py b/bot/seasons/easter/egg_hunt/cog.py index a4ad27df..8178b4ef 100644 --- a/bot/seasons/easter/egg_hunt/cog.py +++ b/bot/seasons/easter/egg_hunt/cog.py @@ -91,7 +91,7 @@ class EggMessage: """Builds the SQL for adding a score to a team in the database.""" return f"UPDATE team_scores SET team_score=team_score+{score} WHERE team_id='{team_name}'" - def finalise_score(self): + def finalise_score(self) -> None: """Sums and actions scoring for this egg drop session.""" db = sqlite3.connect(DB_PATH) c = db.cursor() @@ -133,7 +133,7 @@ class EggMessage: f"FIRST({self.first}) REST({self.users})." ) - async def start_timeout(self, seconds: int = 5): + async def start_timeout(self, seconds: int = 5) -> None: """Begins a task that will sleep until the given seconds before finalizing the session.""" if self.timeout_task: self.timeout_task.cancel() @@ -164,7 +164,7 @@ class EggMessage: return True - async def collect_reacts(self, reaction: discord.Reaction, user: discord.Member): + async def collect_reacts(self, reaction: discord.Reaction, user: discord.Member) -> None: """Handles emitted reaction_add events via listener.""" if not self.is_valid_react(reaction, user): return @@ -182,7 +182,7 @@ class EggMessage: if user != self.first: self.users.add(user) - async def start(self): + async def start(self) -> None: """Starts the egg drop session.""" log.debug(f"EggHunt session started for message {self.message.id}.") bot.add_listener(self.collect_reacts, name="on_reaction_add") @@ -207,7 +207,7 @@ class SuperEggMessage(EggMessage): super().__init__(message, egg) self.window = window - async def finalise_score(self): + async def finalise_score(self) -> None: """Sums and actions scoring for this super egg session.""" try: message = await self.message.channel.fetch_message(self.message.id) @@ -280,7 +280,7 @@ class SuperEggMessage(EggMessage): with contextlib.suppress(discord.HTTPException): await self.message.edit(embed=embed) - async def start_timeout(self, seconds=None): + async def start_timeout(self, seconds: int = None) -> None: """Starts the super egg session.""" if not seconds: return @@ -337,7 +337,7 @@ class EggHunt(commands.Cog): self.task = asyncio.create_task(self.super_egg()) self.task.add_done_callback(self.task_cleanup) - def prepare_db(self): + def prepare_db(self) -> None: """Ensures database tables all exist and if not, creates them.""" db = sqlite3.connect(DB_PATH) c = db.cursor() @@ -358,7 +358,7 @@ class EggHunt(commands.Cog): db.commit() db.close() - def task_cleanup(self, task): + def task_cleanup(self, task: asyncio.Task) -> None: """Returns task result and restarts. Used as a done callback to show raised exceptions.""" task.result() self.task = asyncio.create_task(self.super_egg()) @@ -368,7 +368,7 @@ class EggHunt(commands.Cog): """Returns a timestamp of the current UTC time.""" return datetime.utcnow().replace(tzinfo=timezone.utc).timestamp() - async def super_egg(self): + async def super_egg(self) -> None: """Manages the timing of super egg drops.""" while True: now = int(self.current_timestamp()) @@ -455,7 +455,7 @@ class EggHunt(commands.Cog): await asyncio.sleep(next_loop) @commands.Cog.listener() - async def on_raw_reaction_add(self, payload): + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None: """Reaction event listener for reaction logging for later anti-cheat analysis.""" if payload.channel_id not in EggHuntSettings.allowed_channels: return @@ -471,7 +471,7 @@ class EggHunt(commands.Cog): db.close() @commands.Cog.listener() - async def on_message(self, message): + async def on_message(self, message: discord.Message) -> None: """Message event listener for random egg drops.""" if self.current_timestamp() < EggHuntSettings.start_time: return @@ -487,7 +487,7 @@ class EggHunt(commands.Cog): await EggMessage(message, random.choice([Emoji.egg_white, Emoji.egg_blurple])).start() @commands.group(invoke_without_command=True) - async def hunt(self, ctx): + async def hunt(self, ctx: commands.Context) -> None: """ For 48 hours, hunt down as many eggs randomly appearing as possible. @@ -514,7 +514,7 @@ class EggHunt(commands.Cog): await ctx.invoke(bot.get_command("help"), command="hunt") @hunt.command() - async def countdown(self, ctx): + async def countdown(self, ctx: commands.Context) -> None: """Show the time status of the Egg Hunt event.""" now = self.current_timestamp() if now > EggHuntSettings.end_time: @@ -532,7 +532,7 @@ class EggHunt(commands.Cog): await ctx.send(f"{msg} {hours:.0f}hrs, {minutes:.0f}mins & {r:.0f}secs") @hunt.command() - async def leaderboard(self, ctx): + async def leaderboard(self, ctx: commands.Context) -> None: """Show the Egg Hunt Leaderboards.""" db = sqlite3.connect(DB_PATH) c = db.cursor() @@ -573,7 +573,7 @@ class EggHunt(commands.Cog): await ctx.send(embed=embed) @hunt.command() - async def rank(self, ctx, *, member: discord.Member = None): + async def rank(self, ctx: commands.Context, *, member: discord.Member = None) -> None: """Get your ranking in the Egg Hunt Leaderboard.""" member = member or ctx.author db = sqlite3.connect(DB_PATH) @@ -593,7 +593,7 @@ class EggHunt(commands.Cog): @with_role(MainRoles.admin) @hunt.command() - async def clear_db(self, ctx): + async def clear_db(self, ctx: commands.Context) -> None: """Resets the database to it's initial state.""" def check(msg): if msg.author != ctx.author: diff --git a/bot/seasons/easter/egghead_quiz.py b/bot/seasons/easter/egghead_quiz.py index 3e0cc598..f479504c 100644 --- a/bot/seasons/easter/egghead_quiz.py +++ b/bot/seasons/easter/egghead_quiz.py @@ -3,6 +3,7 @@ import logging import random from json import load from pathlib import Path +from typing import Union import discord from discord.ext import commands @@ -30,12 +31,12 @@ TIMELIMIT = 30 class EggheadQuiz(commands.Cog): """This cog contains the command for the Easter quiz!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot self.quiz_messages = {} @commands.command(aliases=["eggheadquiz", "easterquiz"]) - async def eggquiz(self, ctx): + async def eggquiz(self, ctx: commands.Context) -> None: """ Gives a random quiz question, waits 30 seconds and then outputs the answer @@ -95,13 +96,13 @@ class EggheadQuiz(commands.Cog): await ctx.send(content, embed=a_embed) @staticmethod - async def already_reacted(message, user): + async def already_reacted(message: discord.Message, user: Union[discord.Member, discord.User]) -> bool: """Returns whether a given user has reacted more than once to a given message""" users = [u.id for reaction in [await r.users().flatten() for r in message.reactions] for u in reaction] return users.count(user.id) > 1 # Old reaction plus new reaction @commands.Cog.listener() - async def on_reaction_add(self, reaction, user): + async def on_reaction_add(self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]) -> None: """Listener to listen specifically for reactions of quiz messages""" if user.bot: return @@ -113,7 +114,7 @@ class EggheadQuiz(commands.Cog): return await reaction.message.remove_reaction(reaction, user) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Egghead Quiz Cog load.""" bot.add_cog(EggheadQuiz(bot)) log.info("EggheadQuiz bot loaded") diff --git a/bot/seasons/easter/traditions.py b/bot/seasons/easter/traditions.py index f04b8828..4fb4694f 100644 --- a/bot/seasons/easter/traditions.py +++ b/bot/seasons/easter/traditions.py @@ -14,18 +14,18 @@ with open(Path("bot/resources/easter/traditions.json"), "r", encoding="utf8") as class Traditions(commands.Cog): """A cog which allows users to get a random easter tradition or custom from a random country.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('eastercustoms',)) - async def easter_tradition(self, ctx): + async def easter_tradition(self, ctx: commands.Context) -> None: """Responds with a random tradition or custom""" random_country = random.choice(list(traditions)) await ctx.send(f"{random_country}:\n{traditions[random_country]}") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Traditions Cog load.""" bot.add_cog(Traditions(bot)) log.info("Traditions cog loaded") diff --git a/bot/seasons/evergreen/error_handler.py b/bot/seasons/evergreen/error_handler.py index 6690cf89..120462ee 100644 --- a/bot/seasons/evergreen/error_handler.py +++ b/bot/seasons/evergreen/error_handler.py @@ -4,7 +4,7 @@ import random import sys import traceback -from discord import Colour, Embed +from discord import Colour, Embed, Message from discord.ext import commands from bot.constants import NEGATIVE_REPLIES @@ -16,11 +16,11 @@ log = logging.getLogger(__name__) class CommandErrorHandler(commands.Cog): """A error handler for the PythonDiscord server.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - def revert_cooldown_counter(command, message): + def revert_cooldown_counter(command: commands.Command, message: Message) -> None: """Undoes the last cooldown counter for user-error cases.""" if command._buckets.valid: bucket = command._buckets.get_bucket(message) @@ -30,7 +30,7 @@ class CommandErrorHandler(commands.Cog): ) @commands.Cog.listener() - async def on_command_error(self, ctx, error): + async def on_command_error(self, ctx: commands.Context, error: commands.CommandError) -> None: """Activates when a command opens an error.""" if hasattr(ctx.command, 'on_error'): return logging.debug( @@ -113,7 +113,7 @@ class CommandErrorHandler(commands.Cog): traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr) -def setup(bot): +def setup(bot: commands.Bot) -> None: """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 87077f36..7b3363fc 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -77,7 +77,7 @@ class Fun(Cog): return text -def setup(bot) -> None: +def setup(bot: commands.Bot) -> None: """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 index f19a1129..0ba74d9c 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -12,12 +12,14 @@ log = logging.getLogger(__name__) class Issues(commands.Cog): """Cog that allows users to retrieve issues from GitHub.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=("issues",)) @override_in_channel - async def issue(self, ctx, number: int, repository: str = "seasonalbot", user: str = "python-discord"): + async def issue( + self, ctx: commands.Context, number: int, repository: str = "seasonalbot", user: str = "python-discord" + ) -> None: """Command to retrieve issues from a GitHub repository.""" api_url = f"https://api.github.com/repos/{user}/{repository}/issues/{number}" failed_status = { @@ -49,7 +51,7 @@ class Issues(commands.Cog): await ctx.send(embed=issue_embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """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 55652af7..e47ef454 100644 --- a/bot/seasons/evergreen/magic_8ball.py +++ b/bot/seasons/evergreen/magic_8ball.py @@ -11,13 +11,13 @@ log = logging.getLogger(__name__) class Magic8ball(commands.Cog): """A Magic 8ball command to respond to a user's question.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot 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): + async def output_answer(self, ctx: commands.Context, *, question: str) -> None: """Return a Magic 8ball answer from answers list.""" if len(question.split()) >= 3: answer = random.choice(self.answers) @@ -26,7 +26,7 @@ class Magic8ball(commands.Cog): await ctx.send("Usage: .8ball (minimum length of 3 eg: `will I win?`)") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Magic 8ball Cog load.""" bot.add_cog(Magic8ball(bot)) log.info("Magic8ball cog loaded") diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index cb859ea9..9dadb9f0 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -32,7 +32,7 @@ log = logging.getLogger(__name__) class CoordinateConverter(commands.Converter): """Converter for Coordinates.""" - async def convert(self, ctx, coordinate: str) -> typing.Tuple[int, int]: + async def convert(self, ctx: commands.Context, coordinate: str) -> typing.Tuple[int, int]: """Take in a coordinate string and turn it into x, y""" if not 2 <= len(coordinate) <= 3: raise commands.BadArgument('Invalid co-ordinate provided') @@ -80,7 +80,7 @@ class Minesweeper(commands.Cog): self.games: GamesDict = {} # Store the currently running games @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) - async def minesweeper_group(self, ctx: commands.Context): + async def minesweeper_group(self, ctx: commands.Context) -> None: """Commands for Playing Minesweeper""" await ctx.send_help(ctx.command) @@ -215,7 +215,7 @@ class Minesweeper(commands.Cog): if board[y_][x_] == 0: self.reveal_zeros(revealed, board, x_, y_) - async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool: + async def check_if_won(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard) -> bool: """Checks if a player has won""" if any( revealed[y][x] in ["hidden", "flag"] and board[y][x] != "bomb" @@ -267,7 +267,7 @@ class Minesweeper(commands.Cog): await self.update_boards(ctx) @minesweeper_group.command(name="end") - async def end_command(self, ctx: commands.Context): + async def end_command(self, ctx: commands.Context) -> None: """End your current game""" game = self.games[ctx.author.id] game.revealed = game.board diff --git a/bot/seasons/evergreen/showprojects.py b/bot/seasons/evergreen/showprojects.py index 37809b33..2804bdbe 100644 --- a/bot/seasons/evergreen/showprojects.py +++ b/bot/seasons/evergreen/showprojects.py @@ -1,5 +1,6 @@ import logging +from discord import Message from discord.ext import commands from bot.constants import Channels @@ -10,12 +11,12 @@ log = logging.getLogger(__name__) class ShowProjects(commands.Cog): """Cog that reacts to posts in the #show-your-projects""" - def __init__(self, bot): + def __init__(self, bot: commands.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): + async def on_message(self, message: Message) -> None: """Adds reactions to posts in #show-your-projects""" reactions = ["\U0001f44d", "\U00002764", "\U0001f440", "\U0001f389", "\U0001f680", "\U00002b50", "\U0001f6a9"] if (message.channel.id == Channels.show_your_projects @@ -27,7 +28,7 @@ class ShowProjects(commands.Cog): self.lastPoster = message.author.id -def setup(bot): +def setup(bot: commands.Bot) -> None: """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 d0e57dae..d7f9f20c 100644 --- a/bot/seasons/evergreen/snakes/__init__.py +++ b/bot/seasons/evergreen/snakes/__init__.py @@ -1,11 +1,13 @@ import logging +from discord.ext import commands + from bot.seasons.evergreen.snakes.snakes_cog import Snakes log = logging.getLogger(__name__) -def setup(bot): +def setup(bot: commands.Bot) -> None: """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 f2637530..57103b57 100644 --- a/bot/seasons/evergreen/snakes/converter.py +++ b/bot/seasons/evergreen/snakes/converter.py @@ -1,9 +1,10 @@ import json import logging import random +from typing import Iterable, List import discord -from discord.ext.commands import Converter +from discord.ext.commands import Context, Converter from fuzzywuzzy import fuzz from bot.seasons.evergreen.snakes.utils import SNAKE_RESOURCES @@ -18,7 +19,7 @@ class Snake(Converter): snakes = None special_cases = None - async def convert(self, ctx, name): + async def convert(self, ctx: Context, name: str) -> str: """Convert the input snake name to the closest matching Snake object.""" await self.build_list() name = name.lower() @@ -26,7 +27,7 @@ class Snake(Converter): if name == 'python': return 'Python (programming language)' - def get_potential(iterable, *, threshold=80): + def get_potential(iterable: Iterable, *, threshold: int = 80) -> List[str]: nonlocal name potential = [] @@ -58,7 +59,7 @@ class Snake(Converter): return names.get(name, name) @classmethod - async def build_list(cls): + async def build_list(cls) -> None: """Build list of snakes from the static snake resources.""" # Get all the snakes if cls.snakes is None: @@ -72,7 +73,7 @@ class Snake(Converter): cls.special_cases = {snake['name'].lower(): snake for snake in special_cases} @classmethod - async def random(cls): + async def random(cls) -> str: """ Get a random Snake from the loaded resources. diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py index 38878706..1ed38f86 100644 --- a/bot/seasons/evergreen/snakes/snakes_cog.py +++ b/bot/seasons/evergreen/snakes/snakes_cog.py @@ -9,13 +9,13 @@ import textwrap import urllib from functools import partial from io import BytesIO -from typing import Any, Dict +from typing import Any, Dict, List import aiohttp import async_timeout from PIL import Image, ImageDraw, ImageFont from discord import Colour, Embed, File, Member, Message, Reaction -from discord.ext.commands import BadArgument, Bot, Cog, Context, bot_has_permissions, group +from discord.ext.commands import BadArgument, Bot, Cog, CommandError, Context, bot_has_permissions, group from bot.constants import ERROR_REPLIES, Tokens from bot.decorators import locked @@ -154,7 +154,7 @@ class Snakes(Cog): # region: Helper methods @staticmethod - def _beautiful_pastel(hue): + def _beautiful_pastel(hue: float) -> int: """Returns random bright pastels.""" light = random.uniform(0.7, 0.85) saturation = 1 @@ -250,7 +250,7 @@ class Snakes(Cog): return buffer @staticmethod - def _snakify(message): + def _snakify(message: str) -> str: """Sssnakifffiesss a sstring.""" # Replace fricatives with exaggerated snake fricatives. simple_fricatives = [ @@ -272,7 +272,7 @@ class Snakes(Cog): return message - async def _fetch(self, session, url, params=None): + async def _fetch(self, session: aiohttp.ClientSession, url: str, params: dict = None) -> dict: """Asynchronous web request helper method.""" if params is None: params = {} @@ -281,7 +281,7 @@ class Snakes(Cog): async with session.get(url, params=params) as response: return await response.json() - def _get_random_long_message(self, messages, retries=10): + def _get_random_long_message(self, messages: List[str], retries: int = 10) -> str: """ Fetch a message that's at least 3 words long, if possible to do so in retries attempts. @@ -403,9 +403,9 @@ class Snakes(Cog): """Gets a random snake name.""" return random.choice(self.snake_names) - async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list): + async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list) -> None: """Validate the answer using a reaction event loop.""" - def predicate(reaction, user): + def predicate(reaction: Reaction, user: Member) -> bool: """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. @@ -436,14 +436,14 @@ class Snakes(Cog): # region: Commands @group(name='snakes', aliases=('snake',), invoke_without_command=True) - async def snakes_group(self, ctx: Context): + async def snakes_group(self, ctx: Context) -> None: """Commands from our first code jam.""" await ctx.send_help(ctx.command) @bot_has_permissions(manage_messages=True) @snakes_group.command(name='antidote') @locked() - async def antidote_command(self, ctx: Context): + async def antidote_command(self, ctx: Context) -> None: """ Antidote! Can you create the antivenom before the patient dies? @@ -458,7 +458,7 @@ class Snakes(Cog): This game was created by Lord Bisk and Runew0lf. """ - def predicate(reaction_: Reaction, user_: Member): + def predicate(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" return ( all(( @@ -584,7 +584,7 @@ class Snakes(Cog): await board_id.clear_reactions() @snakes_group.command(name='draw') - async def draw_command(self, ctx: Context): + async def draw_command(self, ctx: Context) -> None: """ Draws a random snek using Perlin noise. @@ -672,7 +672,7 @@ class Snakes(Cog): @snakes_group.command(name='guess', aliases=('identify',)) @locked() - async def guess_command(self, ctx): + async def guess_command(self, ctx: Context) -> None: """ Snake identifying game. @@ -706,7 +706,7 @@ class Snakes(Cog): await self._validate_answer(ctx, guess, answer, options) @snakes_group.command(name='hatch') - async def hatch_command(self, ctx: Context): + async def hatch_command(self, ctx: Context) -> None: """ Hatches your personal snake. @@ -737,7 +737,7 @@ class Snakes(Cog): await ctx.channel.send(embed=my_snake_embed) @snakes_group.command(name='movie') - async def movie_command(self, ctx: Context): + async def movie_command(self, ctx: Context) -> None: """ Gets a random snake-related movie from OMDB. @@ -807,7 +807,7 @@ class Snakes(Cog): @snakes_group.command(name='quiz') @locked() - async def quiz_command(self, ctx: Context): + async def quiz_command(self, ctx: Context) -> None: """ Asks a snake-related question in the chat and validates the user's guess. @@ -832,7 +832,7 @@ class Snakes(Cog): await self._validate_answer(ctx, quiz, answer, options) @snakes_group.command(name='name', aliases=('name_gen',)) - async def name_command(self, ctx: Context, *, name: str = None): + async def name_command(self, ctx: Context, *, name: str = None) -> None: """ Snakifies a username. @@ -904,7 +904,7 @@ class Snakes(Cog): @snakes_group.command(name='sal') @locked() - async def sal_command(self, ctx: Context): + async def sal_command(self, ctx: Context) -> None: """ Play a game of Snakes and Ladders. @@ -922,7 +922,7 @@ class Snakes(Cog): await game.open_game() @snakes_group.command(name='about') - async def about_command(self, ctx: Context): + async def about_command(self, ctx: Context) -> None: """Show an embed with information about the event, its participants, and its winners.""" contributors = [ "<@!245270749919576066>", @@ -965,7 +965,7 @@ class Snakes(Cog): await ctx.channel.send(embed=embed) @snakes_group.command(name='card') - async def card_command(self, ctx: Context, *, name: Snake = None): + async def card_command(self, ctx: Context, *, name: Snake = None) -> None: """ Create an interesting little card from a snake. @@ -1003,7 +1003,7 @@ class Snakes(Cog): ) @snakes_group.command(name='fact') - async def fact_command(self, ctx: Context): + async def fact_command(self, ctx: Context) -> None: """ Gets a snake-related fact. @@ -1019,7 +1019,7 @@ class Snakes(Cog): await ctx.channel.send(embed=embed) @snakes_group.command(name='snakify') - async def snakify_command(self, ctx: Context, *, message: str = None): + async def snakify_command(self, ctx: Context, *, message: str = None) -> None: """ How would I talk if I were a snake? @@ -1060,7 +1060,7 @@ class Snakes(Cog): await ctx.channel.send(embed=embed) @snakes_group.command(name='video', aliases=('get_video',)) - async def video_command(self, ctx: Context, *, search: str = None): + async def video_command(self, ctx: Context, *, search: str = None) -> None: """ Gets a YouTube video about snakes. @@ -1100,7 +1100,7 @@ class Snakes(Cog): log.warning(f"YouTube API error. Full response looks like {response}") @snakes_group.command(name='zen') - async def zen_command(self, ctx: Context): + async def zen_command(self, ctx: Context) -> None: """ Gets a random quote from the Zen of Python, except as if spoken by a snake. @@ -1127,7 +1127,7 @@ class Snakes(Cog): @get_command.error @card_command.error @video_command.error - async def command_error(self, ctx, error): + async def command_error(self, ctx: Context, error: CommandError) -> None: """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 e8d2ee44..24e71227 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -11,7 +11,7 @@ from typing import List, Tuple from PIL import Image from PIL.ImageDraw import ImageDraw from discord import File, Member, Reaction -from discord.ext.commands import Context +from discord.ext.commands import Cog, Context SNAKE_RESOURCES = Path("bot/resources/snakes").absolute() @@ -116,12 +116,12 @@ def get_resource(file: str) -> List[dict]: return json.load(snakefile) -def smoothstep(t): +def smoothstep(t: float) -> float: """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): +def lerp(t: float, a: float, b: float) -> float: """Linear interpolation between a and b, given a fraction t.""" return a + t * (b - a) @@ -138,7 +138,7 @@ class PerlinNoiseFactory(object): Licensed under ISC """ - def __init__(self, dimension, octaves=1, tile=(), unbias=False): + def __init__(self, dimension: int, octaves: int = 1, tile: Tuple[int] = (), unbias: bool = False): """ Create a new Perlin noise factory in the given number of dimensions. @@ -152,7 +152,7 @@ class PerlinNoiseFactory(object): This will produce noise that tiles every 3 units vertically, but never tiles horizontally. - If ``unbias`` is true, the smoothstep function will be applied to the output before returning + 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 @@ -166,7 +166,7 @@ class PerlinNoiseFactory(object): self.gradient = {} - def _generate_gradient(self): + def _generate_gradient(self) -> Tuple[float, ...]: """ Generate a random unit vector at each grid point. @@ -186,7 +186,7 @@ class PerlinNoiseFactory(object): scale = sum(n * n for n in random_point) ** -0.5 return tuple(coord * scale for coord in random_point) - def get_plain_noise(self, *point): + def get_plain_noise(self, *point) -> float: """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( @@ -234,7 +234,7 @@ class PerlinNoiseFactory(object): return dots[0] * self.scale_factor - def __call__(self, *point): + def __call__(self, *point) -> float: """ Get the value of this Perlin noise function at the given point. @@ -367,7 +367,7 @@ GAME_SCREEN_EMOJI = [ class SnakeAndLaddersGame: """Snakes and Ladders game Cog.""" - def __init__(self, snakes, context: Context): + def __init__(self, snakes: Cog, context: Context): self.snakes = snakes self.ctx = context self.channel = self.ctx.channel @@ -382,14 +382,14 @@ class SnakeAndLaddersGame: self.positions = None self.rolls = [] - async def open_game(self): + async def open_game(self) -> None: """ Create a new Snakes and Ladders game. Listen for reactions until players have joined, and the game has been started. """ - def startup_event_check(reaction_: Reaction, user_: Member): + def startup_event_check(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" return ( all(( @@ -454,7 +454,7 @@ class SnakeAndLaddersGame: self.cancel_game(self.author) return # We're done, no reactions for the last 5 minutes - async def _add_player(self, user: Member): + async def _add_player(self, user: Member) -> None: self.players.append(user) self.player_tiles[user.id] = 1 @@ -462,7 +462,7 @@ class SnakeAndLaddersGame: 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): + async def player_join(self, user: Member) -> None: """ Handle players joining the game. @@ -488,7 +488,7 @@ class SnakeAndLaddersGame: delete_after=10 ) - async def player_leave(self, user: Member): + async def player_leave(self, user: Member) -> None: """ Handle players leaving the game. @@ -518,7 +518,7 @@ class SnakeAndLaddersGame: return await self.channel.send(user.mention + " You are not in the match.", delete_after=10) - async def cancel_game(self, user: Member): + async def cancel_game(self, user: Member) -> None: """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) @@ -526,7 +526,7 @@ class SnakeAndLaddersGame: await self.channel.send("**Snakes and Ladders**: Game has been canceled.") self._destruct() - async def start_game(self, user: Member): + async def start_game(self, user: Member) -> None: """ Allow the game author to begin the game. @@ -550,9 +550,9 @@ class SnakeAndLaddersGame: await self.channel.send("**Snakes and Ladders**: The game is starting!\nPlayers: " + player_list) await self.start_round() - async def start_round(self): + async def start_round(self) -> None: """Begin the round.""" - def game_event_check(reaction_: Reaction, user_: Member): + def game_event_check(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" return ( all(( @@ -642,7 +642,7 @@ class SnakeAndLaddersGame: # Round completed await self._complete_round() - async def player_roll(self, user: Member): + async def player_roll(self, user: Member) -> None: """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) @@ -674,7 +674,7 @@ class SnakeAndLaddersGame: self.player_tiles[user.id] = min(100, next_tile) self.round_has_rolled[user.id] = True - async def _complete_round(self): + async def _complete_round(self) -> None: self.state = 'post_round' # check for winner @@ -694,13 +694,13 @@ class SnakeAndLaddersGame: return next((player for player in self.players if self.player_tiles[player.id] == 100), None) - def _check_all_rolled(self): + def _check_all_rolled(self) -> bool: return all(rolled for rolled in self.round_has_rolled.values()) - def _destruct(self): + def _destruct(self) -> None: del self.snakes.active_sal[self.channel] - def _board_coordinate_from_index(self, index: int): + def _board_coordinate_from_index(self, index: int) -> Tuple[float, float]: # converts the tile number to the x/y coordinates for graphical purposes y_level = 9 - math.floor((index - 1) / 10) is_reversed = math.floor((index - 1) / 10) % 2 != 0 diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index f6a43a63..2f59c886 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -13,16 +13,16 @@ with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf-8") class Speedrun(commands.Cog): """Commands about the video game speedrunning community.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="speedrun") - async def get_speedrun(self, ctx): + async def get_speedrun(self, ctx: commands.Context) -> None: """Sends a link to a video of a random speedrun.""" await ctx.send(choice(LINKS)) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Load the Speedrun cog""" bot.add_cog(Speedrun(bot)) log.info("Speedrun cog loaded") diff --git a/bot/seasons/evergreen/uptime.py b/bot/seasons/evergreen/uptime.py index 92066e0a..6f24f545 100644 --- a/bot/seasons/evergreen/uptime.py +++ b/bot/seasons/evergreen/uptime.py @@ -12,11 +12,11 @@ log = logging.getLogger(__name__) class Uptime(commands.Cog): """A cog for posting the bot's uptime.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="uptime") - async def uptime(self, ctx): + async def uptime(self, ctx: commands.Context) -> None: """Responds with the uptime of the bot.""" difference = relativedelta(start_time - arrow.utcnow()) uptime_string = start_time.shift( @@ -28,7 +28,7 @@ class Uptime(commands.Cog): await ctx.send(f"I started up {uptime_string}.") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Uptime Cog load.""" bot.add_cog(Uptime(bot)) log.info("Uptime cog loaded") diff --git a/bot/seasons/halloween/8ball.py b/bot/seasons/halloween/8ball.py index faf59ca9..2e1c2804 100644 --- a/bot/seasons/halloween/8ball.py +++ b/bot/seasons/halloween/8ball.py @@ -15,11 +15,11 @@ with open(Path("bot/resources/halloween/responses.json"), "r", encoding="utf8") class SpookyEightBall(commands.Cog): """Spooky Eightball answers.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('spooky8ball',)) - async def spookyeightball(self, ctx, *, question: str): + async def spookyeightball(self, ctx: commands.Context, *, question: str) -> None: """Responds with a random response to a question.""" choice = random.choice(responses['responses']) msg = await ctx.send(choice[0]) @@ -28,7 +28,7 @@ class SpookyEightBall(commands.Cog): await msg.edit(content=f"{choice[0]} \n{choice[1]}") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky Eight Ball Cog Load.""" bot.add_cog(SpookyEightBall(bot)) log.info("SpookyEightBall cog loaded") diff --git a/bot/seasons/halloween/candy_collection.py b/bot/seasons/halloween/candy_collection.py index d35cbee5..65fa9af8 100644 --- a/bot/seasons/halloween/candy_collection.py +++ b/bot/seasons/halloween/candy_collection.py @@ -3,6 +3,7 @@ import json import logging import os import random +from typing import List, Union import discord from discord.ext import commands @@ -23,7 +24,7 @@ ADD_SKULL_EXISTING_REACTION_CHANCE = 20 # 5% class CandyCollection(commands.Cog): """Candy collection game Cog.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot with open(json_location) as candy: self.candy_json = json.load(candy) @@ -34,7 +35,7 @@ class CandyCollection(commands.Cog): self.get_candyinfo[userid] = userinfo @commands.Cog.listener() - async def on_message(self, message): + async def on_message(self, message: discord.Message) -> None: """Randomly adds candy or skull reaction to non-bot messages in the Event channel.""" # make sure its a human message if message.author.bot: @@ -55,7 +56,7 @@ class CandyCollection(commands.Cog): return await message.add_reaction('\N{CANDY}') @commands.Cog.listener() - async def on_reaction_add(self, reaction, user): + async def on_reaction_add(self, reaction: discord.Reaction, user: discord.Member) -> None: """Add/remove candies from a person if the reaction satisfies criteria.""" message = reaction.message # check to ensure the reactor is human @@ -101,7 +102,7 @@ class CandyCollection(commands.Cog): self.candy_json['records'].append(d) await self.remove_reactions(reaction) - async def reacted_msg_chance(self, message): + async def reacted_msg_chance(self, message: discord.Message) -> None: """ Randomly add a skull or candy reaction to a message if there is a reaction there already. @@ -118,7 +119,7 @@ class CandyCollection(commands.Cog): self.msg_reacted.append(d) return await message.add_reaction('\N{CANDY}') - async def ten_recent_msg(self): + async def ten_recent_msg(self) -> List[int]: """Get the last 10 messages sent in the channel.""" ten_recent = [] recent_msg = max(message.id for message @@ -135,7 +136,7 @@ class CandyCollection(commands.Cog): return ten_recent - async def get_message(self, msg_id): + async def get_message(self, msg_id: int) -> Union[discord.Message, None]: """Get the message from its ID.""" try: o = discord.Object(id=msg_id + 1) @@ -151,11 +152,11 @@ class CandyCollection(commands.Cog): except Exception: return None - async def hacktober_channel(self): + async def hacktober_channel(self) -> discord.TextChannel: """Get #hacktoberbot channel from its ID.""" return self.bot.get_channel(id=Channels.seasonalbot_chat) - async def remove_reactions(self, reaction): + async def remove_reactions(self, reaction: discord.Reaction) -> None: """Remove all candy/skull reactions.""" try: async for user in reaction.users(): @@ -164,20 +165,20 @@ class CandyCollection(commands.Cog): except discord.HTTPException: pass - async def send_spook_msg(self, author, channel, candies): + async def send_spook_msg(self, author: discord.Member, channel: discord.TextChannel, candies: int) -> None: """Send a spooky message.""" e = discord.Embed(colour=author.colour) e.set_author(name="Ghosts and Ghouls and Jack o' lanterns at night; " f"I took {candies} candies and quickly took flight.") await channel.send(embed=e) - def save_to_json(self): + def save_to_json(self) -> None: """Save JSON to a local file.""" with open(json_location, 'w') as outfile: json.dump(self.candy_json, outfile) @commands.command() - async def candy(self, ctx): + async def candy(self, ctx: commands.Context) -> None: """Get the candy leaderboard and save to JSON.""" # Use run_in_executor to prevent blocking thing = functools.partial(self.save_to_json) @@ -213,7 +214,7 @@ class CandyCollection(commands.Cog): await ctx.send(embed=e) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Candy Collection game Cog load.""" bot.add_cog(CandyCollection(bot)) log.info("CandyCollection cog loaded") diff --git a/bot/seasons/halloween/halloween_facts.py b/bot/seasons/halloween/halloween_facts.py index f09aa4ad..f8610bd3 100644 --- a/bot/seasons/halloween/halloween_facts.py +++ b/bot/seasons/halloween/halloween_facts.py @@ -3,6 +3,7 @@ import logging import random from datetime import timedelta from pathlib import Path +from typing import Tuple import discord from discord.ext import commands @@ -28,7 +29,7 @@ INTERVAL = timedelta(hours=6).total_seconds() class HalloweenFacts(commands.Cog): """A Cog for displaying interesting facts about Halloween.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot with open(Path("bot/resources/halloween/halloween_facts.json"), "r") as file: self.halloween_facts = json.load(file) @@ -37,31 +38,31 @@ class HalloweenFacts(commands.Cog): random.shuffle(self.facts) @commands.Cog.listener() - async def on_ready(self): + async def on_ready(self) -> None: """Get event Channel object and initialize fact task loop.""" self.channel = self.bot.get_channel(Channels.seasonalbot_chat) self.bot.loop.create_task(self._fact_publisher_task()) - def random_fact(self): + def random_fact(self) -> Tuple[int, str]: """Return a random fact from the loaded facts.""" return random.choice(self.facts) @commands.command(name="spookyfact", aliases=("halloweenfact",), brief="Get the most recent Halloween fact") - async def get_random_fact(self, ctx): + async def get_random_fact(self, ctx: commands.Context) -> None: """Reply with the most recent Halloween fact.""" index, fact = self.random_fact() embed = self._build_embed(index, fact) await ctx.send(embed=embed) @staticmethod - def _build_embed(index, fact): + def _build_embed(index: int, fact: str) -> discord.Embed: """Builds a Discord embed from the given fact and its index.""" emoji = random.choice(SPOOKY_EMOJIS) title = f"{emoji} Halloween Fact #{index + 1}" return discord.Embed(title=title, description=fact, color=PUMPKIN_ORANGE) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Halloween facts Cog load.""" bot.add_cog(HalloweenFacts(bot)) log.info("HalloweenFacts cog loaded") diff --git a/bot/seasons/halloween/halloweenify.py b/bot/seasons/halloween/halloweenify.py index 334781ab..dfcc2b1e 100644 --- a/bot/seasons/halloween/halloweenify.py +++ b/bot/seasons/halloween/halloweenify.py @@ -13,12 +13,12 @@ log = logging.getLogger(__name__) class Halloweenify(commands.Cog): """A cog to change a invokers nickname to a spooky one!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.cooldown(1, 300, BucketType.user) @commands.command() - async def halloweenify(self, ctx): + async def halloweenify(self, ctx: commands.Context) -> None: """Change your nickname into a much spookier one!""" async with ctx.typing(): with open(Path("bot/resources/halloween/halloweenify.json"), "r") as f: @@ -46,7 +46,7 @@ class Halloweenify(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Halloweenify Cog load.""" bot.add_cog(Halloweenify(bot)) log.info("Halloweenify cog loaded") diff --git a/bot/seasons/halloween/monstersurvey.py b/bot/seasons/halloween/monstersurvey.py index 173ce8eb..cfd3edaf 100644 --- a/bot/seasons/halloween/monstersurvey.py +++ b/bot/seasons/halloween/monstersurvey.py @@ -30,7 +30,7 @@ class MonsterSurvey(Cog): with open(self.registry_location, 'r') as jason: self.voter_registry = json.load(jason) - def json_write(self): + def json_write(self) -> None: """Write voting results to a local JSON file.""" log.info("Saved Monster Survey Results") with open(self.registry_location, 'w') as jason: @@ -50,7 +50,7 @@ class MonsterSurvey(Cog): if id in vr[m]['votes'] and m != monster: vr[m]['votes'].remove(id) - def get_name_by_leaderboard_index(self, n): + def get_name_by_leaderboard_index(self, n: int) -> str: """Return the monster at the specified leaderboard index.""" n = n - 1 vr = self.voter_registry @@ -62,7 +62,7 @@ class MonsterSurvey(Cog): name='monster', aliases=('ms',) ) - async def monster_group(self, ctx: Context): + async def monster_group(self, ctx: Context) -> None: """The base voting command. If nothing is called, then it will return an embed.""" if ctx.invoked_subcommand is None: async with ctx.typing(): @@ -92,7 +92,7 @@ class MonsterSurvey(Cog): @monster_group.command( name='vote' ) - async def monster_vote(self, ctx: Context, name=None): + async def monster_vote(self, ctx: Context, name: str = None) -> None: """ Cast a vote for a particular monster. @@ -143,7 +143,7 @@ class MonsterSurvey(Cog): @monster_group.command( name='show' ) - async def monster_show(self, ctx: Context, name=None) -> None: + async def monster_show(self, ctx: Context, name: str = None) -> None: """Shows the named monster. If one is not named, it sends the default voting embed instead.""" if name is None: await ctx.invoke(self.monster_leaderboard) @@ -200,7 +200,7 @@ class MonsterSurvey(Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: Bot) -> None: """Monster survey Cog load.""" bot.add_cog(MonsterSurvey(bot)) log.info("MonsterSurvey cog loaded") diff --git a/bot/seasons/halloween/scarymovie.py b/bot/seasons/halloween/scarymovie.py index cd95a3a2..3823a3e4 100644 --- a/bot/seasons/halloween/scarymovie.py +++ b/bot/seasons/halloween/scarymovie.py @@ -16,11 +16,11 @@ TMDB_TOKEN = environ.get('TMDB_TOKEN') class ScaryMovie(commands.Cog): """Selects a random scary movie and embeds info into Discord chat.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name='scarymovie', alias=['smovie']) - async def random_movie(self, ctx): + async def random_movie(self, ctx: commands.Context) -> None: """Randomly select a scary movie and display information about it.""" async with ctx.typing(): selection = await self.select_movie() @@ -29,7 +29,7 @@ class ScaryMovie(commands.Cog): await ctx.send(embed=movie_details) @staticmethod - async def select_movie(): + async def select_movie() -> dict: """Selects a random movie and returns a JSON of movie details from TMDb.""" url = 'https://api.themoviedb.org/4/discover/movie' params = { @@ -62,7 +62,7 @@ class ScaryMovie(commands.Cog): return await selection.json() @staticmethod - async def format_metadata(movie): + async def format_metadata(movie: dict) -> Embed: """Formats raw TMDb data to be embedded in Discord chat.""" # Build the relevant URLs. movie_id = movie.get("id") @@ -126,7 +126,7 @@ class ScaryMovie(commands.Cog): return embed -def setup(bot): +def setup(bot: commands.Bot) -> None: """Scary movie Cog load.""" bot.add_cog(ScaryMovie(bot)) log.info("ScaryMovie cog loaded") diff --git a/bot/seasons/halloween/spookyavatar.py b/bot/seasons/halloween/spookyavatar.py index 9bdef1a8..268de3fb 100644 --- a/bot/seasons/halloween/spookyavatar.py +++ b/bot/seasons/halloween/spookyavatar.py @@ -15,10 +15,10 @@ log = logging.getLogger(__name__) class SpookyAvatar(commands.Cog): """A cog that spookifies an avatar.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot - async def get(self, url): + async def get(self, url: str) -> bytes: """Returns the contents of the supplied URL.""" async with aiohttp.ClientSession() as session: async with session.get(url) as resp: @@ -26,7 +26,7 @@ class SpookyAvatar(commands.Cog): @commands.command(name='savatar', aliases=('spookyavatar', 'spookify'), brief='Spookify an user\'s avatar.') - async def spooky_avatar(self, ctx, user: discord.Member = None): + async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: """A command to print the user's spookified avatar.""" if user is None: user = ctx.message.author @@ -47,7 +47,7 @@ class SpookyAvatar(commands.Cog): os.remove(str(ctx.message.id)+'.png') -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky avatar Cog load.""" bot.add_cog(SpookyAvatar(bot)) log.info("SpookyAvatar cog loaded") diff --git a/bot/seasons/halloween/spookygif.py b/bot/seasons/halloween/spookygif.py index ba2ad6e5..818de8cd 100644 --- a/bot/seasons/halloween/spookygif.py +++ b/bot/seasons/halloween/spookygif.py @@ -12,11 +12,11 @@ log = logging.getLogger(__name__) class SpookyGif(commands.Cog): """A cog to fetch a random spooky gif from the web!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="spookygif", aliases=("sgif", "scarygif")) - async def spookygif(self, ctx): + async def spookygif(self, ctx: commands.Context) -> None: """Fetches a random gif from the GIPHY API and responds with it.""" async with ctx.typing(): async with aiohttp.ClientSession() as session: @@ -33,7 +33,7 @@ class SpookyGif(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky GIF Cog load.""" bot.add_cog(SpookyGif(bot)) log.info("SpookyGif cog loaded") diff --git a/bot/seasons/halloween/spookyrating.py b/bot/seasons/halloween/spookyrating.py index 08c17a27..02e6f6b9 100644 --- a/bot/seasons/halloween/spookyrating.py +++ b/bot/seasons/halloween/spookyrating.py @@ -19,13 +19,13 @@ with Path("bot/resources/halloween/spooky_rating.json").open() as file: class SpookyRating(commands.Cog): """A cog for calculating one's spooky rating""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.local_random = random.Random() @commands.command() @commands.cooldown(rate=1, per=5, type=commands.BucketType.user) - async def spookyrating(self, ctx, who: discord.Member = None): + async def spookyrating(self, ctx: commands.Context, who: discord.Member = None) -> None: """ Calculates the spooky rating of someone. @@ -61,7 +61,7 @@ class SpookyRating(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky Rating Cog load.""" bot.add_cog(SpookyRating(bot)) log.info("SpookyRating cog loaded") diff --git a/bot/seasons/halloween/spookyreact.py b/bot/seasons/halloween/spookyreact.py index 5a086072..90b1254d 100644 --- a/bot/seasons/halloween/spookyreact.py +++ b/bot/seasons/halloween/spookyreact.py @@ -2,7 +2,7 @@ import logging import re import discord -from discord.ext.commands import Cog +from discord.ext.commands import Bot, Cog log = logging.getLogger(__name__) @@ -20,11 +20,11 @@ SPOOKY_TRIGGERS = { class SpookyReact(Cog): """A cog that makes the bot react to message triggers.""" - def __init__(self, bot): + def __init__(self, bot: Bot): self.bot = bot @Cog.listener() - async def on_message(self, ctx: discord.Message): + async def on_message(self, ctx: discord.Message) -> None: """ A command to send the seasonalbot github project. @@ -66,7 +66,7 @@ class SpookyReact(Cog): return False -def setup(bot): +def setup(bot: Bot) -> None: """Spooky reaction Cog load.""" bot.add_cog(SpookyReact(bot)) log.info("SpookyReact cog loaded") diff --git a/bot/seasons/halloween/spookysound.py b/bot/seasons/halloween/spookysound.py index 44fdd9d6..e0676d0a 100644 --- a/bot/seasons/halloween/spookysound.py +++ b/bot/seasons/halloween/spookysound.py @@ -13,14 +13,14 @@ log = logging.getLogger(__name__) class SpookySound(commands.Cog): """A cog that plays a spooky sound in a voice channel on command.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.sound_files = list(Path("bot/resources/halloween/spookysounds").glob("*.mp3")) self.channel = None @commands.cooldown(rate=1, per=1) @commands.command(brief="Play a spooky sound, restricted to once per 2 mins") - async def spookysound(self, ctx): + async def spookysound(self, ctx: commands.Context) -> None: """ Connect to the Hacktoberbot voice channel, play a random spooky sound, then disconnect. @@ -37,12 +37,12 @@ class SpookySound(commands.Cog): voice.play(src, after=lambda e: self.bot.loop.create_task(self.disconnect(voice))) @staticmethod - async def disconnect(voice): + async def disconnect(voice: discord.VoiceClient) -> None: """Helper method to disconnect a given voice client.""" await voice.disconnect() -def setup(bot): +def setup(bot: commands.Bot) -> None: """Spooky sound Cog load.""" bot.add_cog(SpookySound(bot)) log.info("SpookySound cog loaded") diff --git a/bot/seasons/halloween/timeleft.py b/bot/seasons/halloween/timeleft.py index a2b16a6c..77767baa 100644 --- a/bot/seasons/halloween/timeleft.py +++ b/bot/seasons/halloween/timeleft.py @@ -1,5 +1,6 @@ import logging from datetime import datetime +from typing import Tuple from discord.ext import commands @@ -9,16 +10,16 @@ log = logging.getLogger(__name__) class TimeLeft(commands.Cog): """A Cog that tells you how long left until Hacktober is over!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - def in_october(): + def in_october() -> bool: """Return True if the current month is October.""" return datetime.utcnow().month == 10 @staticmethod - def load_date(): + def load_date() -> Tuple[int, datetime, datetime]: """Return of a tuple of the current time and the end and start times of the next October.""" now = datetime.utcnow() year = now.year @@ -29,7 +30,7 @@ class TimeLeft(commands.Cog): return now, end, start @commands.command() - async def timeleft(self, ctx): + async def timeleft(self, ctx: commands.Context) -> None: """ Calculates the time left until the end of Hacktober. @@ -53,7 +54,7 @@ class TimeLeft(commands.Cog): ) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog load.""" bot.add_cog(TimeLeft(bot)) log.info("TimeLeft cog loaded") diff --git a/bot/seasons/season.py b/bot/seasons/season.py index c88ef2a7..4e2141c7 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -264,7 +264,7 @@ class SeasonBase: return await self.apply_server_icon() - async def announce_season(self): + async def announce_season(self) -> None: """ Announces a change in season in the announcement channel. @@ -320,7 +320,7 @@ class SeasonBase: await channel.send(mention, embed=embed) - async def load(self): + async def load(self) -> None: """ Loads extensions, bot name and avatar, server icon and announces new season. @@ -361,7 +361,7 @@ class SeasonBase: class SeasonManager(commands.Cog): """A cog for managing seasons.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.season = get_season(date=datetime.datetime.utcnow()) self.season_task = bot.loop.create_task(self.load_seasons()) @@ -378,7 +378,7 @@ class SeasonManager(commands.Cog): ) self.sleep_time = (midnight - datetime.datetime.now()).seconds + 60 - async def load_seasons(self): + async def load_seasons(self) -> None: """Asynchronous timer loop to check for a new season every midnight.""" await self.bot.wait_until_ready() await self.season.load() @@ -397,7 +397,7 @@ class SeasonManager(commands.Cog): @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.command(name="season") - async def change_season(self, ctx, new_season: str): + async def change_season(self, ctx: commands.Context, new_season: str) -> None: """Changes the currently active season on the bot.""" self.season = get_season(season_name=new_season) await self.season.load() @@ -405,10 +405,10 @@ class SeasonManager(commands.Cog): @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.command(name="seasons") - async def show_seasons(self, ctx): + async def show_seasons(self, ctx: commands.Context) -> None: """Shows the available seasons and their dates.""" # Sort by start order, followed by lower duration - def season_key(season_class: Type[SeasonBase]): + def season_key(season_class: Type[SeasonBase]) -> Tuple[datetime.datetime, datetime.timedelta]: return season_class.start(), season_class.end() - datetime.datetime.max current_season = self.season.name @@ -448,13 +448,13 @@ class SeasonManager(commands.Cog): @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.group() - async def refresh(self, ctx): + async def refresh(self, ctx: commands.Context) -> None: """Refreshes certain seasonal elements without reloading seasons.""" if not ctx.invoked_subcommand: await ctx.send_help(ctx.command) @refresh.command(name="avatar") - async def refresh_avatar(self, ctx): + async def refresh_avatar(self, ctx: commands.Context) -> None: """Re-applies the bot avatar for the currently loaded season.""" # Attempt the change is_changed = await self.season.apply_avatar() @@ -477,7 +477,7 @@ class SeasonManager(commands.Cog): await ctx.send(embed=embed) @refresh.command(name="icon") - async def refresh_server_icon(self, ctx): + async def refresh_server_icon(self, ctx: commands.Context) -> None: """Re-applies the server icon for the currently loaded season.""" # Attempt the change is_changed = await self.season.apply_server_icon() @@ -500,7 +500,7 @@ class SeasonManager(commands.Cog): await ctx.send(embed=embed) @refresh.command(name="username", aliases=("name",)) - async def refresh_username(self, ctx): + async def refresh_username(self, ctx: commands.Context) -> None: """Re-applies the bot username for the currently loaded season.""" old_username = str(bot.user) old_display_name = ctx.guild.me.display_name @@ -539,10 +539,10 @@ class SeasonManager(commands.Cog): @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.command() - async def announce(self, ctx): + async def announce(self, ctx: commands.Context) -> None: """Announces the currently loaded season.""" await self.season.announce_season() - def cog_unload(self): + def cog_unload(self) -> None: """Cancel season-related tasks on cog unload.""" self.season_task.cancel() -- cgit v1.2.3 From 4b18d7e430d5cea16406c65349718f72919c01c3 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Mon, 9 Sep 2019 16:37:13 -0400 Subject: Lint remaining files hacktoberstats cog handled in separate PR --- bot/__init__.py | 2 +- bot/decorators.py | 6 +++--- bot/seasons/pride/pride_anthem.py | 6 +++--- bot/seasons/pride/pride_avatar.py | 12 +++++------ bot/seasons/valentines/be_my_valentine.py | 34 +++++++++++++++++------------- bot/seasons/valentines/lovecalculator.py | 6 +++--- bot/seasons/valentines/movie_generator.py | 6 +++--- bot/seasons/valentines/myvalenstate.py | 8 +++---- bot/seasons/valentines/pickuplines.py | 6 +++--- bot/seasons/valentines/savethedate.py | 6 +++--- bot/seasons/valentines/valentine_zodiac.py | 8 +++---- bot/seasons/valentines/whoisvalentine.py | 8 +++---- bot/utils/__init__.py | 4 ++-- bot/utils/halloween/spookifications.py | 8 +++---- 14 files changed, 62 insertions(+), 58 deletions(-) (limited to 'bot') diff --git a/bot/__init__.py b/bot/__init__.py index 8950423f..4729e50c 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -13,7 +13,7 @@ logging.TRACE = 5 logging.addLevelName(logging.TRACE, "TRACE") -def monkeypatch_trace(self, msg: str, *args, **kwargs) -> None: +def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None: """ Log 'msg % args' with severity 'TRACE'. diff --git a/bot/decorators.py b/bot/decorators.py index e12c3f34..dbaad4a2 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -117,7 +117,7 @@ def override_in_channel(func: typing.Callable) -> typing.Callable: return func -def locked(): +def locked() -> typing.Union[typing.Callable, None]: """ Allows the user to only run one instance of the decorated command at a time. @@ -125,11 +125,11 @@ def locked(): This decorator has to go before (below) the `command` decorator. """ - def wrap(func: typing.Callable): + def wrap(func: typing.Callable) -> typing.Union[typing.Callable, None]: func.__locks = WeakValueDictionary() @wraps(func) - async def inner(self, ctx: Context, *args, **kwargs): + async def inner(self: typing.Callable, ctx: Context, *args, **kwargs) -> typing.Union[typing.Callable, None]: lock = func.__locks.setdefault(ctx.author.id, Lock()) if lock.locked(): embed = Embed() diff --git a/bot/seasons/pride/pride_anthem.py b/bot/seasons/pride/pride_anthem.py index f226f4bb..b0c6d34e 100644 --- a/bot/seasons/pride/pride_anthem.py +++ b/bot/seasons/pride/pride_anthem.py @@ -11,7 +11,7 @@ log = logging.getLogger(__name__) class PrideAnthem(commands.Cog): """Embed a random youtube video for a gay anthem!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.anthems = self.load_vids() @@ -39,7 +39,7 @@ class PrideAnthem(commands.Cog): return anthems @commands.command(name="prideanthem", aliases=["anthem", "pridesong"]) - async def prideanthem(self, ctx, genre: str = None): + async def prideanthem(self, ctx: commands.Context, genre: str = None) -> None: """ Sends a message with a video of a random pride anthem. @@ -52,7 +52,7 @@ class PrideAnthem(commands.Cog): await ctx.send("I couldn't find a video, sorry!") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog loader for pride anthem.""" bot.add_cog(PrideAnthem(bot)) log.info("Pride anthems cog loaded!") diff --git a/bot/seasons/pride/pride_avatar.py b/bot/seasons/pride/pride_avatar.py index a5b38d20..85e49d5c 100644 --- a/bot/seasons/pride/pride_avatar.py +++ b/bot/seasons/pride/pride_avatar.py @@ -56,11 +56,11 @@ OPTIONS = { class PrideAvatar(commands.Cog): """Put an LGBT spin on your avatar!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - def crop_avatar(avatar): + def crop_avatar(avatar: Image) -> Image: """This crops the avatar into a circle.""" mask = Image.new("L", avatar.size, 0) draw = ImageDraw.Draw(mask) @@ -69,7 +69,7 @@ class PrideAvatar(commands.Cog): return avatar @staticmethod - def crop_ring(ring, px): + def crop_ring(ring: Image, px: int) -> Image: """This crops the ring into a circle.""" mask = Image.new("L", ring.size, 0) draw = ImageDraw.Draw(mask) @@ -79,7 +79,7 @@ class PrideAvatar(commands.Cog): return ring @commands.group(aliases=["avatarpride", "pridepfp", "prideprofile"], invoke_without_command=True) - async def prideavatar(self, ctx, option="lgbt", pixels: int = 64): + async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: """ This surrounds an avatar with a border of a specified LGBT flag. @@ -126,7 +126,7 @@ class PrideAvatar(commands.Cog): await ctx.send(file=file, embed=embed) @prideavatar.command() - async def flags(self, ctx): + async def flags(self, ctx: commands.Context) -> None: """This lists the flags that can be used with the prideavatar command.""" choices = sorted(set(OPTIONS.values())) options = "• " + "\n• ".join(choices) @@ -139,7 +139,7 @@ class PrideAvatar(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog load.""" bot.add_cog(PrideAvatar(bot)) log.info("PrideAvatar cog loaded") diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index c4acf17a..a073e1bd 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -1,8 +1,8 @@ import logging import random -import typing from json import load from pathlib import Path +from typing import Optional, Tuple import discord from discord.ext import commands @@ -18,12 +18,12 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea class BeMyValentine(commands.Cog): """A cog that sends Valentines to other users!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.valentines = self.load_json() @staticmethod - def load_json(): + def load_json() -> dict: """Load Valentines messages from the static resources.""" p = Path("bot/resources/valentines/bemyvalentine_valentines.json") with p.open() as json_data: @@ -31,7 +31,7 @@ class BeMyValentine(commands.Cog): return valentines @commands.group(name="lovefest", invoke_without_command=True) - async def lovefest_role(self, ctx): + async def lovefest_role(self, ctx: commands.Context) -> None: """ Subscribe or unsubscribe from the lovefest role. @@ -43,7 +43,7 @@ class BeMyValentine(commands.Cog): await ctx.send_help(ctx.command) @lovefest_role.command(name="sub") - async def add_role(self, ctx): + async def add_role(self, ctx: commands.Context) -> None: """Adds the lovefest role.""" user = ctx.author role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) @@ -54,7 +54,7 @@ class BeMyValentine(commands.Cog): await ctx.send("You already have the role !") @lovefest_role.command(name="unsub") - async def remove_role(self, ctx): + async def remove_role(self, ctx: commands.Context) -> None: """Removes the lovefest role.""" user = ctx.author role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) @@ -66,7 +66,9 @@ class BeMyValentine(commands.Cog): @commands.cooldown(1, 1800, BucketType.user) @commands.group(name='bemyvalentine', invoke_without_command=True) - async def send_valentine(self, ctx, user: typing.Optional[discord.Member] = None, *, valentine_type=None): + async def send_valentine( + self, ctx: commands.Context, user: Optional[discord.Member] = None, *, valentine_type: str = None + ) -> None: """ Send a valentine to user, if specified, or to a random user with the lovefest role. @@ -112,7 +114,9 @@ class BeMyValentine(commands.Cog): @commands.cooldown(1, 1800, BucketType.user) @send_valentine.command(name='secret') - async def anonymous(self, ctx, user: typing.Optional[discord.Member] = None, *, valentine_type=None): + async def anonymous( + self, ctx: commands.Context, user: Optional[discord.Member] = None, *, valentine_type: str = None + ) -> None: """ Send an anonymous Valentine via DM to to a user, if specified, or to a random with the lovefest role. @@ -164,7 +168,7 @@ class BeMyValentine(commands.Cog): else: await ctx.author.send(f"Your message has been sent to {user}") - def valentine_check(self, valentine_type): + def valentine_check(self, valentine_type: str) -> Tuple[str, str]: """Return the appropriate Valentine type & title based on the invoking user's input.""" if valentine_type is None: valentine, title = self.random_valentine() @@ -184,7 +188,7 @@ class BeMyValentine(commands.Cog): return valentine, title @staticmethod - def random_user(author: discord.Member, members: discord.Member): + def random_user(author: discord.Member, members: discord.Member) -> None: """ Picks a random member from the list provided in `members`. @@ -196,13 +200,13 @@ class BeMyValentine(commands.Cog): return random.choice(members) if members else None @staticmethod - def random_emoji(): + def random_emoji() -> Tuple[str, str]: """Return two random emoji from the module-defined constants.""" EMOJI_1 = random.choice(HEART_EMOJIS) EMOJI_2 = random.choice(HEART_EMOJIS) return EMOJI_1, EMOJI_2 - def random_valentine(self): + def random_valentine(self) -> Tuple[str, str]: """Grabs a random poem or a compliment (any message).""" valentine_poem = random.choice(self.valentines['valentine_poems']) valentine_compliment = random.choice(self.valentines['valentine_compliments']) @@ -213,18 +217,18 @@ class BeMyValentine(commands.Cog): title = 'A compliment for ' return random_valentine, title - def valentine_poem(self): + def valentine_poem(self) -> str: """Grabs a random poem.""" valentine_poem = random.choice(self.valentines['valentine_poems']) return valentine_poem - def valentine_compliment(self): + def valentine_compliment(self) -> str: """Grabs a random compliment.""" valentine_compliment = random.choice(self.valentines['valentine_compliments']) return valentine_compliment -def setup(bot): +def setup(bot: commands.Bot) -> None: """Be my Valentine Cog load.""" bot.add_cog(BeMyValentine(bot)) log.info("BeMyValentine cog loaded") diff --git a/bot/seasons/valentines/lovecalculator.py b/bot/seasons/valentines/lovecalculator.py index 1d5a028d..207ef557 100644 --- a/bot/seasons/valentines/lovecalculator.py +++ b/bot/seasons/valentines/lovecalculator.py @@ -23,12 +23,12 @@ with Path("bot/resources/valentines/love_matches.json").open() as file: class LoveCalculator(Cog): """A cog for calculating the love between two people.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('love_calculator', 'love_calc')) @commands.cooldown(rate=1, per=5, type=commands.BucketType.user) - async def love(self, ctx, who: Union[Member, str], whom: Union[Member, str] = None): + async def love(self, ctx: commands.Context, who: Union[Member, str], whom: Union[Member, str] = None) -> None: """ Tells you how much the two love each other. @@ -98,7 +98,7 @@ class LoveCalculator(Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Love calculator Cog load.""" bot.add_cog(LoveCalculator(bot)) log.info("LoveCalculator cog loaded") diff --git a/bot/seasons/valentines/movie_generator.py b/bot/seasons/valentines/movie_generator.py index fa5f236a..ce1d7d5b 100644 --- a/bot/seasons/valentines/movie_generator.py +++ b/bot/seasons/valentines/movie_generator.py @@ -14,11 +14,11 @@ log = logging.getLogger(__name__) class RomanceMovieFinder(commands.Cog): """A Cog that returns a random romance movie suggestion to a user.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="romancemovie") - async def romance_movie(self, ctx): + async def romance_movie(self, ctx: commands.Context) -> None: """Randomly selects a romance movie and displays information about it.""" # Selecting a random int to parse it to the page parameter random_page = random.randint(0, 20) @@ -57,7 +57,7 @@ class RomanceMovieFinder(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Romance movie Cog load.""" bot.add_cog(RomanceMovieFinder(bot)) log.info("RomanceMovieFinder cog loaded") diff --git a/bot/seasons/valentines/myvalenstate.py b/bot/seasons/valentines/myvalenstate.py index fad202e3..0256c39a 100644 --- a/bot/seasons/valentines/myvalenstate.py +++ b/bot/seasons/valentines/myvalenstate.py @@ -18,10 +18,10 @@ with open(Path("bot/resources/valentines/valenstates.json"), "r") as file: class MyValenstate(commands.Cog): """A Cog to find your most likely Valentine's vacation destination.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot - def levenshtein(self, source, goal): + def levenshtein(self, source: str, goal: str) -> int: """Calculates the Levenshtein Distance between source and goal.""" if len(source) < len(goal): return self.levenshtein(goal, source) @@ -42,7 +42,7 @@ class MyValenstate(commands.Cog): return pre_row[-1] @commands.command() - async def myvalenstate(self, ctx, *, name=None): + async def myvalenstate(self, ctx: commands.Context, *, name: str = None) -> None: """Find the vacation spot(s) with the most matching characters to the invoking user.""" eq_chars = collections.defaultdict(int) if name is None: @@ -81,7 +81,7 @@ class MyValenstate(commands.Cog): await ctx.channel.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Valenstate Cog load.""" bot.add_cog(MyValenstate(bot)) log.info("MyValenstate cog loaded") diff --git a/bot/seasons/valentines/pickuplines.py b/bot/seasons/valentines/pickuplines.py index 46772197..8b2c9822 100644 --- a/bot/seasons/valentines/pickuplines.py +++ b/bot/seasons/valentines/pickuplines.py @@ -17,11 +17,11 @@ with open(Path("bot/resources/valentines/pickup_lines.json"), "r", encoding="utf class PickupLine(commands.Cog): """A cog that gives random cheesy pickup lines.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command() - async def pickupline(self, ctx): + async def pickupline(self, ctx: commands.Context) -> None: """ Gives you a random pickup line. @@ -39,7 +39,7 @@ class PickupLine(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Pickup lines Cog load.""" bot.add_cog(PickupLine(bot)) log.info('PickupLine cog loaded') diff --git a/bot/seasons/valentines/savethedate.py b/bot/seasons/valentines/savethedate.py index 34264183..e0bc3904 100644 --- a/bot/seasons/valentines/savethedate.py +++ b/bot/seasons/valentines/savethedate.py @@ -19,11 +19,11 @@ with open(Path("bot/resources/valentines/date_ideas.json"), "r", encoding="utf8" class SaveTheDate(commands.Cog): """A cog that gives random suggestion for a Valentine's date.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command() - async def savethedate(self, ctx): + async def savethedate(self, ctx: commands.Context) -> None: """Gives you ideas for what to do on a date with your valentine.""" random_date = random.choice(VALENTINES_DATES['ideas']) emoji_1 = random.choice(HEART_EMOJIS) @@ -36,7 +36,7 @@ class SaveTheDate(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Save the date Cog Load.""" bot.add_cog(SaveTheDate(bot)) log.info("SaveTheDate cog loaded") diff --git a/bot/seasons/valentines/valentine_zodiac.py b/bot/seasons/valentines/valentine_zodiac.py index fa849cb2..c8d77e75 100644 --- a/bot/seasons/valentines/valentine_zodiac.py +++ b/bot/seasons/valentines/valentine_zodiac.py @@ -17,12 +17,12 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.zodiacs = self.load_json() @staticmethod - def load_json(): + def load_json() -> dict: """Load zodiac compatibility from static JSON resource.""" p = Path("bot/resources/valentines/zodiac_compatibility.json") with p.open() as json_data: @@ -30,7 +30,7 @@ class ValentineZodiac(commands.Cog): return zodiacs @commands.command(name="partnerzodiac") - async def counter_zodiac(self, ctx, zodiac_sign): + async def counter_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" try: compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) @@ -52,7 +52,7 @@ class ValentineZodiac(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Valentine zodiac Cog load.""" bot.add_cog(ValentineZodiac(bot)) log.info("ValentineZodiac cog loaded") diff --git a/bot/seasons/valentines/whoisvalentine.py b/bot/seasons/valentines/whoisvalentine.py index d73ccd9b..b8586dca 100644 --- a/bot/seasons/valentines/whoisvalentine.py +++ b/bot/seasons/valentines/whoisvalentine.py @@ -17,11 +17,11 @@ with open(Path("bot/resources/valentines/valentine_facts.json"), "r") as file: class ValentineFacts(commands.Cog): """A Cog for displaying facts about Saint Valentine.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('whoisvalentine', 'saint_valentine')) - async def who_is_valentine(self, ctx): + async def who_is_valentine(self, ctx: commands.Context) -> None: """Displays info about Saint Valentine.""" embed = discord.Embed( title="Who is Saint Valentine?", @@ -36,7 +36,7 @@ class ValentineFacts(commands.Cog): await ctx.channel.send(embed=embed) @commands.command() - async def valentine_fact(self, ctx): + async def valentine_fact(self, ctx: commands.Context) -> None: """Shows a random fact about Valentine's Day.""" embed = discord.Embed( title=choice(FACTS['titles']), @@ -47,7 +47,7 @@ class ValentineFacts(commands.Cog): await ctx.channel.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Who is Valentine Cog load.""" bot.add_cog(ValentineFacts(bot)) log.info("ValentineFacts cog loaded") diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 3249a9cf..8732eb22 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -29,7 +29,7 @@ async def disambiguate( choices = (f'{index}: {entry}' for index, entry in enumerate(entries, start=1)) - def check(message): + def check(message: discord.Message) -> bool: return (message.content.isdigit() and message.author == ctx.author and message.channel == ctx.channel) @@ -109,7 +109,7 @@ def replace_many( pattern = "|".join(re.escape(word) for word in words_to_replace) regex = re.compile(pattern, re.I if ignore_case else 0) - def _repl(match): + def _repl(match: re.Match) -> str: """Returns replacement depending on `ignore_case` and `match_case`""" word = match.group(0) replacement = replacements[word.lower() if ignore_case else word] diff --git a/bot/utils/halloween/spookifications.py b/bot/utils/halloween/spookifications.py index 69b49919..11f69850 100644 --- a/bot/utils/halloween/spookifications.py +++ b/bot/utils/halloween/spookifications.py @@ -7,7 +7,7 @@ from PIL import ImageOps log = logging.getLogger() -def inversion(im): +def inversion(im: Image) -> Image: """ Inverts the image. @@ -18,7 +18,7 @@ def inversion(im): return inv -def pentagram(im): +def pentagram(im: Image) -> Image: """Adds pentagram to the image.""" im = im.convert('RGB') wt, ht = im.size @@ -28,7 +28,7 @@ def pentagram(im): return im -def bat(im): +def bat(im: Image) -> Image: """ Adds a bat silhoutte to the image. @@ -50,7 +50,7 @@ def bat(im): return im -def get_random_effect(im): +def get_random_effect(im: Image) -> Image: """Randomly selects and applies an effect.""" effects = [inversion, pentagram, bat] effect = choice(effects) -- cgit v1.2.3 From 6db70919837823b2aefea58e684cf60c2ada8463 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Wed, 11 Sep 2019 22:53:14 +0800 Subject: Add embed conversion functionality to .uwu --- bot/seasons/evergreen/fun.py | 75 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 14 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 87077f36..997bab20 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -1,6 +1,9 @@ +import functools import logging import random +from typing import Callable, Tuple, Union +from discord import Embed, Message from discord.ext import commands from discord.ext.commands import Bot, Cog, Context, MessageConverter @@ -43,38 +46,82 @@ class Fun(Cog): @commands.command(name="uwu", aliases=("uwuwize", "uwuify",)) async def uwu_command(self, ctx: Context, *, text: str) -> None: - """Converts a given `text` into it's uwu equivalent.""" - text = await Fun.get_discord_message(ctx, text) - converted = utils.replace_many(text, UWU_WORDS, ignore_case=True, match_case=True) - await ctx.send(f">>> {converted}") + """ + Converts a given `text` into it's uwu equivalent. + + Also accepts a valid discord Message ID or link. + """ + conversion_func = functools.partial( + utils.replace_many, replacements=UWU_WORDS, ignore_case=True, match_case=True + ) + text, embed = Fun._get_text_and_embed(ctx, text) + if embed is not None: + embed = await Fun._convert_embed(conversion_func, embed) + converted_text = conversion_func(text) + await ctx.send(content=f">>> {converted_text.lstrip('> ')}", embed=embed) @commands.command(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",)) async def randomcase_command(self, ctx: Context, *, text: str) -> None: """Randomly converts the casing of a given `text`.""" - text = await Fun.get_discord_message(ctx, text) + text = await Fun._get_discord_message(ctx, text) converted = ( char.upper() if round(random.random()) else char.lower() for char in text ) await ctx.send(f">>> {''.join(converted)}") @staticmethod - async def get_discord_message(ctx: Context, text: str) -> str: + async def _get_text_and_embed(ctx: Context, text: str) -> Tuple[str, Union[Embed, None]]: """ - Attempts to convert a given `text` to a discord Message object, then return the contents. + Attempts to extract the text and embed from a possible link to a discord Message. - Useful if the user enters a link or an id to a valid Discord message, because the contents - of the message get returned. + Returns a tuple of: + str: If `text` is a valid discord Message, the contents of the message, else `text`. + Union[Embed, None]: The embed if found in the valid Message, else None + """ + embed = None + message = Fun._get_discord_message(ctx, text) + if isinstance(message, Message): + text = text.content + # Take first embed because we can't send multiple embeds + if message.embeds: + embed = message.embeds[0] + return (text, embed) + @staticmethod + async def _get_discord_message(ctx: Context, text: str) -> Union[Message, str]: + """ + Attempts to convert a given `text` to a discord Message object and return it. + + Conversion will succeed if given a discord Message ID or link. Returns `text` if the conversion fails. """ try: - message = await MessageConverter().convert(ctx, text) + text = await MessageConverter().convert(ctx, text) except commands.BadArgument: log.debug(f"Input '{text:.20}...' is not a valid Discord Message") - else: - text = message.content - finally: - return text + return text + + @staticmethod + async def _convert_embed(func: Callable[[str, ], str], embed: Embed) -> Embed: + """ + Converts the text in an embed using a given conversion function, then return the embed. + + Only modifies the following fields: title, description, footer, fields + """ + embed_dict = embed.to_dict() + + embed_dict["title"] = func(embed_dict.get("title", "")) + embed_dict["description"] = func(embed_dict.get("description", "")) + + if "footer" in embed_dict: + embed_dict["footer"]["text"] = func(embed_dict["footer"].get("text", "")) + + if "fields" in embed_dict: + for field in embed_dict["fields"]: + field["name"] = func(field.get("name", "")) + field["value"] = func(field.get("value", "")) + + return Embed.from_dict(embed_dict) def setup(bot) -> None: -- cgit v1.2.3 From 9a844fa1ae3a8c38280af3a3539c3951cc0a3d45 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Wed, 11 Sep 2019 23:14:35 +0800 Subject: Add embed conversion functionality to .rcase, small fixes --- bot/seasons/evergreen/fun.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 997bab20..7a2a72ba 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -54,20 +54,37 @@ class Fun(Cog): conversion_func = functools.partial( utils.replace_many, replacements=UWU_WORDS, ignore_case=True, match_case=True ) - text, embed = Fun._get_text_and_embed(ctx, text) + text, embed = await Fun._get_text_and_embed(ctx, text) + # Convert embed if it exists if embed is not None: - embed = await Fun._convert_embed(conversion_func, embed) + embed = Fun._convert_embed(conversion_func, embed) converted_text = conversion_func(text) - await ctx.send(content=f">>> {converted_text.lstrip('> ')}", embed=embed) + # Don't put >>> if only embed present + if converted_text: + converted_text = f">>> {converted_text.lstrip('> ')}" + await ctx.send(content=converted_text, embed=embed) @commands.command(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",)) async def randomcase_command(self, ctx: Context, *, text: str) -> None: - """Randomly converts the casing of a given `text`.""" - text = await Fun._get_discord_message(ctx, text) - converted = ( - char.upper() if round(random.random()) else char.lower() for char in text - ) - await ctx.send(f">>> {''.join(converted)}") + """ + Randomly converts the casing of a given `text`. + + Also accepts a valid discord Message ID or link. + """ + def conversion_func(text): + """Randomly converts the casing of a given string""" + return "".join( + char.upper() if round(random.random()) else char.lower() for char in text + ) + text, embed = await Fun._get_text_and_embed(ctx, text) + # Convert embed if it exists + if embed is not None: + embed = Fun._convert_embed(conversion_func, embed) + converted_text = conversion_func(text) + # Don't put >>> if only embed present + if converted_text: + converted_text = f">>> {converted_text.lstrip('> ')}" + await ctx.send(content=converted_text, embed=embed) @staticmethod async def _get_text_and_embed(ctx: Context, text: str) -> Tuple[str, Union[Embed, None]]: @@ -79,9 +96,9 @@ class Fun(Cog): Union[Embed, None]: The embed if found in the valid Message, else None """ embed = None - message = Fun._get_discord_message(ctx, text) + message = await Fun._get_discord_message(ctx, text) if isinstance(message, Message): - text = text.content + text = message.content # Take first embed because we can't send multiple embeds if message.embeds: embed = message.embeds[0] @@ -102,7 +119,7 @@ class Fun(Cog): return text @staticmethod - async def _convert_embed(func: Callable[[str, ], str], embed: Embed) -> Embed: + def _convert_embed(func: Callable[[str, ], str], embed: Embed) -> Embed: """ Converts the text in an embed using a given conversion function, then return the embed. -- cgit v1.2.3 From 70e3720feac090ba833d4456da4c21ff6c3f8cef Mon Sep 17 00:00:00 2001 From: kosayoda Date: Wed, 11 Sep 2019 23:15:30 +0800 Subject: Add more uwu conversions --- bot/seasons/evergreen/fun.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 7a2a72ba..9fb338d3 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -16,6 +16,7 @@ UWU_WORDS = { "fi": "fwi", "l": "w", "r": "w", + "some": "sum", "th": "d", "thing": "fing", "tho": "fo", -- cgit v1.2.3 From 6da59578b515a461c07dbae203f4a799d80f44d6 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Wed, 11 Sep 2019 23:32:08 +0800 Subject: Dumb kosa forgot to relint after making a small change --- bot/seasons/evergreen/fun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 9fb338d3..09e447b7 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -73,7 +73,7 @@ class Fun(Cog): Also accepts a valid discord Message ID or link. """ def conversion_func(text): - """Randomly converts the casing of a given string""" + """Randomly converts the casing of a given string.""" return "".join( char.upper() if round(random.random()) else char.lower() for char in text ) -- cgit v1.2.3 From da20c52802cbb4c68cfbb058e9ffc986b591240f Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Wed, 11 Sep 2019 11:33:16 -0400 Subject: Fix incorrect merge conflict resolutions, lint remaining items --- bot/seasons/easter/avatar_easterifier.py | 2 +- bot/seasons/easter/bunny_name_generator.py | 6 +++--- bot/seasons/easter/easter_riddle.py | 2 +- bot/seasons/easter/egghead_quiz.py | 4 ++-- bot/seasons/easter/traditions.py | 2 +- bot/seasons/evergreen/minesweeper.py | 8 ++++---- bot/seasons/evergreen/showprojects.py | 4 ++-- bot/seasons/evergreen/snakes/utils.py | 26 ++++++++++++++++---------- bot/seasons/evergreen/speedrun.py | 2 +- bot/seasons/halloween/hacktoberstats.py | 2 +- bot/utils/__init__.py | 2 +- tox.ini | 2 +- 12 files changed, 34 insertions(+), 28 deletions(-) (limited to 'bot') diff --git a/bot/seasons/easter/avatar_easterifier.py b/bot/seasons/easter/avatar_easterifier.py index f056068e..85c32909 100644 --- a/bot/seasons/easter/avatar_easterifier.py +++ b/bot/seasons/easter/avatar_easterifier.py @@ -34,7 +34,7 @@ class AvatarEasterifier(commands.Cog): r1, g1, b1 = x def distance(point: Tuple[int, int, int]) -> Tuple[int, int, int]: - """Finds the difference between a pastel colour and the original pixel colour""" + """Finds the difference between a pastel colour and the original pixel colour.""" r2, g2, b2 = point return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 22957b7f..97c467e1 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -47,7 +47,7 @@ class BunnyNameGenerator(commands.Cog): return new_name def append_name(self, displayname: str) -> str: - """Adds a suffix to the end of the Discord name""" + """Adds a suffix to the end of the Discord name.""" extensions = ['foot', 'ear', 'nose', 'tail'] suffix = random.choice(extensions) appended_name = displayname + suffix @@ -56,12 +56,12 @@ class BunnyNameGenerator(commands.Cog): @commands.command() async def bunnyname(self, ctx: commands.Context) -> None: - """Picks a random bunny name from a JSON file""" + """Picks a random bunny name from a JSON file.""" await ctx.send(random.choice(BUNNY_NAMES["names"])) @commands.command() async def bunnifyme(self, ctx: commands.Context) -> None: - """Gets your Discord username and bunnifies it""" + """Gets your Discord username and bunnifies it.""" username = ctx.message.author.display_name # If name contains spaces or other separators, get the individual words to randomly bunnify diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index c3f19055..4b98b204 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -84,7 +84,7 @@ class EasterRiddle(commands.Cog): @commands.Cog.listener() async def on_message(self, message: discord.Messaged) -> None: - """If a non-bot user enters a correct answer, their username gets added to self.winners""" + """If a non-bot user enters a correct answer, their username gets added to self.winners.""" if self.current_channel != message.channel: return diff --git a/bot/seasons/easter/egghead_quiz.py b/bot/seasons/easter/egghead_quiz.py index 0b175bf1..bd179fe2 100644 --- a/bot/seasons/easter/egghead_quiz.py +++ b/bot/seasons/easter/egghead_quiz.py @@ -97,13 +97,13 @@ class EggheadQuiz(commands.Cog): @staticmethod async def already_reacted(message: discord.Message, user: Union[discord.Member, discord.User]) -> bool: - """Returns whether a given user has reacted more than once to a given message""" + """Returns whether a given user has reacted more than once to a given message.""" users = [u.id for reaction in [await r.users().flatten() for r in message.reactions] for u in reaction] return users.count(user.id) > 1 # Old reaction plus new reaction @commands.Cog.listener() async def on_reaction_add(self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]) -> None: - """Listener to listen specifically for reactions of quiz messages""" + """Listener to listen specifically for reactions of quiz messages.""" if user.bot: return if reaction.message.id not in self.quiz_messages: diff --git a/bot/seasons/easter/traditions.py b/bot/seasons/easter/traditions.py index 4fb4694f..9529823f 100644 --- a/bot/seasons/easter/traditions.py +++ b/bot/seasons/easter/traditions.py @@ -19,7 +19,7 @@ class Traditions(commands.Cog): @commands.command(aliases=('eastercustoms',)) async def easter_tradition(self, ctx: commands.Context) -> None: - """Responds with a random tradition or custom""" + """Responds with a random tradition or custom.""" random_country = random.choice(list(traditions)) await ctx.send(f"{random_country}:\n{traditions[random_country]}") diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 015b09df..b0ba8145 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -33,7 +33,7 @@ class CoordinateConverter(commands.Converter): """Converter for Coordinates.""" async def convert(self, ctx: commands.Context, coordinate: str) -> typing.Tuple[int, int]: - """Take in a coordinate string and turn it into x, y""" + """Take in a coordinate string and turn it into an (x, y) tuple.""" if not 2 <= len(coordinate) <= 3: raise commands.BadArgument('Invalid co-ordinate provided') @@ -81,7 +81,7 @@ class Minesweeper(commands.Cog): @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) async def minesweeper_group(self, ctx: commands.Context) -> None: - """Commands for Playing Minesweeper""" + """Commands for Playing Minesweeper.""" await ctx.send_help(ctx.command) @staticmethod @@ -216,7 +216,7 @@ class Minesweeper(commands.Cog): self.reveal_zeros(revealed, board, x_, y_) async def check_if_won(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard) -> bool: - """Checks if a player has won""" + """Checks if a player has won.""" if any( revealed[y][x] in ["hidden", "flag"] and board[y][x] != "bomb" for x in range(10) @@ -268,7 +268,7 @@ class Minesweeper(commands.Cog): @minesweeper_group.command(name="end") async def end_command(self, ctx: commands.Context) -> None: - """End your current game""" + """End your current game.""" game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) diff --git a/bot/seasons/evergreen/showprojects.py b/bot/seasons/evergreen/showprojects.py index d41132aa..a943e548 100644 --- a/bot/seasons/evergreen/showprojects.py +++ b/bot/seasons/evergreen/showprojects.py @@ -17,7 +17,7 @@ class ShowProjects(commands.Cog): @commands.Cog.listener() async def on_message(self, message: Message) -> None: - """Adds reactions to posts in #show-your-projects""" + """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 @@ -29,6 +29,6 @@ class ShowProjects(commands.Cog): def setup(bot: commands.Bot) -> None: - """Show Projects Reaction Cog""" + """Show Projects Reaction Cog.""" bot.add_cog(ShowProjects(bot)) log.info("ShowProjects cog loaded") diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index 76809bd4..7d6caf04 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -388,8 +388,7 @@ class SnakeAndLaddersGame: """ Create a new Snakes and Ladders game. - Listen for reactions until players have joined, - and the game has been started. + Listen for reactions until players have joined, and the game has been started. """ def startup_event_check(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" @@ -457,6 +456,7 @@ class SnakeAndLaddersGame: return # We're done, no reactions for the last 5 minutes async def _add_player(self, user: Member) -> None: + """Add player to game.""" self.players.append(user) self.player_tiles[user.id] = 1 @@ -490,7 +490,7 @@ class SnakeAndLaddersGame: delete_after=10 ) - async def player_leave(self, user: Member) -> None: + async def player_leave(self, user: Member) -> bool: """ Handle players leaving the game. @@ -515,11 +515,13 @@ class SnakeAndLaddersGame: is_surrendered = True self._destruct() - async def cancel_game(self, user: Member) -> None: - """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 + return is_surrendered + else: + await self.channel.send(user.mention + " You are not in the match.", delete_after=10) + return is_surrendered + + async def cancel_game(self) -> None: + """Cancel the running game.""" await self.channel.send("**Snakes and Ladders**: Game has been canceled.") self._destruct() @@ -670,6 +672,7 @@ class SnakeAndLaddersGame: self.round_has_rolled[user.id] = True async def _complete_round(self) -> None: + """At the conclusion of a round check to see if there's been a winner.""" self.state = 'post_round' # check for winner @@ -684,19 +687,22 @@ class SnakeAndLaddersGame: self._destruct() def _check_winner(self) -> Member: + """Return a winning member if we're in the post-round state and there's a winner.""" if self.state != 'post_round': return None return next((player for player in self.players if self.player_tiles[player.id] == 100), None) def _check_all_rolled(self) -> bool: + """Check if all members have made their roll.""" return all(rolled for rolled in self.round_has_rolled.values()) def _destruct(self) -> None: + """Clean up the finished game object.""" del self.snakes.active_sal[self.channel] - def _board_coordinate_from_index(self, index: int) -> Tuple[float, float]: - # converts the tile number to the x/y coordinates for graphical purposes + def _board_coordinate_from_index(self, index: int) -> Tuple[int, int]: + """Convert the tile number to the x/y coordinates for graphical purposes.""" y_level = 9 - math.floor((index - 1) / 10) is_reversed = math.floor((index - 1) / 10) % 2 != 0 x_level = (index - 1) % 10 diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index 2f59c886..76c5e8d3 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -23,6 +23,6 @@ class Speedrun(commands.Cog): def setup(bot: commands.Bot) -> None: - """Load the Speedrun cog""" + """Load the Speedrun cog.""" bot.add_cog(Speedrun(bot)) log.info("Speedrun cog loaded") diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index 5687a5c7..0f513953 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -19,7 +19,7 @@ PRS_FOR_SHIRT = 4 # Minimum number of PRs before a shirt is awarded class HacktoberStats(commands.Cog): """Hacktoberfest statistics Cog.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.link_json = Path("bot/resources/github_links.json") self.linked_accounts = self.load_linked_users() diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 8732eb22..0aa50af6 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -110,7 +110,7 @@ def replace_many( regex = re.compile(pattern, re.I if ignore_case else 0) def _repl(match: re.Match) -> str: - """Returns replacement depending on `ignore_case` and `match_case`""" + """Returns replacement depending on `ignore_case` and `match_case`.""" word = match.group(0) replacement = replacements[word.lower() if ignore_case else word] diff --git a/tox.ini b/tox.ini index dec88854..ee898b0d 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ ignore= # Docstring Quotes D301,D302, # Docstring Content - D400,D401,D402,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414 + D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417 # Type Annotations TYP002,TYP003,TYP101,TYP102,TYP204,TYP206 exclude= -- cgit v1.2.3 From b0a8b98c37187e24f73a1bd4e8f26b3c75fd583e Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 19 Sep 2019 22:47:17 +1000 Subject: Remove egg_hunt --- bot/resources/persist/egg_hunt.sqlite | Bin 16384 -> 0 bytes bot/seasons/easter/egg_hunt/__init__.py | 13 - bot/seasons/easter/egg_hunt/cog.py | 618 ------------------------------- bot/seasons/easter/egg_hunt/constants.py | 40 -- 4 files changed, 671 deletions(-) delete mode 100644 bot/resources/persist/egg_hunt.sqlite delete mode 100644 bot/seasons/easter/egg_hunt/__init__.py delete mode 100644 bot/seasons/easter/egg_hunt/cog.py delete mode 100644 bot/seasons/easter/egg_hunt/constants.py (limited to 'bot') diff --git a/bot/resources/persist/egg_hunt.sqlite b/bot/resources/persist/egg_hunt.sqlite deleted file mode 100644 index 6a7ae32d..00000000 Binary files a/bot/resources/persist/egg_hunt.sqlite and /dev/null differ diff --git a/bot/seasons/easter/egg_hunt/__init__.py b/bot/seasons/easter/egg_hunt/__init__.py deleted file mode 100644 index e7e71ccb..00000000 --- a/bot/seasons/easter/egg_hunt/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging - -from discord.ext import commands - -from .cog import EggHunt - -log = logging.getLogger(__name__) - - -def setup(bot: commands.Bot) -> None: - """Easter Egg Hunt Cog load.""" - bot.add_cog(EggHunt()) - log.info("EggHunt cog loaded") diff --git a/bot/seasons/easter/egg_hunt/cog.py b/bot/seasons/easter/egg_hunt/cog.py deleted file mode 100644 index 8178b4ef..00000000 --- a/bot/seasons/easter/egg_hunt/cog.py +++ /dev/null @@ -1,618 +0,0 @@ -import asyncio -import contextlib -import logging -import random -import sqlite3 -from datetime import datetime, timezone -from pathlib import Path - -import discord -from discord.ext import commands - -from bot.bot import bot -from bot.constants import Channels, Client, Roles as MainRoles -from bot.decorators import with_role -from .constants import Colours, EggHuntSettings, Emoji, Roles - -log = logging.getLogger(__name__) - -DB_PATH = Path("bot/resources/persist/egg_hunt.sqlite") - -TEAM_MAP = { - Roles.white: Emoji.egg_white, - Roles.blurple: Emoji.egg_blurple, - Emoji.egg_white: Roles.white, - Emoji.egg_blurple: Roles.blurple -} - -GUILD = bot.get_guild(Client.guild) - -MUTED = GUILD.get_role(MainRoles.muted) - - -def get_team_role(user: discord.Member) -> discord.Role: - """Helper function to get the team role for a member.""" - if Roles.white in user.roles: - return Roles.white - if Roles.blurple in user.roles: - return Roles.blurple - - -async def assign_team(user: discord.Member) -> discord.Member: - """Helper function to assign a new team role for a member.""" - db = sqlite3.connect(DB_PATH) - c = db.cursor() - c.execute(f"SELECT team FROM user_scores WHERE user_id = {user.id}") - result = c.fetchone() - if not result: - c.execute( - "SELECT team, COUNT(*) AS count FROM user_scores " - "GROUP BY team ORDER BY count ASC LIMIT 1;" - ) - result = c.fetchone() - result = result[0] if result else "WHITE" - - if result[0] == "WHITE": - new_team = Roles.white - else: - new_team = Roles.blurple - - db.close() - - log.debug(f"Assigned role {new_team} to {user}.") - - await user.add_roles(new_team) - return GUILD.get_member(user.id) - - -class EggMessage: - """Handles a single egg reaction drop session.""" - - def __init__(self, message: discord.Message, egg: discord.Emoji): - self.message = message - self.egg = egg - self.first = None - self.users = set() - self.teams = {Roles.white: "WHITE", Roles.blurple: "BLURPLE"} - self.new_team_assignments = {} - self.timeout_task = None - - @staticmethod - def add_user_score_sql(user_id: int, team: str, score: int) -> str: - """Builds the SQL for adding a score to a user in the database.""" - return ( - "INSERT INTO user_scores(user_id, team, score)" - f"VALUES({user_id}, '{team}', {score})" - f"ON CONFLICT (user_id) DO UPDATE SET score=score+{score}" - ) - - @staticmethod - def add_team_score_sql(team_name: str, score: int) -> str: - """Builds the SQL for adding a score to a team in the database.""" - return f"UPDATE team_scores SET team_score=team_score+{score} WHERE team_id='{team_name}'" - - def finalise_score(self) -> None: - """Sums and actions scoring for this egg drop session.""" - db = sqlite3.connect(DB_PATH) - c = db.cursor() - - team_scores = {"WHITE": 0, "BLURPLE": 0} - - first_team = get_team_role(self.first) - if not first_team: - log.debug("User without team role!") - db.close() - return - - score = 3 if first_team == TEAM_MAP[first_team] else 2 - - c.execute(self.add_user_score_sql(self.first.id, self.teams[first_team], score)) - team_scores[self.teams[first_team]] += score - - for user in self.users: - team = get_team_role(user) - if not team: - log.debug("User without team role!") - continue - - team_name = self.teams[team] - team_scores[team_name] += 1 - score = 2 if team == first_team else 1 - c.execute(self.add_user_score_sql(user.id, team_name, score)) - - for team_name, score in team_scores.items(): - if not score: - continue - c.execute(self.add_team_score_sql(team_name, score)) - - db.commit() - db.close() - - log.debug( - f"EggHunt session finalising: ID({self.message.id}) " - f"FIRST({self.first}) REST({self.users})." - ) - - async def start_timeout(self, seconds: int = 5) -> None: - """Begins a task that will sleep until the given seconds before finalizing the session.""" - if self.timeout_task: - self.timeout_task.cancel() - self.timeout_task = None - - await asyncio.sleep(seconds) - - bot.remove_listener(self.collect_reacts, name="on_reaction_add") - - with contextlib.suppress(discord.Forbidden): - await self.message.clear_reactions() - - if self.first: - self.finalise_score() - - def is_valid_react(self, reaction: discord.Reaction, user: discord.Member) -> bool: - """Validates a reaction event was meant for this session.""" - if user.bot: - return False - if reaction.message.id != self.message.id: - return False - if reaction.emoji != self.egg: - return False - - # Ignore the punished - if MUTED in user.roles: - return False - - return True - - async def collect_reacts(self, reaction: discord.Reaction, user: discord.Member) -> None: - """Handles emitted reaction_add events via listener.""" - if not self.is_valid_react(reaction, user): - return - - team = get_team_role(user) - if not team: - log.debug(f"Assigning a team for {user}.") - user = await assign_team(user) - - if not self.first: - log.debug(f"{user} was first to react to egg on {self.message.id}.") - self.first = user - await self.start_timeout() - else: - if user != self.first: - self.users.add(user) - - async def start(self) -> None: - """Starts the egg drop session.""" - log.debug(f"EggHunt session started for message {self.message.id}.") - bot.add_listener(self.collect_reacts, name="on_reaction_add") - with contextlib.suppress(discord.Forbidden): - await self.message.add_reaction(self.egg) - self.timeout_task = asyncio.create_task(self.start_timeout(300)) - while True: - if not self.timeout_task: - break - if not self.timeout_task.done(): - await self.timeout_task - else: - # make sure any exceptions raise if necessary - self.timeout_task.result() - break - - -class SuperEggMessage(EggMessage): - """Handles a super egg session.""" - - def __init__(self, message: discord.Message, egg: discord.Emoji, window: int): - super().__init__(message, egg) - self.window = window - - async def finalise_score(self) -> None: - """Sums and actions scoring for this super egg session.""" - try: - message = await self.message.channel.fetch_message(self.message.id) - except discord.NotFound: - return - - count = 0 - white = 0 - blurple = 0 - react_users = [] - for reaction in message.reactions: - if reaction.emoji == self.egg: - react_users = await reaction.users().flatten() - for user in react_users: - team = get_team_role(user) - if team == Roles.white: - white += 1 - elif team == Roles.blurple: - blurple += 1 - count = reaction.count - 1 - break - - score = 50 if self.egg == Emoji.egg_gold else 100 - if white == blurple: - log.debug("Tied SuperEgg Result.") - team = None - score /= 2 - elif white > blurple: - team = Roles.white - else: - team = Roles.blurple - - embed = self.message.embeds[0] - - db = sqlite3.connect(DB_PATH) - c = db.cursor() - - user_bonus = 5 if self.egg == Emoji.egg_gold else 10 - for user in react_users: - if user.bot: - continue - role = get_team_role(user) - if not role: - print("issue") - user_score = 1 if user != self.first else user_bonus - c.execute(self.add_user_score_sql(user.id, self.teams[role], user_score)) - - if not team: - embed.description = f"{embed.description}\n\nA Tie!\nBoth got {score} points!" - c.execute(self.add_team_score_sql(self.teams[Roles.white], score)) - c.execute(self.add_team_score_sql(self.teams[Roles.blurple], score)) - team_name = "TIE" - else: - team_name = self.teams[team] - embed.description = ( - f"{embed.description}\n\nTeam {team_name.capitalize()} won the points!" - ) - c.execute(self.add_team_score_sql(team_name, score)) - - c.execute( - "INSERT INTO super_eggs (message_id, egg_type, team, window) " - f"VALUES ({self.message.id}, '{self.egg.name}', '{team_name}', {self.window});" - ) - - log.debug("Committing Super Egg scores.") - db.commit() - db.close() - - embed.set_footer(text=f"Finished with {count} total reacts.") - with contextlib.suppress(discord.HTTPException): - await self.message.edit(embed=embed) - - async def start_timeout(self, seconds: int = None) -> None: - """Starts the super egg session.""" - if not seconds: - return - count = 4 - for _ in range(count): - await asyncio.sleep(60) - embed = self.message.embeds[0] - embed.set_footer(text=f"Finishing in {count} minutes.") - try: - await self.message.edit(embed=embed) - except discord.HTTPException: - break - count -= 1 - bot.remove_listener(self.collect_reacts, name="on_reaction_add") - await self.finalise_score() - - -class EggHunt(commands.Cog): - """Easter Egg Hunt Event.""" - - def __init__(self): - self.event_channel = GUILD.get_channel(Channels.seasonalbot_chat) - self.super_egg_buffer = 60*60 - self.tables = { - "super_eggs": ( - "CREATE TABLE super_eggs (" - "message_id INTEGER NOT NULL " - " CONSTRAINT super_eggs_pk PRIMARY KEY, " - "egg_type TEXT NOT NULL, " - "team TEXT NOT NULL, " - "window INTEGER);" - ), - "team_scores": ( - "CREATE TABLE team_scores (" - "team_id TEXT, " - "team_score INTEGER DEFAULT 0);" - ), - "user_scores": ( - "CREATE TABLE user_scores(" - "user_id INTEGER NOT NULL " - " CONSTRAINT user_scores_pk PRIMARY KEY, " - "team TEXT NOT NULL, " - "score INTEGER DEFAULT 0 NOT NULL);" - ), - "react_logs": ( - "CREATE TABLE react_logs(" - "member_id INTEGER NOT NULL, " - "message_id INTEGER NOT NULL, " - "reaction_id TEXT NOT NULL, " - "react_timestamp REAL NOT NULL);" - ) - } - self.prepare_db() - self.task = asyncio.create_task(self.super_egg()) - self.task.add_done_callback(self.task_cleanup) - - def prepare_db(self) -> None: - """Ensures database tables all exist and if not, creates them.""" - db = sqlite3.connect(DB_PATH) - c = db.cursor() - - exists_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';" - - missing_tables = [] - for table in self.tables: - c.execute(exists_sql.format(table_name=table)) - result = c.fetchone() - if not result: - missing_tables.append(table) - - for table in missing_tables: - log.info(f"Table {table} is missing, building new one.") - c.execute(self.tables[table]) - - db.commit() - db.close() - - def task_cleanup(self, task: asyncio.Task) -> None: - """Returns task result and restarts. Used as a done callback to show raised exceptions.""" - task.result() - self.task = asyncio.create_task(self.super_egg()) - - @staticmethod - def current_timestamp() -> float: - """Returns a timestamp of the current UTC time.""" - return datetime.utcnow().replace(tzinfo=timezone.utc).timestamp() - - async def super_egg(self) -> None: - """Manages the timing of super egg drops.""" - while True: - now = int(self.current_timestamp()) - - if now > EggHuntSettings.end_time: - log.debug("Hunt ended. Ending task.") - break - - if now < EggHuntSettings.start_time: - remaining = EggHuntSettings.start_time - now - log.debug(f"Hunt not started yet. Sleeping for {remaining}.") - await asyncio.sleep(remaining) - - log.debug(f"Hunt started.") - - db = sqlite3.connect(DB_PATH) - c = db.cursor() - - current_window = None - next_window = None - windows = EggHuntSettings.windows.copy() - windows.insert(0, EggHuntSettings.start_time) - for i, window in enumerate(windows): - c.execute(f"SELECT COUNT(*) FROM super_eggs WHERE window={window}") - already_dropped = c.fetchone()[0] - - if already_dropped: - log.debug(f"Window {window} already dropped, checking next one.") - continue - - if now < window: - log.debug("Drop windows up to date, sleeping until next one.") - await asyncio.sleep(window-now) - now = int(self.current_timestamp()) - - current_window = window - next_window = windows[i+1] - break - - count = c.fetchone() - db.close() - - if not current_window: - log.debug("No drop windows left, ending task.") - break - - log.debug(f"Current Window: {current_window}. Next Window {next_window}") - - if not count: - if next_window < now: - log.debug("An Egg Drop Window was missed, dropping one now.") - next_drop = 0 - else: - next_drop = random.randrange(now, next_window) - - if next_drop: - log.debug(f"Sleeping until next super egg drop: {next_drop}.") - await asyncio.sleep(next_drop) - - if random.randrange(10) <= 2: - egg = Emoji.egg_diamond - egg_type = "Diamond" - score = "100" - colour = Colours.diamond - else: - egg = Emoji.egg_gold - egg_type = "Gold" - score = "50" - colour = Colours.gold - - embed = discord.Embed( - title=f"A {egg_type} Egg Has Appeared!", - description=f"**Worth {score} team points!**\n\n" - "The team with the most reactions after 5 minutes wins!", - colour=colour - ) - embed.set_thumbnail(url=egg.url) - embed.set_footer(text="Finishing in 5 minutes.") - msg = await self.event_channel.send(embed=embed) - await SuperEggMessage(msg, egg, current_window).start() - - log.debug("Sleeping until next window.") - next_loop = max(next_window - int(self.current_timestamp()), self.super_egg_buffer) - await asyncio.sleep(next_loop) - - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None: - """Reaction event listener for reaction logging for later anti-cheat analysis.""" - if payload.channel_id not in EggHuntSettings.allowed_channels: - return - - now = self.current_timestamp() - db = sqlite3.connect(DB_PATH) - c = db.cursor() - c.execute( - "INSERT INTO react_logs(member_id, message_id, reaction_id, react_timestamp) " - f"VALUES({payload.user_id}, {payload.message_id}, '{payload.emoji}', {now})" - ) - db.commit() - db.close() - - @commands.Cog.listener() - async def on_message(self, message: discord.Message) -> None: - """Message event listener for random egg drops.""" - if self.current_timestamp() < EggHuntSettings.start_time: - return - - if message.channel.id not in EggHuntSettings.allowed_channels: - log.debug("Message not in Egg Hunt channel; ignored.") - return - - if message.author.bot: - return - - if random.randrange(100) <= 5: - await EggMessage(message, random.choice([Emoji.egg_white, Emoji.egg_blurple])).start() - - @commands.group(invoke_without_command=True) - async def hunt(self, ctx: commands.Context) -> None: - """ - For 48 hours, hunt down as many eggs randomly appearing as possible. - - Standard Eggs - -------------- - Egg React: +1pt - Team Bonus for Claimed Egg: +1pt - First React on Other Team Egg: +1pt - First React on Your Team Egg: +2pt - - If you get first react, you will claim that egg for your team, allowing - your team to get the Team Bonus point, but be quick, as the egg will - disappear after 5 seconds of the first react. - - Super Eggs - ----------- - Gold Egg: 50 team pts, 5pts to first react - Diamond Egg: 100 team pts, 10pts to first react - - Super Eggs only appear in #seasonalbot-chat so be sure to keep an eye - out. They stay around for 5 minutes and the team with the most reacts - wins the points. - """ - await ctx.invoke(bot.get_command("help"), command="hunt") - - @hunt.command() - async def countdown(self, ctx: commands.Context) -> None: - """Show the time status of the Egg Hunt event.""" - now = self.current_timestamp() - if now > EggHuntSettings.end_time: - return await ctx.send("The Hunt has ended.") - - difference = EggHuntSettings.start_time - now - if difference < 0: - difference = EggHuntSettings.end_time - now - msg = "The Egg Hunt will end in" - else: - msg = "The Egg Hunt will start in" - - hours, r = divmod(difference, 3600) - minutes, r = divmod(r, 60) - await ctx.send(f"{msg} {hours:.0f}hrs, {minutes:.0f}mins & {r:.0f}secs") - - @hunt.command() - async def leaderboard(self, ctx: commands.Context) -> None: - """Show the Egg Hunt Leaderboards.""" - db = sqlite3.connect(DB_PATH) - c = db.cursor() - c.execute(f"SELECT *, RANK() OVER(ORDER BY score DESC) AS rank FROM user_scores LIMIT 10") - user_result = c.fetchall() - c.execute(f"SELECT * FROM team_scores ORDER BY team_score DESC") - team_result = c.fetchall() - db.close() - output = [] - if user_result: - # Get the alignment needed for the score - score_lengths = [] - for result in user_result: - length = len(str(result[2])) - score_lengths.append(length) - - score_length = max(score_lengths) - for user_id, team, score, rank in user_result: - user = GUILD.get_member(user_id) or user_id - team = team.capitalize() - score = f"{score}pts" - output.append(f"{rank:>2}. {score:>{score_length+3}} - {user} ({team})") - user_board = "\n".join(output) - else: - user_board = "No entries." - if team_result: - output = [] - for team, score in team_result: - output.append(f"{team:<7}: {score}") - team_board = "\n".join(output) - else: - team_board = "No entries." - embed = discord.Embed( - title="Egg Hunt Leaderboards", - description=f"**Team Scores**\n```\n{team_board}\n```\n" - f"**Top 10 Members**\n```\n{user_board}\n```" - ) - await ctx.send(embed=embed) - - @hunt.command() - async def rank(self, ctx: commands.Context, *, member: discord.Member = None) -> None: - """Get your ranking in the Egg Hunt Leaderboard.""" - member = member or ctx.author - db = sqlite3.connect(DB_PATH) - c = db.cursor() - c.execute( - "SELECT rank FROM " - "(SELECT RANK() OVER(ORDER BY score DESC) AS rank, user_id FROM user_scores)" - f"WHERE user_id = {member.id};" - ) - result = c.fetchone() - db.close() - if not result: - embed = discord.Embed().set_author(name=f"Egg Hunt - No Ranking") - else: - embed = discord.Embed().set_author(name=f"Egg Hunt - Rank #{result[0]}") - await ctx.send(embed=embed) - - @with_role(MainRoles.admin) - @hunt.command() - async def clear_db(self, ctx: commands.Context) -> None: - """Resets the database to it's initial state.""" - def check(msg): - if msg.author != ctx.author: - return False - if msg.channel != ctx.channel: - return False - return True - await ctx.send( - "WARNING: This will delete all current event data.\n" - "Please verify this action by replying with 'Yes, I want to delete all data.'" - ) - reply_msg = await bot.wait_for('message', check=check) - if reply_msg.content != "Yes, I want to delete all data.": - return await ctx.send("Reply did not match. Aborting database deletion.") - db = sqlite3.connect(DB_PATH) - c = db.cursor() - c.execute("DELETE FROM super_eggs;") - c.execute("DELETE FROM user_scores;") - c.execute("UPDATE team_scores SET team_score=0") - db.commit() - db.close() - await ctx.send("Database successfully cleared.") diff --git a/bot/seasons/easter/egg_hunt/constants.py b/bot/seasons/easter/egg_hunt/constants.py deleted file mode 100644 index 02f6e9f2..00000000 --- a/bot/seasons/easter/egg_hunt/constants.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -from discord import Colour - -from bot.bot import bot -from bot.constants import Channels, Client - - -GUILD = bot.get_guild(Client.guild) - - -class EggHuntSettings: - start_time = int(os.environ["HUNT_START"]) - end_time = start_time + 172800 # 48 hrs later - windows = [int(w) for w in os.environ.get("HUNT_WINDOWS").split(',')] or [] - allowed_channels = [ - Channels.seasonalbot_chat, - Channels.off_topic_0, - Channels.off_topic_1, - Channels.off_topic_2, - ] - - -class Roles: - white = GUILD.get_role(569304397054607363) - blurple = GUILD.get_role(569304472820514816) - - -class Emoji: - egg_white = bot.get_emoji(569266762428841989) - egg_blurple = bot.get_emoji(569266666094067819) - egg_gold = bot.get_emoji(569266900106739712) - egg_diamond = bot.get_emoji(569266839738384384) - - -class Colours: - white = Colour(0xFFFFFF) - blurple = Colour(0x7289DA) - gold = Colour(0xE4E415) - diamond = Colour(0xECF5FF) -- cgit v1.2.3 From c4ccb671c8a0f365f0f5baa8dfd56a5af1036180 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 19 Sep 2019 22:48:22 +1000 Subject: Prevent empty JSON raising an exception. --- bot/resources/halloween/github_links.json | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/resources/halloween/github_links.json b/bot/resources/halloween/github_links.json index e69de29b..0967ef42 100644 --- a/bot/resources/halloween/github_links.json +++ b/bot/resources/halloween/github_links.json @@ -0,0 +1 @@ +{} -- cgit v1.2.3 From f91d49d987065df43cbaa8264d349885f001aa17 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 19 Sep 2019 22:59:46 +1000 Subject: Add persistent datafile utils. --- bot/seasons/halloween/hacktoberstats.py | 4 +++- bot/utils/persist.py | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 bot/utils/persist.py (limited to 'bot') diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index 0f513953..9dfb20bd 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -10,6 +10,8 @@ import aiohttp import discord from discord.ext import commands +from bot.utils.persist import datafile + log = logging.getLogger(__name__) CURRENT_YEAR = datetime.now().year # Used to construct GH API query @@ -21,7 +23,7 @@ class HacktoberStats(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - self.link_json = Path("bot/resources/github_links.json") + self.link_json = datafile(Path("bot", "resources", "halloween", "github_links.json")) self.linked_accounts = self.load_linked_users() @commands.group(name="hacktoberstats", aliases=("hackstats",), invoke_without_command=True) diff --git a/bot/utils/persist.py b/bot/utils/persist.py new file mode 100644 index 00000000..ec6f306a --- /dev/null +++ b/bot/utils/persist.py @@ -0,0 +1,24 @@ +import sqlite3 +from pathlib import Path +from shutil import copyfile + +DIRECTORY = Path("data") # directory that has a persistent volume mapped to it + + +def datafile(file_path: Path) -> Path: + """Copy datafile at the provided file_path to the persistent data directory.""" + if not file_path.exists(): + raise OSError(f"File not found at {file_path}.") + + persistant_path = Path(DIRECTORY, file_path.name) + + if not persistant_path.exists(): + copyfile(file_path, persistant_path) + + return persistant_path + + +def sqlite(db_path: Path) -> sqlite3.Connection: + """Copy sqlite file to the persistent data directory and return an open connection.""" + persistant_path = datafile(db_path) + return sqlite3.connect(persistant_path) -- cgit v1.2.3 From 4d7c93296d67f182b849d2a5227d692640452085 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 20 Sep 2019 11:10:30 +1000 Subject: Add better explanatory docstring and example for persist.datafile. --- bot/utils/persist.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/utils/persist.py b/bot/utils/persist.py index ec6f306a..06c3764a 100644 --- a/bot/utils/persist.py +++ b/bot/utils/persist.py @@ -6,7 +6,23 @@ DIRECTORY = Path("data") # directory that has a persistent volume mapped to it def datafile(file_path: Path) -> Path: - """Copy datafile at the provided file_path to the persistent data directory.""" + """ + Copy datafile at the provided file_path to the persistent data directory. + + A persistent data file is needed by some features in order to not lose data + after bot rebuilds. + + This function will ensure that a clean data file with default schema, + structure or data is copied over to the persistent volume before returning + the path to this new persistent version of the file. + + If the persistent file already exists, it won't be overwritten with the + clean default file, just returning the Path instead to the existing file. + + Example Usage: + >>> clean_default_datafile = Path("bot", "resources", "datafile.json") + >>> persistent_file_path = datafile(clean_default_datafile) + """ if not file_path.exists(): raise OSError(f"File not found at {file_path}.") -- cgit v1.2.3 From b385db0f08fb8bb3d46cf8f820d5dd525d7b2272 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 12:01:57 +1000 Subject: Check explicitly if file exists rather than any existing path. Co-Authored-By: Mark --- bot/utils/persist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/utils/persist.py b/bot/utils/persist.py index 06c3764a..35e1e41a 100644 --- a/bot/utils/persist.py +++ b/bot/utils/persist.py @@ -23,7 +23,7 @@ def datafile(file_path: Path) -> Path: >>> clean_default_datafile = Path("bot", "resources", "datafile.json") >>> persistent_file_path = datafile(clean_default_datafile) """ - if not file_path.exists(): + if not file_path.is_file(): raise OSError(f"File not found at {file_path}.") persistant_path = Path(DIRECTORY, file_path.name) -- cgit v1.2.3 From c1b8b23e4a327631f287b1dc68ab76967651e57e Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 16:12:18 +1000 Subject: Improve func name, example, directory management Function name has been changed to `make_persistent` after prompt by @lemonsaurus asking for a more descriptive name. Thanks @MarkKoz for providing the alternate name. During local testing, the `data` directory doesn't exist yet. In prod, this isn't an issue as the persistent volume is mounted at that location. To make local testing more convenient, the directory is checked and made if not found. Persistent data files will be placed in a seasonal subdirectory so long as they have a valid season name somewhere in their path, otherwise they will be placed directly in the data directory. Added a note to docstring to avoid same-named files in the same seasons or it will conflict with each other in the persistent data directory. The example was extended a little bit to make it both actually valid if tested and hopefully make it easier to understand what's going on. --- bot/utils/persist.py | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/utils/persist.py b/bot/utils/persist.py index 35e1e41a..939a95c9 100644 --- a/bot/utils/persist.py +++ b/bot/utils/persist.py @@ -2,10 +2,12 @@ import sqlite3 from pathlib import Path from shutil import copyfile +from bot.seasons.season import get_seasons + DIRECTORY = Path("data") # directory that has a persistent volume mapped to it -def datafile(file_path: Path) -> Path: +def make_persistent(file_path: Path) -> Path: """ Copy datafile at the provided file_path to the persistent data directory. @@ -19,22 +21,48 @@ def datafile(file_path: Path) -> Path: If the persistent file already exists, it won't be overwritten with the clean default file, just returning the Path instead to the existing file. + Note: Avoid using the same file name as other features in the same seasons + as otherwise only one datafile can be persistent and will be returned for + both cases. + Example Usage: - >>> clean_default_datafile = Path("bot", "resources", "datafile.json") - >>> persistent_file_path = datafile(clean_default_datafile) + >>> import json + >>> template_datafile = Path("bot", "resources", "evergreen", "myfile.json") + >>> path_to_persistent_file = make_persistent(template_datafile) + >>> print(path_to_persistent_file) + data/evergreen/myfile.json + >>> with path_to_persistent_file.open("w+") as f: + >>> data = json.load(f) """ + # ensure the persistent data directory exists + if not DIRECTORY.exists(): + DIRECTORY.mkdir() + if not file_path.is_file(): raise OSError(f"File not found at {file_path}.") - persistant_path = Path(DIRECTORY, file_path.name) + # detect season in datafile path for assigning to subdirectory + season = next((s for s in get_seasons() if s in file_path.parts), None) + + if season: + # make sure subdirectory exists first + subdirectory = Path(DIRECTORY, season) + if not subdirectory.exists(): + subdirectory.mkdir() + + persistent_path = Path(subdirectory, file_path.name) + + else: + persistent_path = Path(DIRECTORY, file_path.name) - if not persistant_path.exists(): - copyfile(file_path, persistant_path) + # copy base/template datafile to persistent directory + if not persistent_path.exists(): + copyfile(file_path, persistent_path) - return persistant_path + return persistent_path def sqlite(db_path: Path) -> sqlite3.Connection: """Copy sqlite file to the persistent data directory and return an open connection.""" - persistant_path = datafile(db_path) - return sqlite3.connect(persistant_path) + persistent_path = make_persistent(db_path) + return sqlite3.connect(persistent_path) -- cgit v1.2.3 From 3ea2130d71b69bd733675c43f91d7d081472735c Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 17:34:30 +1000 Subject: Use mkdir exists kwarg instead of checking existing ahead of time. --- bot/utils/persist.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/utils/persist.py b/bot/utils/persist.py index 939a95c9..a60a1219 100644 --- a/bot/utils/persist.py +++ b/bot/utils/persist.py @@ -35,8 +35,7 @@ def make_persistent(file_path: Path) -> Path: >>> data = json.load(f) """ # ensure the persistent data directory exists - if not DIRECTORY.exists(): - DIRECTORY.mkdir() + DIRECTORY.mkdir(exist_ok=True) if not file_path.is_file(): raise OSError(f"File not found at {file_path}.") @@ -47,8 +46,7 @@ def make_persistent(file_path: Path) -> Path: if season: # make sure subdirectory exists first subdirectory = Path(DIRECTORY, season) - if not subdirectory.exists(): - subdirectory.mkdir() + subdirectory.mkdir(exist_ok=True) persistent_path = Path(subdirectory, file_path.name) -- cgit v1.2.3 From d8434029a5305d467031bf0edf7e1d747045b4ae Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 18:18:21 +1000 Subject: Reflect persist module changes in hacktober cog. Co-Authored-By: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> --- bot/seasons/halloween/hacktoberstats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index 9dfb20bd..20797037 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -10,7 +10,7 @@ import aiohttp import discord from discord.ext import commands -from bot.utils.persist import datafile +from bot.utils.persist import make_persistent log = logging.getLogger(__name__) @@ -23,7 +23,7 @@ class HacktoberStats(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - self.link_json = datafile(Path("bot", "resources", "halloween", "github_links.json")) + self.link_json = make_persistent(Path("bot", "resources", "halloween", "github_links.json")) self.linked_accounts = self.load_linked_users() @commands.group(name="hacktoberstats", aliases=("hackstats",), invoke_without_command=True) -- cgit v1.2.3 From 765ecab67994b533e1779e59dedfb82d6659cb40 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Thu, 26 Sep 2019 22:16:09 -0400 Subject: Update flake8-annotation pin & relint --- Pipfile | 2 +- Pipfile.lock | 47 ++++++++++++++++++++++---------- bot/seasons/easter/avatar_easterifier.py | 2 +- bot/seasons/evergreen/fun.py | 2 +- bot/seasons/season.py | 2 +- bot/seasons/valentines/lovecalculator.py | 2 +- 6 files changed, 38 insertions(+), 19 deletions(-) (limited to 'bot') diff --git a/Pipfile b/Pipfile index f02f709f..64c47b2a 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ pytz = "~=2019.2" [dev-packages] flake8 = "~=3.7" -flake8-annotations = "~=1.0" +flake8-annotations = "~=1.1" flake8-bugbear = "~=19.8" flake8-docstrings = "~=1.4" flake8-import-order = "~=0.18" diff --git a/Pipfile.lock b/Pipfile.lock index abf39d9f..19ca0618 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8861de068d14f2c48bebdc2691ab62753c502ffb20735aa7b037b154f9f84a9c" + "sha256": "da19ab2567a55706054eae245eb95a2b6f861836a47ef40641b0c6976b509c65" }, "pipfile-spec": 6, "requires": { @@ -53,11 +53,11 @@ }, "arrow": { "hashes": [ - "sha256:704f5403299fe092c69479e0a2516a434003e82d37439a9e47c31285faf3947b", - "sha256:9b92a8e151e168b742a36b622deadf860d1686af8c5bbe46eca8da04b10fe92f" + "sha256:10257c5daba1a88db34afa284823382f4963feca7733b9107956bed041aff24f", + "sha256:c2325911fcd79972cf493cfd957072f9644af8ad25456201ae1ede3316576eb4" ], "index": "pypi", - "version": "==0.15.0" + "version": "==0.15.2" }, "async-timeout": { "hashes": [ @@ -230,7 +230,6 @@ }, "pycparser": { "hashes": [ - "sha256:83870b0dff6e9c1b1f721ee062f2e198c1ce7f107220020bf4de788dca009944", "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], "version": "==2.19" @@ -259,10 +258,10 @@ }, "soupsieve": { "hashes": [ - "sha256:8662843366b8d8779dec4e2f921bebec9afd856a5ff2e82cd419acc5054a1a92", - "sha256:a5a6166b4767725fd52ae55fee8c8b6137d9a51e9f1edea461a062a759160118" + "sha256:605f89ad5fdbfefe30cdc293303665eff2d188865d4dbe4eb510bba1edfbfce3", + "sha256:b91d676b330a0ebd5b21719cb6e9b57c57d433671f65b9c28dd3461d9a1ed0b6" ], - "version": "==1.9.3" + "version": "==1.9.4" }, "websockets": { "hashes": [ @@ -346,11 +345,11 @@ }, "flake8-annotations": { "hashes": [ - "sha256:1309f2bc9853a2d77d578b089d331b0b832b40c97932641e136e1b49d3650c82", - "sha256:3ecdd27054c3eed6484139025698465e3c9f4e68dbd5043d0204fcb2550ee27b" + "sha256:6ac7ca1e706307686b60af8043ff1db31dc2cfc1233c8210d67a3d9b8f364736", + "sha256:b51131007000d67217608fa028a35ff80aa400b474e5972f1f99c2cf9d26bd2e" ], "index": "pypi", - "version": "==1.0.0" + "version": "==1.1.0" }, "flake8-bugbear": { "hashes": [ @@ -408,10 +407,10 @@ }, "importlib-metadata": { "hashes": [ - "sha256:9ff1b1c5a354142de080b8a4e9803e5d0d59283c93aed808617c787d16768375", - "sha256:b7143592e374e50584564794fcb8aaf00a23025f9db866627f89a21491847a8d" + "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", + "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" ], - "version": "==0.20" + "version": "==0.23" }, "mccabe": { "hashes": [ @@ -500,6 +499,26 @@ ], "version": "==0.10.0" }, + "typed-ast": { + "hashes": [ + "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", + "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", + "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", + "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", + "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", + "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", + "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", + "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", + "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", + "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", + "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", + "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", + "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", + "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", + "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" + ], + "version": "==1.4.0" + }, "virtualenv": { "hashes": [ "sha256:680af46846662bb38c5504b78bad9ed9e4f3ba2d54f54ba42494fdf94337fe30", diff --git a/bot/seasons/easter/avatar_easterifier.py b/bot/seasons/easter/avatar_easterifier.py index 85c32909..e21e35fc 100644 --- a/bot/seasons/easter/avatar_easterifier.py +++ b/bot/seasons/easter/avatar_easterifier.py @@ -56,7 +56,7 @@ class AvatarEasterifier(commands.Cog): Colours are split by spaces, unless you wrap the colour name in double quotes. Discord colour names, HTML colour names, XKCD colour names and hex values are accepted. """ - async def send(*args, **kwargs): + async def send(*args, **kwargs) -> str: """ This replaces the original ctx.send. diff --git a/bot/seasons/evergreen/fun.py b/bot/seasons/evergreen/fun.py index 4a96743f..889ae079 100644 --- a/bot/seasons/evergreen/fun.py +++ b/bot/seasons/evergreen/fun.py @@ -72,7 +72,7 @@ class Fun(Cog): Also accepts a valid discord Message ID or link. """ - def conversion_func(text): + def conversion_func(text: str) -> str: """Randomly converts the casing of a given string.""" return "".join( char.upper() if round(random.random()) else char.lower() for char in text diff --git a/bot/seasons/season.py b/bot/seasons/season.py index 4e2141c7..8d8179f6 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -303,7 +303,7 @@ class SeasonBase: cogs.append(cog_name) if cogs: - def cog_name(cog): + def cog_name(cog: commands.Cog) -> str: return type(cog).__name__ cog_info = [] diff --git a/bot/seasons/valentines/lovecalculator.py b/bot/seasons/valentines/lovecalculator.py index 207ef557..03d3d7d5 100644 --- a/bot/seasons/valentines/lovecalculator.py +++ b/bot/seasons/valentines/lovecalculator.py @@ -53,7 +53,7 @@ class LoveCalculator(Cog): staff = ctx.guild.get_role(Roles.helpers).members whom = random.choice(staff) - def normalize(arg): + def normalize(arg: Union[Member, str]) -> str: if isinstance(arg, Member): # If we are given a member, return name#discrim without any extra changes arg = str(arg) -- cgit v1.2.3