diff options
author | 2019-10-12 05:10:26 +0200 | |
---|---|---|
committer | 2019-10-12 05:10:26 +0200 | |
commit | 3c41e5b5dc84a1ab78863afbf751b70c2f83ccfc (patch) | |
tree | 344746635d27c4f95ec7494c4f261cc0479bd810 /bot | |
parent | Fixed forgetting to divide by 100. (diff) | |
parent | Merge branch 'master' into hacktober-issue-finder (diff) |
Merge remote-tracking branch 'origin/hacktober-issue-finder' into hacktober-issue-finder
Diffstat (limited to 'bot')
-rw-r--r-- | bot/constants.py | 1 | ||||
-rw-r--r-- | bot/decorators.py | 51 | ||||
-rw-r--r-- | bot/resources/evergreen/trivia_quiz.json | 221 | ||||
-rw-r--r-- | bot/resources/pride/drag_queen_names.json | 249 | ||||
-rw-r--r-- | bot/resources/pride/facts.json | 34 | ||||
-rw-r--r-- | bot/seasons/christmas/adventofcode.py | 2 | ||||
-rw-r--r-- | bot/seasons/evergreen/issues.py | 2 | ||||
-rw-r--r-- | bot/seasons/evergreen/trivia_quiz.py | 234 | ||||
-rw-r--r-- | bot/seasons/halloween/hacktoberstats.py | 9 | ||||
-rw-r--r-- | bot/seasons/pride/drag_queen_name.py | 33 | ||||
-rw-r--r-- | bot/seasons/pride/pride_facts.py | 106 |
11 files changed, 926 insertions, 16 deletions
diff --git a/bot/constants.py b/bot/constants.py index dbf35754..0d4321c8 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -52,6 +52,7 @@ class Channels(NamedTuple): python_discussion = 267624335836053506 show_your_projects = int(environ.get("CHANNEL_SHOW_YOUR_PROJECTS", 303934982764625920)) show_your_projects_discussion = 360148304664723466 + hacktoberfest_2019 = 628184417646411776 class Client(NamedTuple): diff --git a/bot/decorators.py b/bot/decorators.py index dbaad4a2..58f67a15 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -64,12 +64,16 @@ def without_role(*role_ids: int) -> bool: 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.""" + """ + Checks that the message is in a whitelisted channel or optionally has a bypass role. + + If `in_channel_override` is present, check if it contains channels + and use them in place of the global whitelist. + """ 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 " @@ -77,13 +81,6 @@ def in_channel_check(*channels: int, bypass_roles: typing.Container[int] = None) ) return True - if hasattr(ctx.command.callback, "in_channel_override"): - 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 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 " @@ -91,6 +88,31 @@ def in_channel_check(*channels: int, bypass_roles: typing.Container[int] = None) ) return True + if hasattr(ctx.command.callback, "in_channel_override"): + override = ctx.command.callback.in_channel_override + if override is None: + 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 + else: + if ctx.channel.id in override: + log.debug( + f"{ctx.author} tried to call the '{ctx.command.name}' command " + f"and the command was used in an overridden whitelisted channel." + ) + return True + + log.debug( + f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The overridden in_channel check failed." + ) + channels_str = ', '.join(f"<#{c_id}>" for c_id in override) + raise InChannelCheckFailure( + f"Sorry, but you may only use this command within {channels_str}." + ) + log.debug( f"{ctx.author} tried to call the '{ctx.command.name}' command. " f"The in_channel check failed." @@ -107,14 +129,19 @@ def in_channel_check(*channels: int, bypass_roles: typing.Container[int] = None) in_channel = commands.check(in_channel_check) -def override_in_channel(func: typing.Callable) -> typing.Callable: +def override_in_channel(channels: typing.Tuple[int] = None) -> typing.Callable: """ Set command callback attribute for detection in `in_channel_check`. + Override global whitelist if channels are specified. + This decorator has to go before (below) below the `command` decorator. """ - func.in_channel_override = True - return func + def inner(func: typing.Callable) -> typing.Callable: + func.in_channel_override = channels + return func + + return inner def locked() -> typing.Union[typing.Callable, None]: diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json new file mode 100644 index 00000000..1ad2a1e1 --- /dev/null +++ b/bot/resources/evergreen/trivia_quiz.json @@ -0,0 +1,221 @@ +{ + "retro": [ + { + "id": 1, + "hints": ["It is not a mainline Mario Game, although the plumber is present.", "It is not a mainline Zelda Game, although Link is present."], + "question": "What is the best selling game on the Nintendo GameCube?", + "answer": "Super Smash Bros" + }, + { + "id": 2, + "hints": ["It was released before the 90's.", "It was released after 1980."], + "question": "What year was Tetris released?", + "answer": "1984" + }, + { + "id": 3, + "hints": ["The occupation was in construction", "He appeared as this kind of worker in 1981's Donkey Kong"], + "question": "What was Mario's original occupation?", + "answer": "Carpenter" + }, + { + "id": 4, + "hints": ["It was revealed in the Nintendo Character Guide in 1993.", "His last name has to do with eating Mario's enemies."], + "question": "What is Yoshi's (from Mario Bros.) full name?", + "answer": "Yoshisaur Munchakoopas" + }, + { + "id": 5, + "hints": ["The game was released in 1990.", "It was released on the SNES."], + "question": "What was the first game Yoshi appeared in?", + "answer": "Super Mario World" + } + ], + "general":[ + { + "id": 100, + "question": "Name \"the land of a thousand lakes\"", + "answer": "Finland", + "info": "Finland is a country in Northern Europe. Sweden borders it to the northwest, Estonia to the south, Russia to the east, and Norway to the north. Finland is part of the European Union with its capital city being Helsinki. With a population of 5.5 million people, it has over 187,000 lakes. The thousands of lakes in Finland are the reason why the country's nickname is \"the land of a thousand lakes.\"" + }, + { + "id": 101, + "question": "Who was the winner of FIFA 2018?", + "answer": "France", + "info": "France 4 - 2 Croatia" + }, + { + "id": 102, + "question": "What is the largest ocean in the world?", + "answer": "Pacific", + "info": "The Pacific Ocean is the largest and deepest of the world ocean basins. Covering approximately 63 million square miles and containing more than half of the free water on Earth, the Pacific is by far the largest of the world's ocean basins." + }, + { + "id": 103, + "question": "Who gifted the Statue Of Liberty?", + "answer": "France", + "info": "The Statue of Liberty was a gift from the French people commemorating the alliance of France and the United States during the American Revolution. Yet, it represented much more to those individuals who proposed the gift." + }, + { + "id": 104, + "question": "Which country is known as the \"Land Of The Rising Sun\"?", + "answer": "Japan", + "info": "The title stems from the Japanese names for Japan, Nippon/Nihon, both literally translating to \"the suns origin\"." + }, + { + "id": 105, + "question": "What's known as the \"Playground of Europe\"?", + "answer": "Switzerland", + "info": "It comes from the title of a book written in 1870 by Leslie Stephen (father of Virginia Woolf) detailing his exploits of mountain climbing (not skiing) of which sport he was one of the pioneers and trekking or walking." + }, + { + "id": 106, + "question": "Which country is known as the \"Land of Thunderbolt\"?", + "info": "Bhutan is known as the \"Land of Thunder Dragon\" or \"Land of Thunderbolt\" due to the violent and large thunderstorms that whip down through the valleys from the Himalayas. The dragon reference was due to people thinking the sparkling light of thunderbolts was the red fire of a dragon." + }, + { + "id": 107, + "question": "Which country is the largest producer of tea in the world?", + "answer": "China", + "info": "Tea is mainly grown in Asia, Africa, South America, and around the Black and Caspian Seas. The four biggest tea-producing countries today are China, India, Sri Lanka and Kenya. Together they represent 75% of world production." + }, + { + "id": 108, + "question": "Which country is the largest producer of coffee?", + "answer": "Brazil", + "info": "Brazil is the world's largest coffee producer. In 2016, Brazil produced a staggering 2,595,000 metric tons of coffee beans. It is not a new development, as Brazil has been the highest global producer of coffee beans for over 150 years." + }, + { + "id": 109, + "question": "Which country is Mount Etna, one of the most active volcanoes in the world, located?", + "answer": "Italy", + "info": "Mount Etna is the highest volcano in Europe. Towering above the city of Catania on the island of Sicily, it has been growing for about 500,000 years and is in the midst of a series of eruptions that began in 2001." + }, + { + "id": 110, + "question": "Which country is called \"Battleground of Europe?\"", + "answer": "Belgium", + "info": "Belgium has been the \"Battleground of Europe\" since the Roman Empire as it had no natural protection from its larger neighbouring countries. The battles of Oudenaarde, Ramillies, Waterloo, Ypres and Bastogne were all fought on Belgian soil." + }, + { + "id": 111, + "question": "Which is the largest tropical rain forest in the world?", + "answer": "Amazon", + "info": "The Amazon is regarded as vital in the fight against global warming due to its ability to absorb carbon from the air. It's often referred to as the \"lungs of the Earth,\" as more than 20 per cent of the world's oxygen is produced there." + }, + { + "id": 112, + "question": "Which is the largest island in the world?", + "answer": "Greenland", + "info": "Commonly thought to be Australia, but as it's actually a continental landmass, it doesn't get to make it in the list." + }, + { + "id": 113, + "question": "What's the name of the tallest waterfall in the world.", + "answer": "Angel", + "info": "Angel Falls (Salto Ángel) in Venezuela is the highest waterfall in the world. The falls are 3230 feet in height, with an uninterrupted drop of 2647 feet. Angel Falls is located on a tributary of the Rio Caroni." + }, + { + "id": 114, + "question": "What country is called \"Land of White Elephants\"?", + "answer": "Thailand", + "info": "White elephants were regarded to be holy creatures in ancient Thailand and some other countries. Today, white elephants are still used as a symbol of divine and royal power in the country. Ownership of a white elephant symbolizes wealth, success, royalty, political power, wisdom, and prosperity." + }, + { + "id": 115, + "question": "Which city is in two continents?", + "answer": "Istanbul", + "info": "Istanbul embraces two continents, one arm reaching out to Asia, the other to Europe." + }, + { + "id": 116, + "question": "The Vally Of The Kings is located in which country?", + "answer": "Egypt", + "info": "The Valley of the Kings, also known as the Valley of the Gates of the Kings, is a valley in Egypt where, for a period of nearly 500 years from the 16th to 11th century BC, rock cut tombs were excavated for the pharaohs and powerful nobles of the New Kingdom (the Eighteenth to the Twentieth Dynasties of Ancient Egypt)." + }, + { + "id": 117, + "question": "Diamonds are always nice in Minecraft, but can you name the \"Diamond Capital in the World\"?", + "answer": "Antwerp", + "info": "Antwerp, Belgium is where 60-80% of the world's diamonds are cut and traded, and is known as the \"Diamond Capital of the World.\"" + }, + { + "id": 118, + "question": "Where is the \"International Court Of Justice\" located at?", + "answer": "Hague", + "info": "" + }, + { + "id": 119, + "question": "In which country is Bali located in?", + "answer": "Indonesia", + "info": "" + }, + { + "id": 120, + "question": "What country is the world's largest coral reef system, the \"Great Barrier Reef\", located in?", + "answer": "Australia", + "info": "The Great Barrier Reef is the world's largest coral reef system composed of over 2,900 individual reefs and 900 islands stretching for over 2,300 kilometres (1,400 mi) over an area of approximately 344,400 square kilometres (133,000 sq mi). The reef is located in the Coral Sea, off the coast of Queensland, Australia." + }, + { + "id": 121, + "question": "When did the First World War start?", + "answer": "1914", + "info": "The first world war began in August 1914. It was directly triggered by the assassination of the Austrian archduke, Franz Ferdinand and his wife, on 28th June 1914 by Bosnian revolutionary, Gavrilo Princip. This event was, however, simply the trigger that set off declarations of war." + }, + { + "id": 122, + "question": "Which is the largest hot desert in the world?", + "answer": "Sahara", + "info": "The Sahara Desert covers 3.6 million square miles. It is almost the same size as the United States or China. There are sand dunes in the Sahara as tall as 590 feet." + }, + { + "id": 123, + "question": "Who lived at 221B, Baker Street, London?", + "answer": "Sherlock Holmes", + "info": "" + }, + { + "id": 124, + "question": "When did the Second World War end?", + "answer": "1945", + "info": "World War 2 ended with the unconditional surrender of the Axis powers. On 8 May 1945, the Allies accepted Germany's surrender, about a week after Adolf Hitler had committed suicide. VE Day – Victory in Europe celebrates the end of the Second World War on 8 May 1945." + }, + { + "id": 125, + "question": "What is the name of the largest dam in the world?", + "answer": "Three Gorges Dam", + "info": "At 1.4 miles wide (2.3 kilometers) and 630 feet (192 meters) high, Three Gorges Dam is the largest hydroelectric dam in the world, according to International Water Power & Dam Construction magazine. Three Gorges impounds the Yangtze River about 1,000 miles (1,610 km) west of Shanghai." + }, + { + "id": 126, + "question": "What's the name of the largest river in the world?", + "answer": "Nile", + "info": "The Nile, which is about 6,650 km (4,130 mi) long, is an \"international\" river as its drainage basin covers eleven countries, namely, Tanzania, Uganda, Rwanda, Burundi, the Democratic Republic of the Congo, Kenya, Ethiopia, Eritrea, South Sudan, Republic of the Sudan and Egypt." + }, + { + "id": 127, + "question": "Which is the smallest planet in the Solar System?", + "answer": "Mercury", + "info": "Mercury is the smallest planet in our solar system. It's just a little bigger than Earth's moon. It is the closest planet to the sun, but it's actually not the hottest. Venus is hotter." + }, + { + "id": 128, + "question": "What is the smallest country?", + "answer": "Vatican City", + "info": "With an area of 0.17 square miles (0.44 km2) and a population right around 1,000, Vatican City is the smallest country in the world, both in terms of size and population." + }, + { + "id": 129, + "question": "What's the name of the largest bird?", + "answer": "Ostrich", + "info": "The largest living bird, a member of the Struthioniformes, is the ostrich (Struthio camelus), from the plains of Africa and Arabia. A large male ostrich can reach a height of 2.8 metres (9.2 feet) and weigh over 156 kilograms (344 pounds)." + }, + { + "id": 130, + "question": "What does the acronym GPRS stand for?", + "answer": "General Packet Radio Service", + "info": "General Packet Radio Service (GPRS) is a packet-based mobile data service on the global system for mobile communications (GSM) of 3G and 2G cellular communication systems. It is a non-voice, high-speed and useful packet-switching technology intended for GSM networks." + } + ] +} diff --git a/bot/resources/pride/drag_queen_names.json b/bot/resources/pride/drag_queen_names.json new file mode 100644 index 00000000..f63cdec3 --- /dev/null +++ b/bot/resources/pride/drag_queen_names.json @@ -0,0 +1,249 @@ +[ + "Adelle Lectible", + "Adelle Light", + "Adelle Lirious", + "Alison Wonder", + "Amie Thyst", + "Amie Zonite", + "Angela Develle", + "Anna Conda", + "Anne Amaley", + "Annie Nigma", + "Aria Hymn", + "Aria Viderci", + "Aroa Mattic", + "Aster Starr", + "Aura Aurora", + "Aura Ley", + "Aurora Dorea", + "Barba Rouse", + "Bea Constrictor", + "Bella Lush", + "Belle Icoza", + "Belle Ligerrente", + "Betty Brilliance", + "Bo Deysious", + "Carol Chorale", + "Cecil Clouds", + "Cecil Sunshine", + "Celeste Booday", + "Chichi Swank", + "Claire Geeman", + "Claire Rickal", + "Claire Voyance", + "Cleo Patrix", + "Connie Fidence", + "Corra Rageous", + "Daye Light", + "Deedee Cation", + "Deedee Sign", + "Dianne Gerous", + "Didi Divine", + "Diemme Monds", + "Dorothy Doughty", + "Dutches Dauntless", + "Ella Gance", + "Ella Gants", + "Ella Menterry", + "Ella Stique", + "Elle Lectrick", + "Elle Lure", + "Emma Geddon", + "Emma Phasis", + "Emma Rald", + "Emme Plosion", + "Emme Pulse", + "Emme Vention", + "Enna Fincible", + "Enne Phinite", + "Enne Treppide", + "Etha Nitty", + "Etha Reyal", + "Euphoria Bliss", + "Eva Nessent", + "Eve Forric", + "Eve Ningowne", + "Eve Ville", + "Faith Lesse", + "Faschia Nation", + "Faye Boulous", + "Faye Lacious", + "Faye Minine", + "Faye Nixx", + "Felicity Spice", + "Freya Domme", + "Gal Gallant", + "Gal Galore", + "Gal Lante", + "Gemma Safir", + "Gena Rocity", + "Genna Russ", + "Gigi Lamour", + "Gigi Rand", + "Glemma Rouss", + "Grace Iyus", + "Haye Light", + "Hazel Nutt", + "Hella Billy", + "Hella Centrique", + "Hella Cious", + "Hella Riouss", + "Hella Whole", + "Hellen Back", + "Herra Zee", + "Ina Creddeble", + "Ina Fernalle", + "Jo Nee", + "Jo Phial", + "Joye Ryde", + "Jue Cee", + "Jue Wells", + "Juju Bee", + "Kaia Cayenne", + "Kaye Bye", + "Kitsch Kitsch Bang Bang", + "Lady Lace", + "Lavish Lazuli", + "Lea Ness", + "Leye Berty", + "Lisse Truss", + "Liv Lee", + "Lola Lavish", + "Lolo Yaltie", + "Lucy Fur", + "Lucy Luck", + "Lulu LaBye", + "Lulu Xuri", + "Lunaye Clipse", + "Lyra Kall", + "Maggie Magma", + "Mara Bells", + "Marry Golds", + "Marry Nayde", + "Marry Sipan", + "Marve Vellus", + "Mary Ganal", + "Mary Malade", + "May Jestic", + "May Lancholly", + "May Licious", + "May Lodi", + "May Morable", + "May Stirius", + "May Varlous", + "Melody Gale", + "Melody Toune", + "Miss Adora", + "Miss Alure", + "Miss Chieff", + "Miss Fortune", + "Miss Mash", + "Miss Mood", + "Miss Nomer", + "Miss Sanguine", + "Miss Sublime", + "Mistress Galore", + "Monique Mystique", + "Morgan Fatana", + "Nashay Kitt", + "Nicole Lorful", + "Noë Stalgia", + "Ora Kelle", + "Ora Nate", + "Patty Siyens", + "Penny Laized", + "Penny Ramma", + "Penny Rammic", + "Penny Talloons", + "Percey Ferance", + "Perry Fomance", + "Phara Waye", + "Phata Morgana", + "Pho Latyle", + "Pho Lume", + "Phoebe Rant", + "Phoenix Bright", + "Pippa Pepper", + "Pippa Pizazz", + "Polly Tickle", + "Poppy Corn", + "Poppy Cox", + "Poppy Domm", + "Poppy Larr", + "Poppy Lerry", + "Poppy Sickles", + "Portia Bella", + "Portia Nette", + "Pria Steegious", + "Pria Steen", + "Prissa Teen", + "Raye Bitt", + "Raye Diante", + "Raye Nessance", + "Raye Storm", + "Remi Nissent", + "Rey Mantique", + "Rey Markeble", + "Rey Moorse", + "Rey Torric", + "Rococo Jazz", + "Roma Ence", + "Rose Budd", + "Ruby Redd", + "Ruby Ree", + "Ruth Lezz", + "Sall Laikeen", + "Sall Lay", + "Sally Ness", + "Sam Armie", + "Sam Ooth", + "Sara Castique", + "Sara Donique", + "Sara Penth", + "Sarah Pentine", + "Sarah Reen", + "Sasha Sass", + "Satty Phection", + "Sella Fish", + "Sella Stice", + "Selly Foxx", + "Senna Guinne", + "Senna Seer", + "Shia Mirring", + "Sia Dellic", + "Sia Dowe", + "Siam Pathy", + "Silver Foxx", + "Siri Price", + "Sofie Moore", + "Sofie Stication", + "Su Blime", + "Sue Burben", + "Sue Missif", + "Sue Pernova", + "Sue Preem", + "Super Nova", + "Suse Pense", + "Suzu Blime", + "Temma Tation", + "Tempest Wilde", + "Terra Gique", + "Thea Terre", + "Tina Cious", + "Tina Scious", + "Tira Mendus", + "Tira Quoise", + "Trinity Quart", + "Trixie Foxx", + "Tye Gress", + "Tye Phun", + "Vall Canno", + "Vall Iant", + "Vall Orous", + "Vanity Fairchild", + "Vicki Tory", + "Vivi Venus", + "Vivian Foxx", + "Vye Vacius", + "Zahara Dessert" +]
\ No newline at end of file diff --git a/bot/resources/pride/facts.json b/bot/resources/pride/facts.json new file mode 100644 index 00000000..f6597201 --- /dev/null +++ b/bot/resources/pride/facts.json @@ -0,0 +1,34 @@ +{ + "2020": [ + "No research has conclusively proven what causes homosexuality, heterosexuality, or bisexuality.", + "Records of same-sex relationships have been found in nearly every culture throughout history with varying degrees of acceptance.", + "Various slurs targeting queer people have been reappropriated by them, notably \"dyke\", and \"queer\".", + "Historians note that in some cultures, some homosexual behavior was not viewed as effeminate, but as evidence of a man's masculinity. Examples include the Celtic and Greek cultures.", + "Over time, the proportion of people who identify as homosexual or bisexual appears to be increasing. It is not know if this is because they feel it is safer to come out, or if the actual numbers of homosexual/bisexual people are increasing.", + "A large proportion of people, both in and out of the LGBTQ+ communities, do not believe bisexuality exists. This is known as bisexual erasure.", + "Queer people commit suicide are much more common in politically conservative regions, and also more common than non-queer people in general.", + "October 8th is lesbian pride day!", + "Stormé DeLarverie, a lesbian drag king, had a \"scuffle\" with the police which many claim is what kicked off the Stonewall Riots.", + "Gilbert Baker, also known as the “Gay Betsy Ross,” designed the rainbow flag, or Pride Flag, in San Francisco in 1978.", + "The rainbow pride flag is well-known, but there are flags for most labeled gender/sexual minorities.", + "In 1968, Dr. John Money performed the first complete male-to-female sex-change operation in the United States at Johns Hopkins University.", + "At the age of 24, Leonardo Da Vinci was arrested for having sex with a man. He was eventually acquitted.", + "Alfred Kinsey, the creator of the Kinsey scale, is known as \"the father of the sexual revolution\". The Kinsey scale was created in order to demonstrate that sexuality does not fit into two strict categories: homosexual and heterosexual. Instead, Kinsey believed that sexuality is fluid and subject to change over time.", + "The Kinsey scale ranges from 0, which is exclusively heterosexual, to 6, which is exclusively homosexual.", + "November 20th is the Transgender Day of Remembrance, which is a day to memorialize those who have been murdered as a result of transphobia.", + "The pink triangle was the symbol that queer people were required to wear in Nazi concentration camps during WWII. The symbol has since been reclaimed as a positive symbol of self-identity.", + "The term \"AC/DC\" has been used to refer to bisexuals.", + "September 23rd is bisexual pride day!", + "Pride Day refers to celebrations that typically take place in June that commemorate the Stonewall Inn riots of June 28, 1969. These riots are considered the birth of the modern gay civil rights movement.", + "A \"beard\" is someone of the opposite sex who knowingly dates a closeted lesbian or gay man to provide that person with a heterosexual \"disguise\", usually for family or career purposes.", + "In Nigeria, where homosexuality is punishable by death by stoning, a post-grad student claimed he had proved being gay was wrong by using magnets. He hoped to win a Nobel Prize for his research. He has not received one.", + "In 1982, the Gay Related Immune Disorder (GRID) was renamed Acquired Immune Deficiency Syndrome (AIDS).", + "The word \"lesbian\" is derived from the Greek island Lesbos, home of Greek poet Sappho. Her poetry proclaimed her love for women, and their beauty.", + "Nearly all bonobos (a kind of chimpanzee) appear to be bisexual.", + "Homosexual behavior has been observed in 1,500 animal species and is most widespread among animals with a complex herd life.", + "Many queer people identify their sexual orientation independently from their romantic orientation. For instance, it is possible to be sexually attracted to both women and men, but only be romantically attracted to one of them.", + "In 2005, Swedish researchers found that when straight men smelled a female urine compound, their hypothalamus lit up in brain images. In gay men, it did not. Instead, homosexual men's hypothalamus lit up when they smelled the male-sweat compound, which was the same way straight women responded.", + "As of 2019-10-02, there are 17 states in the United States of America where queer people can be fired for being queer. In most other states, there is minimal protection offered, often only for public employees.", + "In 1985, an official Star Trek novel was published with scenes depicting Kirk and Spock as lovers. These parts were largely removed, which made the original into a collector's item." + ] +}
\ No newline at end of file diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index 6609387e..513c1020 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -126,7 +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 + @override_in_channel() async def adventofcode_group(self, ctx: commands.Context) -> None: """All of the Advent of Code commands.""" await ctx.send_help(ctx.command) diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index 0ba74d9c..438ab475 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -16,7 +16,7 @@ class Issues(commands.Cog): self.bot = bot @commands.command(aliases=("issues",)) - @override_in_channel + @override_in_channel() async def issue( self, ctx: commands.Context, number: int, repository: str = "seasonalbot", user: str = "python-discord" ) -> None: diff --git a/bot/seasons/evergreen/trivia_quiz.py b/bot/seasons/evergreen/trivia_quiz.py new file mode 100644 index 00000000..798523e6 --- /dev/null +++ b/bot/seasons/evergreen/trivia_quiz.py @@ -0,0 +1,234 @@ +import asyncio +import json +import logging +import random +from pathlib import Path + +import discord +from discord.ext import commands +from fuzzywuzzy import fuzz + +from bot.constants import Roles + + +logger = logging.getLogger(__name__) + + +ANNOYED_EXPRESSIONS = ["-_-", "-.-"] + +WRONG_ANS_RESPONSE = [ + "No one gave the correct answer", + "Better luck next time" +] + + +class TriviaQuiz(commands.Cog): + """A cog for all quiz commands.""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.questions = self.load_questions() + self.game_status = {} + self.game_owners = {} + self.question_limit = 4 + self.player_dict = {} + self.categories = { + "general": "Test your general knowledge" + # "retro": "Questions related to retro gaming." + } + + @staticmethod + def load_questions() -> dict: + """Load the questions from json file.""" + p = Path("bot", "resources", "evergreen", "trivia_quiz.json") + with p.open() as json_data: + questions = json.load(json_data) + return questions + + @commands.command(name="quiz", aliases=["trivia"]) + async def quiz_game(self, ctx: commands.Context, category: str = "general") -> None: + """ + Start/Stop a quiz! + + arguments: + option: + - start : to start a quiz in a channel + - stop : stop the quiz running in that channel. + + Questions for the quiz can be selected from the following categories: + - general : Test your general knowledge. (default) + (we wil be adding more later) + """ + category = category.lower() + + if ctx.channel.id not in self.game_status: + self.game_status[ctx.channel.id] = False + self.player_dict[ctx.channel.id] = {} + + if not self.game_status[ctx.channel.id]: + self.game_owners[ctx.channel.id] = ctx.author + self.game_status[ctx.channel.id] = True + start_embed = discord.Embed(colour=discord.Colour.red()) + start_embed.title = "Quiz game Starting!!" + start_embed.description = "Each game consists of 5 questions.\n" + start_embed.description += "**Rules :**\nNo cheating and have fun!" + start_embed.set_footer( + text="Points for a question reduces by 25 after 10s or after a hint. Total time is 30s per question" + ) + await ctx.send(embed=start_embed) # send an embed with the rules + await asyncio.sleep(1) + + else: + if ( + ctx.author == self.game_owners[ctx.channel.id] + or Roles.moderator in [role.id for role in ctx.author.roles] + ): + await ctx.send("Quiz is no longer running.") + await self.declare_winner(ctx.channel, self.player_dict[ctx.channel.id]) + self.game_status[ctx.channel.id] = False + del self.game_owners[ctx.channel.id] + else: + await ctx.send(f"{ctx.author.mention}, you are not authorised to stop this game :ghost: !") + + if category not in self.categories: + embed = self.category_embed + await ctx.send(embed=embed) + return + topic = self.questions[category] + + unanswered = 0 + done_question = [] + hint_no = 0 + answer = None + while self.game_status[ctx.channel.id]: + if len(done_question) > self.question_limit and hint_no == 0: + await ctx.send("The round ends here.") + await self.declare_winner(ctx.channel, self.player_dict[ctx.channel.id]) + break + if unanswered > 3: + await ctx.send("Game stopped due to inactivity.") + await self.declare_winner(ctx.channel, self.player_dict[ctx.channel.id]) + break + if hint_no == 0: + while True: + question_dict = random.choice(topic) + if question_dict["id"] not in done_question: + done_question.append(question_dict["id"]) + break + q = question_dict["question"] + answer = question_dict["answer"] + + embed = discord.Embed(colour=discord.Colour.gold()) + embed.title = f"Question #{len(done_question)}" + embed.description = q + await ctx.send(embed=embed) + + def check(m: discord.Message) -> bool: + ratio = fuzz.ratio(answer.lower(), m.content.lower()) + return ratio > 85 and m.channel == ctx.channel + try: + msg = await self.bot.wait_for('message', check=check, timeout=10) + except asyncio.TimeoutError: + if self.game_status[ctx.channel.id] is False: + break + if hint_no < 2: + hint_no += 1 + if "hints" in question_dict: + hints = question_dict["hints"] + await ctx.send(f"**Hint #{hint_no+1}\n**{hints[hint_no]}") + else: + await ctx.send(f"Cmon guys, {30-hint_no*10}s left!") + + else: + response = random.choice(WRONG_ANS_RESPONSE) + expression = random.choice(ANNOYED_EXPRESSIONS) + await ctx.send(f"{response} {expression}") + await self.send_answer(ctx.channel, question_dict) + await asyncio.sleep(1) + hint_no = 0 + unanswered += 1 + await self.send_score(ctx.channel, self.player_dict[ctx.channel.id]) + await asyncio.sleep(2) + + else: + points = 100 - 25*hint_no + if msg.author in self.player_dict[ctx.channel.id]: + self.player_dict[ctx.channel.id][msg.author] += points + else: + self.player_dict[ctx.channel.id][msg.author] = points + hint_no = 0 + unanswered = 0 + await ctx.send(f"{msg.author.mention} got the correct answer :tada: {points} points for ya.") + await self.send_answer(ctx.channel, question_dict) + await self.send_score(ctx.channel, self.player_dict[ctx.channel.id]) + await asyncio.sleep(2) + + @staticmethod + async def send_score(channel: discord.TextChannel, player_data: dict) -> None: + """A function which sends the score.""" + embed = discord.Embed(colour=discord.Colour.blue()) + embed.title = "Score Board" + embed.description = "" + for k, v in player_data.items(): + embed.description += f"{k} : {v}\n" + await channel.send(embed=embed) + + @staticmethod + async def declare_winner(channel: discord.TextChannel, player_data: dict) -> None: + """Announce the winner of the quiz in the game channel.""" + if player_data: + highest_points = max(list(player_data.values())) + no_of_winners = list(player_data.values()).count(highest_points) + + # Check if more than 1 player has highest points. + if no_of_winners > 1: + word = "You guys" + winners = [] + points_copy = list(player_data.values()).copy() + for _ in range(no_of_winners): + index = points_copy.index(highest_points) + winners.append(list(player_data.keys())[index]) + points_copy[index] = 0 + winners_mention = None + for winner in winners: + winners_mention += f"{winner.mention} " + + else: + word = "You" + author_index = list(player_data.values()).index(highest_points) + winner = list(player_data.keys())[author_index] + winners_mention = winner.mention + await channel.send( + f"Congratz {winners_mention} :tada: " + f"{word} have won this quiz game with a grand total of {highest_points} points!!" + ) + + @property + def category_embed(self) -> discord.Embed: + """Build an embed showing all available trivia categories.""" + embed = discord.Embed(colour=discord.Colour.blue()) + embed.title = "The available question categories are:" + embed.description = "" + for cat, description in self.categories.items(): + embed.description += f"**- {cat.capitalize()}**\n{description.capitalize()}\n" + embed.set_footer(text="If not category is chosen, then a random one will be selected.") + return embed + + @staticmethod + async def send_answer(channel: discord.TextChannel, question_dict: dict) -> None: + """Send the correct answer of a question to the game channel.""" + answer = question_dict["answer"] + info = question_dict["info"] + embed = discord.Embed(color=discord.Colour.red()) + embed.title = f"The correct answer is **{answer}**\n" + embed.description = "" + if info != "": + embed.description += f"**Information**\n{info}\n\n" + embed.description += "Lets move to the next question.\nRemaining questions: " + await channel.send(embed=embed) + + +def setup(bot: commands.Bot) -> None: + """Loading the cog.""" + bot.add_cog(TriviaQuiz(bot)) + logger.debug("TriviaQuiz cog loaded") diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index 20797037..9ad44e3f 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -10,12 +10,16 @@ import aiohttp import discord from discord.ext import commands +from bot.constants import Channels, WHITELISTED_CHANNELS +from bot.decorators import override_in_channel from bot.utils.persist import make_persistent + 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 +HACKTOBER_WHITELIST = WHITELISTED_CHANNELS + (Channels.hacktoberfest_2019,) class HacktoberStats(commands.Cog): @@ -27,6 +31,7 @@ class HacktoberStats(commands.Cog): self.linked_accounts = self.load_linked_users() @commands.group(name="hacktoberstats", aliases=("hackstats",), invoke_without_command=True) + @override_in_channel(HACKTOBER_WHITELIST) async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None) -> None: """ Display an embed for a user's Hacktoberfest contributions. @@ -220,7 +225,7 @@ class HacktoberStats(commands.Cog): not_label = "invalid" action_type = "pr" is_query = f"public+author:{github_username}" - date_range = f"{CURRENT_YEAR}-10-01..{CURRENT_YEAR}-10-31" + date_range = f"{CURRENT_YEAR}-10-01T00:00:00%2B14:00..{CURRENT_YEAR}-10-31T23:59:59-11:00" per_page = "300" query_url = ( f"{base_url}" @@ -231,7 +236,7 @@ class HacktoberStats(commands.Cog): f"&per_page={per_page}" ) - headers = {"user-agent": "Discord Python Hactoberbot"} + headers = {"user-agent": "Discord Python Hacktoberbot"} async with aiohttp.ClientSession() as session: async with session.get(query_url, headers=headers) as resp: jsonresp = await resp.json() diff --git a/bot/seasons/pride/drag_queen_name.py b/bot/seasons/pride/drag_queen_name.py new file mode 100644 index 00000000..43813fbd --- /dev/null +++ b/bot/seasons/pride/drag_queen_name.py @@ -0,0 +1,33 @@ +import json +import logging +import random +from pathlib import Path + +from discord.ext import commands + +log = logging.getLogger(__name__) + + +class DragNames(commands.Cog): + """Gives a random drag queen name!""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + self.names = self.load_names() + + @staticmethod + def load_names() -> list: + """Loads a list of drag queen names.""" + with open(Path("bot/resources/pride/drag_queen_names.json"), "r", encoding="utf-8") as f: + return json.load(f) + + @commands.command(name="dragname", aliases=["dragqueenname", "queenme"]) + async def dragname(self, ctx: commands.Context) -> None: + """Sends a message with a drag queen name.""" + await ctx.send(random.choice(self.names)) + + +def setup(bot: commands.Bot) -> None: + """Cog loader for drag queen name generator.""" + bot.add_cog(DragNames(bot)) + log.info("Drag queen name generator cog loaded!") diff --git a/bot/seasons/pride/pride_facts.py b/bot/seasons/pride/pride_facts.py new file mode 100644 index 00000000..b705bfb4 --- /dev/null +++ b/bot/seasons/pride/pride_facts.py @@ -0,0 +1,106 @@ +import asyncio +import json +import logging +import random +from datetime import datetime +from pathlib import Path +from typing import Union + +import dateutil.parser +import discord +from discord.ext import commands + +from bot.constants import Channels +from bot.constants import Colours + +log = logging.getLogger(__name__) + +Sendable = Union[commands.Context, discord.TextChannel] + + +class PrideFacts(commands.Cog): + """Provides a new fact every day during the Pride season!""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + self.facts = self.load_facts() + + @staticmethod + def load_facts() -> dict: + """Loads a dictionary of years mapping to lists of facts.""" + with open(Path("bot/resources/pride/facts.json"), "r", encoding="utf-8") as f: + return json.load(f) + + async def send_pride_fact_daily(self) -> None: + """Background task to post the daily pride fact every day.""" + channel = self.bot.get_channel(Channels.seasonalbot_chat) + while True: + await self.send_select_fact(channel, datetime.utcnow()) + await asyncio.sleep(24 * 60 * 60) + + async def send_random_fact(self, ctx: commands.Context) -> None: + """Provides a fact from any previous day, or today.""" + now = datetime.utcnow() + previous_years_facts = (self.facts[x] for x in self.facts.keys() if int(x) < now.year) + current_year_facts = self.facts.get(str(now.year), [])[:now.day] + previous_facts = current_year_facts + [x for y in previous_years_facts for x in y] + try: + await ctx.send(embed=self.make_embed(random.choice(previous_facts))) + except IndexError: + await ctx.send("No facts available") + + async def send_select_fact(self, target: Sendable, _date: Union[str, datetime]) -> None: + """Provides the fact for the specified day, if the day is today, or is in the past.""" + now = datetime.utcnow() + if isinstance(_date, str): + try: + date = dateutil.parser.parse(_date, dayfirst=False, yearfirst=False, fuzzy=True) + except (ValueError, OverflowError) as err: + await target.send(f"Error parsing date: {err}") + return + else: + date = _date + if date.year < now.year or (date.year == now.year and date.day <= now.day): + try: + await target.send(embed=self.make_embed(self.facts[str(date.year)][date.day - 1])) + except KeyError: + await target.send(f"The year {date.year} is not yet supported") + return + except IndexError: + await target.send(f"Day {date.day} of {date.year} is not yet support") + return + else: + await target.send("The fact for the selected day is not yet available.") + + @commands.command(name="pridefact", aliases=["pridefacts"]) + async def pridefact(self, ctx: commands.Context) -> None: + """ + Sends a message with a pride fact of the day. + + If "random" is given as an argument, a random previous fact will be provided. + + If a date is given as an argument, and the date is in the past, the fact from that day + will be provided. + """ + message_body = ctx.message.content[len(ctx.invoked_with) + 2:] + if message_body == "": + await self.send_select_fact(ctx, datetime.utcnow()) + elif message_body.lower().startswith("rand"): + await self.send_random_fact(ctx) + else: + await self.send_select_fact(ctx, message_body) + + def make_embed(self, fact: str) -> discord.Embed: + """Makes a nice embed for the fact to be sent.""" + return discord.Embed( + colour=Colours.pink, + title="Pride Fact!", + description=fact + ) + + +def setup(bot: commands.Bot) -> None: + """Cog loader for pride facts.""" + bot.loop.create_task(PrideFacts(bot).send_pride_fact_daily()) + bot.add_cog(PrideFacts(bot)) + log.info("Pride facts cog loaded!") |