diff options
17 files changed, 526 insertions, 35 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4161715e..e2e48bd3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,10 +4,12 @@ Seasonalbot is a community project for the Python Discord community over at http Our projects are open-source and are automatically deployed whenever commits are pushed to the `master` branch on each repository, so we've created a set of guidelines in order to keep everything clean and in working order. +Note that contributions may be rejected on the basis of a contributor failing to follow these guidelines. + ## Rules 1. You must be a member of [our Discord community](https://discord.gg/python) in order to contribute to this project. -2. Your pull request must solve an issue created by or approved a staff member or event handler. These will be labeled with the `approved` label. Feel free to suggest issues of your own, which staff can choose to approve. +2. Your pull request must solve an issue created or approved by a staff member. These will be labeled with the `approved` label. Feel free to suggest issues of your own, which staff can review for approval. 3. **No force-pushes** or modifying the Git history in any way. 4. If you have direct access to the repository, **create a branch for your changes** and create a pull request for that branch. If not, create a branch on a fork of the repository and create a pull request from there. * It's common practice for a repository to reject direct pushes to `master`, so make branching a habit! @@ -21,10 +23,13 @@ Our projects are open-source and are automatically deployed whenever commits are 7. **Avoid frequent pushes to the main repository**. This goes for PRs opened against your fork as well. Our test build pipelines are triggered every time a push to the repository (or PR) is made. Try to batch your commits until you've finished working for that session, or you've reached a point where collaborators need your commits to continue their own work. This also provides you the opportunity to amend commits for minor changes rather than having to commit them on their own because you've already pushed. * This includes merging master into your branch. Try to leave merging from master for after your PR passes review; a maintainer will bring your PR up to date before merging. Exceptions to this include: resolving merge conflicts, needing something that was pushed to master for your branch, or something was pushed to master that could potentionally affect the functionality of what you're writing. 8. **Don't fight the framework**. Every framework has its flaws, but the frameworks we've picked out have been carefully chosen for their particular merits. If you can avoid it, please resist reimplementing swathes of framework logic - the work has already been done for you! -9. If someone is working on a pull request, **do not open your own pull request for the same task**. Instead, collaborate with the author(s) of the existing pull request. Communication is key, and there's no point in two separate implementations of the same thing. +9. If someone is working on an issue or pull request, **do not open your own pull request for the same task**. Instead, collaborate with the author(s) of the existing pull request. Duplicate PRs opened without communicating with the other author(s) and/or PyDis staff will be closed. Communication is key, and there's no point in two separate implementations of the same thing. * One option is to fork the other contributor's repository and submit your changes to their branch with your own pull request. We suggest following these guidelines when interacting with their repository as well. -10. **Work as a team** and collaborate whereever possible. Keep things friendly and help each other out - these are shared projects and nobody likes to have their feet trodden on. + * The author(s) of inactive PRs and claimed issues will be be pinged after a week of inactivity for an update. Continued inactivity may result in the issue being released back to the community and/or PR closure. +10. **Work as a team** and collaborate wherever possible. Keep things friendly and help each other out - these are shared projects and nobody likes to have their feet trodden on. 11. **Internal projects are internal**. As a contributor, you have access to information that the rest of the server does not. With this trust comes responsibility - do not release any information you have learned as a result of your contributor position. We are very strict about announcing things at specific times, and many staff members will not appreciate a disruption of the announcement schedule. +12. All static content, such as images or audio, **must be licensed for open public use**. + * Static content must be hosted by a service designed to do so. Failing to do so is known as "leeching" and is frowned upon, as it generates extra bandwidth costs to the host without providing benefit. It would be best if appropriately licensed content is added to the repository itself so it can be served by PyDis' infrastructure. Above all, the needs of our community should come before the wants of an individual. Work together, build solutions to problems and try to do so in a way that people can learn from easily. Abuse of our trust may result in the loss of your Contributor role, especially in relation to Rule 7. @@ -34,7 +39,56 @@ All projects evolve over time, and this contribution guide is no different. This ## Supplemental Information ### Developer Environment -Seasonalbot utilizes [Pipenv](https://pipenv.readthedocs.io/en/latest/) for installation and dependency management. For users unfamiliar with the Pipenv workflow, Pipenv's documentation provides a [Basic Usage](https://pipenv.readthedocs.io/en/latest/basics/) tutorial, along with some of the more advanced workflows. +Seasonalbot utilizes [Pipenv](https://pipenv.readthedocs.io/en/latest/) for installation and dependency management. For users unfamiliar with the Pipenv workflow, Pipenv's documentation provides a [Basic Usage](https://pipenv.readthedocs.io/en/latest/basics/) tutorial, along with some of the more advanced workflows. A project-specific installation guide can be found in [Seasonalbot's README](https://github.com/python-discord/seasonalbot/blob/master/README.md). + +When pulling down changes from GitHub, remember to sync your environment using `pipenv sync --dev` to ensure you're using the most up-to-date versions the project's dependencies. + +### Type Hinting +[PEP 484](https://www.python.org/dev/peps/pep-0484/) formally specifies type hints for Python functions, added to the Python Standard Library in version 3.5. Type hints are recognized by most modern code editing tools and provide useful insight into both the input and output types of a function, preventing the user from having to go through the codebase to determine these types. + +For example: + +```py +def foo(input_1: int, input_2: dict) -> bool: +``` + +Tells us that `foo` accepts an `int` and a `dict` and returns a `bool`. + +All function declarations should be type hinted in code contributed to the PyDis organization. + +For more information, see *[PEP 483](https://www.python.org/dev/peps/pep-0483/) - The Theory of Type Hints* and Python's documentation for the [`typing`](https://docs.python.org/3/library/typing.html) module. + +### AutoDoc Formatting Directives +Many documentation packages provide support for automatic documentation generation from the codebase's docstrings. These tools utilize special formatting directives to enable richer formatting in the generated documentation. + +For example: + +```py +def foo(bar: int, baz: dict=None) -> bool: + """ + Does some things with some stuff. + + :param bar: Some input + :param baz: Optional, some other input + + :return: Some boolean + """ +``` + +Since PyDis does not utilize automatic documentation generation, use of this syntax should not be used in code contributed to the organization. Should the purpose and type of the input variables not be easily discernable from the variable name and type annotation, a prose explanation can be used. Explicit references to variables, functions, classes, etc. should be wrapped with backticks (`` ` ``). + +For example, the above docstring would become: + +```py +def foo(bar: int, baz: dict=None) -> bool: + """ + Does some things with some stuff. + + This function takes an index, `bar` and checks for its presence in the database `baz`, passed as a dictionary. + + Returns `False` if `baz` is not passed. + """ +``` ### Logging levels The project currently defines [`logging`](https://docs.python.org/3/library/logging.html) levels as follows: @@ -45,6 +99,11 @@ The project currently defines [`logging`](https://docs.python.org/3/library/logg * **ERROR:** An error that affects the specific part that is being interacted with * **CRITICAL:** An error that affects the whole application. +### Work in Progress (WIP) PRs +Github [has introduced a new PR feature](https://github.blog/2019-02-14-introducing-draft-pull-requests/) that allows the PR author to mark it as a WIP. This provides both a visual and functional indicator that the contents of the PR are in a draft state and not yet ready for formal review. + +This feature should be utilized in place of the traditional method of prepending `[WIP]` to the PR title. + ## Footnotes This document was inspired by the [Glowstone contribution guidelines](https://github.com/GlowstoneMC/Glowstone/blob/dev/docs/CONTRIBUTING.md). diff --git a/bot/resources/easter/egghead_questions.json b/bot/resources/easter/egghead_questions.json new file mode 100644 index 00000000..e4e21ebe --- /dev/null +++ b/bot/resources/easter/egghead_questions.json @@ -0,0 +1,181 @@ +[ + { + "question": "Where did the idea of the Easter Bunny originate?", + "answers": [ + "Russia", + "The United States", + "The UK", + "Germany" + ], + "correct_answer": 3 + }, + { + "question": "The Easter Bunny was originally going to be a...", + "answers": [ + "hare", + "possum", + "cat", + "dove" + ], + "correct_answer": 0 + }, + { + "question": "Which of the following is NOT a movie about Easter?", + "answers": [ + "Winnie the Pooh - Springtime with Roo", + "It's a Wonderful Life", + "The Passion of the Christ", + "Here Comes Peter Cottontail" + ], + "correct_answer": 1 + }, + { + "question": "In Australia, what animal is used instead of the Easter Bunny?", + "answers": [ + "kangaroo", + "wombat", + "koala", + "bilby" + ], + "correct_answer": 3 + }, + { + "question": "When was the first Earth Day?", + "answers": [ + "1982", + "2003", + "1999", + "1970" + ], + "correct_answer": 2 + }, + { + "question": "Who is considered to be the founder of Earth Day?", + "answers": [ + "President Jimmy Carter", + "President John F. Kennedy", + "Vice President Al Gore", + "Senator Gaylord Nelson" + ], + "correct_answer": 3 + }, + { + "question": "Approximately how many countries participated in Earth Day 2000?", + "answers": [ + "60", + "140", + "180", + "240" + ], + "correct_answer": 2 + }, + { + "question": "As Earth Day is this month, how old is the Earth?", + "answers": [ + "4.5 billion years old", + "5 million years old", + "10 billion years old", + "6.7 billion years old" + ], + "correct_answer": 0 + }, + { + "question": "As a celebration of Earth Day, what is the percentage of Oxygen in the Earth's atmosphere?", + "answers": [ + "18%", + "21%", + "25%", + "31%" + ], + "correct_answer": 1 + }, + { + "question": "In what year did Google begin its tradition of April Fools Jokes?", + "answers": [ + "1997", + "2000", + "2003", + "2007" + ], + "correct_answer": 1 + }, + { + "question": "Which type of chocolate is the most healthy?", + "answers": [ + "Dark", + "White", + "Milk" + ], + "correct_answer": 0 + }, + { + "question": "How many bars of milk chocolate would you have to eat to get the same amount of caffeine as in one cup of coffee?", + "answers": [ + "3", + "9", + "14", + "20" + ], + "correct_answer": 2 + }, + { + "question": "Aztecs used to use one of the ingedients of chocolate, cocoa beans, as...", + "answers": [ + "currency", + "medicine", + "dye", + "fertilizer" + ], + "correct_answer": 0 + }, + { + "question": "Which European country was the first to enjoy chocolate?", + "answers": [ + "France", + "Spain", + "England", + "Switzerland" + ], + "correct_answer": 1 + }, + { + "question": "The first European Chocolate Shop opened in what city in 1657?", + "answers": [ + "Paris, France", + "Madrid, Spain", + "Zürich, Switzerland", + "London, England" + ], + "correct_answer": 3 + }, + { + "question": "On average, how many eggs does a hen lay in a year?", + "answers": [ + "Between 200-230", + "Between 250-270", + "Between 300-330", + "Between 370-400" + ], + "correct_answer": 1 + }, + { + "question": "What determines the colour of an egg yolk?", + "answers": [ + "The size of the hen", + "The age of a hen", + "The diet of a hen", + "The colour of a hen's feathers" + ], + "correct_answer": 2 + }, + { + "question": "What country produces the most eggs in a year?", + "answers": [ + "China", + "India", + "The United States", + "Japan" + ], + "correct_answer": 0 + } +]
\ No newline at end of file diff --git a/bot/resources/easter/traditions.json b/bot/resources/easter/traditions.json new file mode 100644 index 00000000..f9dd6d81 --- /dev/null +++ b/bot/resources/easter/traditions.json @@ -0,0 +1,13 @@ +{"England": "Easter in England is celebrated through the exchange of Easter Eggs and other gifts like clothes, chocolates or holidays packages. Easter bonnets or baskets are also made that have fillings like daffodils in them.", +"Haiti": "In Haiti, kids have the freedom to spend Good Friday playing outdoors. On this day colourful kites fill the sky and children run long distances, often barefoot, trying to get their kite higher than their friends.", +"Indonesia": "Slightly unconventional, but kids in Indonesia celebrate Easter with a tooth brushing competition!", +"Ethipoia": "In Ethiopia, Easter is called Fasika and marks the end of a 55-day fast during which Christians have only eaten one vegetarian meal a day. Ethiopians will often break their fast after church by eating injera (a type of bread) or teff pancakes, made from grass flour.", +"El Salvador": "On Good Friday communities make rug-like paintings on the streets with sand and sawdust. These later become the path for processions and main avenues and streets are closed", +"Ghana": "Ghanaians dress in certain colours to mark the different days of Easter. On Good Friday, depending on the church denomination, men and women will either dress in dark mourning clothes or bright colours. On Easter Sunday everyone wears white.", +"Kenya": "On Easter Sunday, kids in Kenya look forward to a sumptuous Easter meal after church (Easter services are known to last for three hours!). Children share Nyama Choma (roasted meat) and have a soft drink with their meal!", +"Guatemala": "In Guatemala, Easter customs include a large, colourful celebration marked by countless processions. The main roads are closed, and the sound of music rings through the streets. Special food is prepared such as curtido (a diced vegetable mix which is cooked in vinegar to achieve a sour taste), fish, eggs, chickpeas, fruit mix, pumpkin, pacaya palm and spondias fruit (a Spanish version of a plum.)", +"Germany": "In Germany, Easter is known by the name of Ostern. Easter holidays for children last for about three weeks. Good Friday, Easter Saturday and Easter Sunday are the days when people do not work at all.", +"Mexico": "Semana Santa and Pascua (two separate observances) form a part of Easter celebrations in Mexico. Semana Santa stands for the entire Holy Week, from Palm Sunday to Easter Saturday, whereas the Pascua is the observance of the period from the Resurrection Sunday to the following Saturday.", +"Poland": "They shape the Easter Butter Lamb (Baranek Wielkanocyny) from a chunk of butter. They attempt to make it look like a fluffy lamb!", +"Greece": "They burn an effigy of Judas Iscariot, the betrayer of Jesus, sometimes is done as part of a Passion Play! It is hung by the neck and then burnt.", +"Philippines": "Some Christians put themselves through the same pain that Christ endured, they have someone naile them to a cross and put a crown of thornes on their head."} diff --git a/bot/resources/halloween/spooky_rating.json b/bot/resources/halloween/spooky_rating.json new file mode 100644 index 00000000..1815befc --- /dev/null +++ b/bot/resources/halloween/spooky_rating.json @@ -0,0 +1,47 @@ +{ + "-1": { + "title": "\uD83D\uDD6F You're not scarin' anyone \uD83D\uDD6F", + "text": "No matter what you say or do, nobody even flinches when you try to scare them. Was your costume this year only a white sheet with holes for eyes? Or did you even bother with a costume at all? Either way, don't expect too many treats when going from door-to-door.", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/candle.jpeg" + }, + "5": { + "title": "\uD83D\uDC76 Like taking candy from a baby \uD83D\uDC76", + "text": "Your scaring will probably make a baby cry... but that's the limit on your frightening powers. Be careful not to get to the point where everyone's running away from you because they don't like you, not because they're scared of you.", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/baby.jpeg" + }, + "20": { + "title": "\uD83C\uDFDA You're skills are forming... \uD83C\uDFDA", + "text": "As you become the Devil's apprentice, you begin to make people jump every time you sneak up on them. A good start, but you have to learn not to wear the same costume every year until it doesn't fit you. People will notice you and your prowess will decrease.", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/tiger.jpeg" + }, + "30": { + "title": "\uD83D\uDC80 Picture Perfect... \uD83D\uDC80", + "text": "You've nailed the costume this year! You look suuuper scary! Now make sure to play the part and act out your costume and you'll be sure to give a few people a massive fright!", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/costume.jpeg" + }, + "50": { + "title": "\uD83D\uDC7B Uhm... are you human \uD83D\uDC7B", + "text": "Uhm... you're too good to be human and now you're beginning to sound like a ghost. You're almost invisible when haunting and nobody truly knows where you are at any given time. But they will always scream at the sound of a ghost...", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/ghost.jpeg" + }, + "65": { + "title": "\uD83C\uDF83 That potion can't be real \uD83C\uDF83", + "text": "You're carrying... some... unknown liquids and no one knows who they are but yourself. Be careful on who you use these powerful spells on, because no Mage has the power to do any irreversible enchantments because even you won't know what will happen to these mortals.", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/necromancer.jepg" + }, + "80": { + "title": "\uD83E\uDD21 The most sinister face \uD83E\uDD21", + "text": "Who knew something intended to be playful could be so menacing... Especially other people seeing you in their nightmares, continuing to haunt them day by day, stuck in their head throughout the entire year. Make sure to pull a face they will never forget.", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/clown.jpeg" + }, + "95": { + "title": "\uD83D\uDE08 The Devil's Accomplice \uD83D\uDE08", + "text": "Imagine being allies with the most evil character with an aim to scare people to death. Force people to suffer as they proceed straight to hell to meet your boss and best friend. Not even you know the power He has...", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/jackolantern.jpg" + }, + "100": { + "title":"\uD83D\uDC7F The Devil Himself \uD83D\uDC7F", + "text": "You are the evillest creature in existence to scare anyone and everyone humanly possible. The reason your underlings are called mortals is that they die. With your help, they die a lot quicker. With all the evil power in the universe, you know what to do.", + "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/devil.jpeg" + } +}
\ No newline at end of file diff --git a/bot/resources/halloween/spookyrating/baby.jpeg b/bot/resources/halloween/spookyrating/baby.jpeg Binary files differnew file mode 100644 index 00000000..199f8bca --- /dev/null +++ b/bot/resources/halloween/spookyrating/baby.jpeg diff --git a/bot/resources/halloween/spookyrating/candle.jpeg b/bot/resources/halloween/spookyrating/candle.jpeg Binary files differnew file mode 100644 index 00000000..9913752b --- /dev/null +++ b/bot/resources/halloween/spookyrating/candle.jpeg diff --git a/bot/resources/halloween/spookyrating/clown.jpeg b/bot/resources/halloween/spookyrating/clown.jpeg Binary files differnew file mode 100644 index 00000000..f23c4f70 --- /dev/null +++ b/bot/resources/halloween/spookyrating/clown.jpeg diff --git a/bot/resources/halloween/spookyrating/costume.jpeg b/bot/resources/halloween/spookyrating/costume.jpeg Binary files differnew file mode 100644 index 00000000..b3c21af0 --- /dev/null +++ b/bot/resources/halloween/spookyrating/costume.jpeg diff --git a/bot/resources/halloween/spookyrating/devil.jpeg b/bot/resources/halloween/spookyrating/devil.jpeg Binary files differnew file mode 100644 index 00000000..4f45aaa7 --- /dev/null +++ b/bot/resources/halloween/spookyrating/devil.jpeg diff --git a/bot/resources/halloween/spookyrating/ghost.jpeg b/bot/resources/halloween/spookyrating/ghost.jpeg Binary files differnew file mode 100644 index 00000000..0cb13346 --- /dev/null +++ b/bot/resources/halloween/spookyrating/ghost.jpeg diff --git a/bot/resources/halloween/spookyrating/jackolantern.jpeg b/bot/resources/halloween/spookyrating/jackolantern.jpeg Binary files differnew file mode 100644 index 00000000..d7cf3d08 --- /dev/null +++ b/bot/resources/halloween/spookyrating/jackolantern.jpeg diff --git a/bot/resources/halloween/spookyrating/necromancer.jpeg b/bot/resources/halloween/spookyrating/necromancer.jpeg Binary files differnew file mode 100644 index 00000000..60b1e689 --- /dev/null +++ b/bot/resources/halloween/spookyrating/necromancer.jpeg diff --git a/bot/resources/halloween/spookyrating/tiger.jpeg b/bot/resources/halloween/spookyrating/tiger.jpeg Binary files differnew file mode 100644 index 00000000..0419f5df --- /dev/null +++ b/bot/resources/halloween/spookyrating/tiger.jpeg diff --git a/bot/seasons/easter/egghead_quiz.py b/bot/seasons/easter/egghead_quiz.py new file mode 100644 index 00000000..8dd2c21d --- /dev/null +++ b/bot/seasons/easter/egghead_quiz.py @@ -0,0 +1,121 @@ +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', 'egghead_questions.json'), 'r', encoding="utf8") as f: + EGGHEAD_QUESTIONS = load(f) + + +EMOJIS = [ + '\U0001f1e6', '\U0001f1e7', '\U0001f1e8', '\U0001f1e9', '\U0001f1ea', + '\U0001f1eb', '\U0001f1ec', '\U0001f1ed', '\U0001f1ee', '\U0001f1ef', + '\U0001f1f0', '\U0001f1f1', '\U0001f1f2', '\U0001f1f3', '\U0001f1f4', + '\U0001f1f5', '\U0001f1f6', '\U0001f1f7', '\U0001f1f8', '\U0001f1f9', + '\U0001f1fa', '\U0001f1fb', '\U0001f1fc', '\U0001f1fd', '\U0001f1fe', + '\U0001f1ff' +] # Regional Indicators A-Z (used for voting) + +TIMELIMIT = 30 + + +class EggheadQuiz(commands.Cog): + """This cog contains the command for the Easter quiz!""" + + def __init__(self, bot): + self.bot = bot + self.quiz_messages = {} + + @commands.command(aliases=["eggheadquiz", "easterquiz"]) + async def eggquiz(self, ctx): + """ + Gives a random quiz question, waits 30 seconds and then outputs the answer + + Also informs of the percentages and votes of each option + """ + + random_question = random.choice(EGGHEAD_QUESTIONS) + question, answers = random_question["question"], random_question["answers"] + answers = [(EMOJIS[i], a) for i, a in enumerate(answers)] + correct = EMOJIS[random_question["correct_answer"]] + + valid_emojis = [emoji for emoji, _ in answers] + + description = f"You have {TIMELIMIT} seconds to vote.\n\n" + description += "\n".join([f"{emoji} -> **{answer}**" for emoji, answer in answers]) + + q_embed = discord.Embed(title=question, description=description, colour=Colours.pink) + + msg = await ctx.send(embed=q_embed) + for emoji in valid_emojis: + await msg.add_reaction(emoji) + + self.quiz_messages[msg.id] = valid_emojis + + await asyncio.sleep(TIMELIMIT) + + del self.quiz_messages[msg.id] + + msg = await ctx.channel.fetch_message(msg.id) # Refreshes message + + total_no = sum([len(await r.users().flatten()) for r in msg.reactions]) - len(valid_emojis) # - bot's reactions + + if total_no == 0: + return await msg.delete() # to avoid ZeroDivisionError if nobody reacts + + results = ["**VOTES:**"] + for emoji, _ in answers: + num = [len(await r.users().flatten()) for r in msg.reactions if str(r.emoji) == emoji][0] - 1 + percent = round(100 * num / total_no) + s = "" if num == 1 else "s" + string = f"{emoji} - {num} vote{s} ({percent}%)" + results.append(string) + + mentions = " ".join([ + u.mention for u in [ + await r.users().flatten() for r in msg.reactions if str(r.emoji) == correct + ][0] if not u.bot + ]) + + content = f"Well done {mentions} for getting it correct!" if mentions else "Nobody got it right..." + + a_embed = discord.Embed( + title=f"The correct answer was {correct}!", + description="\n".join(results), + colour=Colours.pink + ) + + await ctx.send(content, embed=a_embed) + + @staticmethod + async def already_reacted(message, user): + """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""" + if user.bot: + return + if reaction.message.id not in self.quiz_messages: + return + if str(reaction.emoji) not in self.quiz_messages[reaction.message.id]: + return await reaction.message.remove_reaction(reaction, user) + if await self.already_reacted(reaction.message, user): + return await reaction.message.remove_reaction(reaction, user) + + +def setup(bot): + """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 new file mode 100644 index 00000000..05cd79f3 --- /dev/null +++ b/bot/seasons/easter/traditions.py @@ -0,0 +1,33 @@ +import json +import logging +import random +from pathlib import Path + +from discord.ext import commands + +log = logging.getLogger(__name__) + +with open(Path('bot', 'resources', 'easter', 'traditions.json'), 'r', encoding="utf8") as f: + traditions = json.load(f) + + +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): + self.bot = bot + + @commands.command(aliases=('eastercustoms',)) + async def easter_tradition(self, ctx): + """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): + """Traditions Cog load.""" + + bot.add_cog(Traditions(bot)) + log.info("Traditions cog loaded") diff --git a/bot/seasons/evergreen/lemonstats.py b/bot/seasons/evergreen/lemonstats.py deleted file mode 100644 index b23c65a4..00000000 --- a/bot/seasons/evergreen/lemonstats.py +++ /dev/null @@ -1,31 +0,0 @@ -import logging - -from discord.ext import commands - - -log = logging.getLogger(__name__) - - -class LemonStats(commands.Cog): - """A cog for generating useful lemon-related statistics.""" - - def __init__(self, bot): - self.bot = bot - - @commands.command() - async def lemoncount(self, ctx: commands.Context): - """Count the number of users on the server with `'lemon'` in their nickname.""" - - async with ctx.typing(): - lemoncount = sum( - ['lemon' in server_member.display_name.lower() for server_member in self.bot.guilds[0].members] - ) - - await ctx.send(f"There are currently {lemoncount} lemons on the server.") - - -def setup(bot): - """Load LemonStats Cog.""" - - bot.add_cog(LemonStats(bot)) - log.info("LemonStats cog loaded") diff --git a/bot/seasons/halloween/spookyrating.py b/bot/seasons/halloween/spookyrating.py new file mode 100644 index 00000000..a9cfda9b --- /dev/null +++ b/bot/seasons/halloween/spookyrating.py @@ -0,0 +1,68 @@ +import bisect +import json +import logging +import random +from pathlib import Path + +import discord +from discord.ext import commands + +from bot.constants import Colours + +log = logging.getLogger(__name__) + +with Path('bot', 'resources', 'halloween', 'spooky_rating.json').open() as file: + SPOOKY_DATA = json.load(file) + SPOOKY_DATA = sorted((int(key), value) for key, value in SPOOKY_DATA.items()) + + +class SpookyRating(commands.Cog): + """A cog for calculating one's spooky rating""" + + def __init__(self, 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): + """ + Calculates the spooky rating of someone. + + Any user will always yield the same result, no matter who calls the command + """ + + if who is None: + who = ctx.author + + # This ensures that the same result over multiple runtimes + self.local_random.seed(who.id) + spooky_percent = self.local_random.randint(1, 101) + + # We need the -1 due to how bisect returns the point + # see the documentation for further detail + # https://docs.python.org/3/library/bisect.html#bisect.bisect + index = bisect.bisect(SPOOKY_DATA, (spooky_percent,)) - 1 + + _, data = SPOOKY_DATA[index] + + embed = discord.Embed( + title=data['title'], + description=f'{who} scored {spooky_percent}%!', + color=Colours.orange + ) + embed.add_field( + name='A whisper from Satan', + value=data['text'] + ) + embed.set_thumbnail( + url=data['image'] + ) + + await ctx.send(embed=embed) + + +def setup(bot): + """Cog load.""" + bot.add_cog(SpookyRating(bot)) + log.info("SpookyRating cog loaded") |