From d3690c4056826d963c4a25b8fc7f5704aa8e93e1 Mon Sep 17 00:00:00 2001 From: Hambira Date: Sat, 30 May 2020 22:36:45 +0530 Subject: "adding xkcd feature" --- bot/exts/evergreen/xkcd.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 bot/exts/evergreen/xkcd.py (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py new file mode 100644 index 00000000..11477216 --- /dev/null +++ b/bot/exts/evergreen/xkcd.py @@ -0,0 +1,72 @@ +import logging +import random + +import discord +from discord.ext import commands + +log = logging.getLogger(__name__) + + +class XKCD(commands.Cog): + """A cog for posting the XKCD .""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command(name="xkcd") + async def fetch_xkcd_comics(self, ctx: commands.Context, comic: str = "latest") -> None: + """Read your Fav XKCD comics.""" + if comic not in ["random", "latest"]: + url = f"https://xkcd.com/{comic}/info.0.json" + else: + url = "https://xkcd.com/info.0.json" + + # ---- random choice ----- + if comic == "random": + async with self.bot.http_session.get(url) as r: + json_data = await r.json() + random_pick = random.randint(1, int(json_data["num"])) + url = f"https://xkcd.com/{random_pick}/info.0.json" + + log.trace(f"Querying xkcd API: {url}") + async with self.bot.http_session.get(url) as r: + if r.status == "200": + json_data = await r.json() + else: + # ----- Exception handling | Guides to use ------ + log.warning(f"Received response {r.status} from: {url}") + # -- get the latest comic number --- + url = f"https://xkcd.com/info.0.json" + async with self.bot.http_session.get(url) as r: + latest_data = await r.json() + + # --- beautify response --- + latest_num = latest_data["num"] + resp = discord.Embed( + title="Guides | Usage", + description=f''' + .xkcd latest (Retrieves the latest comic) + .xkcd random (Retrieves random comic) + .xkcd number (Enter a comic number between 1 & {latest_num}) + ''' + ) + return await ctx.send(embed=resp) + + # --- response variables ---- + day, month, year = json_data["day"], json_data["month"], json_data["year"] + comic_number = json_data["num"] + + # ---- beautify response ---- + embed = discord.Embed( + title=json_data['title'], + description=json_data["alt"] + ) + embed.set_image(url=json_data['img']) + embed.set_footer(text=f"Post date : {day}-{month}-{year} | xkcd comics - {comic_number}") + + await ctx.send(embed=embed) + + +def setup(bot: commands.Bot) -> None: + """XKCD Cog load.""" + bot.add_cog(XKCD(bot)) -- cgit v1.2.3 From fcad0c8421a185ea696d02bf92d78fb7e21fc63e Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Thu, 22 Oct 2020 07:09:17 +0000 Subject: improve message when user not found --- bot/exts/halloween/hacktoberstats.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 94bfe138..e8ed1d1b 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -4,7 +4,7 @@ import re from collections import Counter from datetime import datetime, timedelta from pathlib import Path -from typing import List, Tuple, Union +from typing import List, Optional, Tuple, Union import aiohttp import discord @@ -179,11 +179,15 @@ class HacktoberStats(commands.Cog): async with ctx.typing(): prs = await self.get_october_prs(github_username) + if isinstance(prs, str): # it will be a string if user not found or no october prs found + await ctx.send(prs) + return + if prs: stats_embed = await self.build_embed(github_username, prs) await ctx.send('Here are some stats!', embed=stats_embed) else: - await ctx.send(f"No valid October GitHub contributions found for '{github_username}'") + await ctx.send(f"No valid hacktoberfest contributions found for '{github_username}'") async def build_embed(self, github_username: str, prs: List[dict]) -> discord.Embed: """Return a stats embed built from github_username's PRs.""" @@ -232,7 +236,7 @@ class HacktoberStats(commands.Cog): return stats_embed @staticmethod - async def get_october_prs(github_username: str) -> Union[List[dict], None]: + async def get_october_prs(github_username: str) -> Optional[Union[List[dict], str]]: """ Query GitHub's API for PRs created during the month of October by github_username. @@ -279,15 +283,18 @@ class HacktoberStats(commands.Cog): # Ignore logging non-existent users or users we do not have permission to see if api_message == GITHUB_NONEXISTENT_USER_MESSAGE: - logging.debug(f"No GitHub user found named '{github_username}'") + message = f"No GitHub user found named '{github_username}'" + logging.debug(message) else: logging.error(f"GitHub API request for '{github_username}' failed with message: {api_message}") - return + message = None + return message if jsonresp["total_count"] == 0: # Short circuit if there aren't any PRs - logging.info(f"No Hacktoberfest PRs found for GitHub user: '{github_username}'") - return + message = f"No october PRs found for GitHub user: '{github_username}'" + logging.info(message) + return message logging.info(f"Found {len(jsonresp['items'])} Hacktoberfest PRs for GitHub user: '{github_username}'") outlist = [] # list of pr information dicts that will get returned -- cgit v1.2.3 From df9d809c223d01b4e7bb056468fed7eb5e5989d1 Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Thu, 22 Oct 2020 07:49:11 +0000 Subject: improve implementation with return value instead of string/None now its empty list/None --- bot/exts/halloween/hacktoberstats.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 9ae9c227..9fb71651 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -179,8 +179,8 @@ class HacktoberStats(commands.Cog): async with ctx.typing(): prs = await self.get_october_prs(github_username) - if isinstance(prs, str): # it will be a string if user not found or no october prs found - await ctx.send(prs) + if prs is None: # it will be a None if user not found + await ctx.send("GitHub user not found: " + github_username) return if prs: @@ -236,7 +236,7 @@ class HacktoberStats(commands.Cog): return stats_embed @staticmethod - async def get_october_prs(github_username: str) -> Optional[Union[List[dict], str]]: + async def get_october_prs(github_username: str) -> Optional[List[dict]]: """ Query GitHub's API for PRs created during the month of October by github_username. @@ -256,7 +256,8 @@ class HacktoberStats(commands.Cog): "number": int } - Otherwise, return None + Otherwise, return empty list + None will be returned when github user not found """ logging.info(f"Fetching Hacktoberfest Stats for GitHub user: '{github_username}'") base_url = "https://api.github.com/search/issues?q=" @@ -283,18 +284,16 @@ class HacktoberStats(commands.Cog): # Ignore logging non-existent users or users we do not have permission to see if api_message == GITHUB_NONEXISTENT_USER_MESSAGE: - message = f"No GitHub user found named '{github_username}'" - logging.debug(message) + logging.debug(f"No GitHub user found named '{github_username}'") + return else: logging.error(f"GitHub API request for '{github_username}' failed with message: {api_message}") - message = None - return message + return [] # not returning None here, because that should be for when user not found if jsonresp["total_count"] == 0: # Short circuit if there aren't any PRs - message = f"No october PRs found for GitHub user: '{github_username}'" - logging.info(message) - return message + logging.info(f"No october PRs found for GitHub user: '{github_username}'") + return [] logging.info(f"Found {len(jsonresp['items'])} Hacktoberfest PRs for GitHub user: '{github_username}'") outlist = [] # list of pr information dicts that will get returned @@ -340,7 +339,7 @@ class HacktoberStats(commands.Cog): jsonresp2 = await HacktoberStats._fetch_url(topics_query_url, GITHUB_TOPICS_ACCEPT_HEADER) if jsonresp2.get("names") is None: logging.error(f"Error fetching topics for {shortname}: {jsonresp2['message']}") - return + return [] # PRs after oct 3 that doesn't have 'hacktoberfest-accepted' label # must be in repo with 'hacktoberfest' topic -- cgit v1.2.3 From 664c64ffb9b163e663740fd9fe56fc977fb1c36d Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Thu, 22 Oct 2020 07:56:51 +0000 Subject: better no-prs message --- bot/exts/halloween/hacktoberstats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 9fb71651..1640e6cf 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -187,7 +187,7 @@ class HacktoberStats(commands.Cog): stats_embed = await self.build_embed(github_username, prs) await ctx.send('Here are some stats!', embed=stats_embed) else: - await ctx.send(f"No valid hacktoberfest contributions found for '{github_username}'") + await ctx.send(f"No valid hacktoberfest PRs found for '{github_username}'") async def build_embed(self, github_username: str, prs: List[dict]) -> discord.Embed: """Return a stats embed built from github_username's PRs.""" -- cgit v1.2.3 From 2b379c2f8f352caf694913c3cb298599463c87c0 Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Sat, 31 Oct 2020 05:18:21 +0000 Subject: Fix capitalization and grammar For Hacktoberstats --- bot/exts/halloween/hacktoberstats.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 1640e6cf..0f6b9924 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -179,7 +179,7 @@ class HacktoberStats(commands.Cog): async with ctx.typing(): prs = await self.get_october_prs(github_username) - if prs is None: # it will be a None if user not found + if prs is None: # Will be None if the user was not found await ctx.send("GitHub user not found: " + github_username) return @@ -187,14 +187,14 @@ class HacktoberStats(commands.Cog): stats_embed = await self.build_embed(github_username, prs) await ctx.send('Here are some stats!', embed=stats_embed) else: - await ctx.send(f"No valid hacktoberfest PRs found for '{github_username}'") + await ctx.send(f"No valid Hacktoberfest PRs found for '{github_username}'") async 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}'") in_review, accepted = await self._categorize_prs(prs) - n = len(accepted) + len(in_review) # total number of PRs + n = len(accepted) + len(in_review) # Total number of PRs if n >= PRS_FOR_SHIRT: shirtstr = f"**{github_username} is eligible for a T-shirt or a tree!**" elif n == PRS_FOR_SHIRT - 1: @@ -220,7 +220,7 @@ class HacktoberStats(commands.Cog): icon_url="https://avatars1.githubusercontent.com/u/35706162?s=200&v=4" ) - # this will handle when no PRs in_review or accepted + # This will handle when no PRs in_review or accepted review_str = self._build_prs_string(in_review, github_username) or "None" accepted_str = self._build_prs_string(accepted, github_username) or "None" stats_embed.add_field( @@ -257,7 +257,7 @@ class HacktoberStats(commands.Cog): } Otherwise, return empty list - None will be returned when github user not found + None will be returned when the GitHub user was not found """ logging.info(f"Fetching Hacktoberfest Stats for GitHub user: '{github_username}'") base_url = "https://api.github.com/search/issues?q=" @@ -288,11 +288,11 @@ class HacktoberStats(commands.Cog): return else: logging.error(f"GitHub API request for '{github_username}' failed with message: {api_message}") - return [] # not returning None here, because that should be for when user not found + return [] # No October PRs were found due to error if jsonresp["total_count"] == 0: # Short circuit if there aren't any PRs - logging.info(f"No october PRs found for GitHub user: '{github_username}'") + logging.info(f"No October PRs found for GitHub user: '{github_username}'") return [] logging.info(f"Found {len(jsonresp['items'])} Hacktoberfest PRs for GitHub user: '{github_username}'") @@ -310,7 +310,7 @@ class HacktoberStats(commands.Cog): "number": item["number"] } - # if the PR has 'invalid' or 'spam' labels, the PR must be + # If the PR has 'invalid' or 'spam' labels, the PR must be # either merged or approved for it to be included if HacktoberStats._has_label(item, ["invalid", "spam"]): if not await HacktoberStats._is_accepted(itemdict): @@ -323,17 +323,17 @@ class HacktoberStats(commands.Cog): outlist.append(itemdict) continue - # checking PR's labels for "hacktoberfest-accepted" + # Checking PR's labels for "hacktoberfest-accepted" if HacktoberStats._has_label(item, "hacktoberfest-accepted"): outlist.append(itemdict) continue - # no need to query github if repo topics are fetched before already + # No need to query GitHub if repo topics are fetched before already if shortname in hackto_topics.keys(): if hackto_topics[shortname]: outlist.append(itemdict) continue - # fetch topics for the pr repo + # Fetch topics for the pr repo topics_query_url = f"https://api.github.com/repos/{shortname}/topics" logging.debug(f"Fetching repo topics for {shortname} with url: {topics_query_url}") jsonresp2 = await HacktoberStats._fetch_url(topics_query_url, GITHUB_TOPICS_ACCEPT_HEADER) @@ -344,7 +344,7 @@ class HacktoberStats(commands.Cog): # PRs after oct 3 that doesn't have 'hacktoberfest-accepted' label # must be in repo with 'hacktoberfest' topic if "hacktoberfest" in jsonresp2["names"]: - hackto_topics[shortname] = True # cache result in the dict for later use if needed + hackto_topics[shortname] = True # Cache result in the dict for later use if needed outlist.append(itemdict) return outlist -- cgit v1.2.3 From 147e37a53073b6e4291e1d386f8d2a6b0114f535 Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Sat, 31 Oct 2020 07:28:56 +0000 Subject: Put GitHub user-not-found message in embed With random `NEGATIVE_REPLIES` + color=red --- bot/exts/halloween/hacktoberstats.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 0f6b9924..bb5a6325 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -1,5 +1,6 @@ import json import logging +import random import re from collections import Counter from datetime import datetime, timedelta @@ -10,7 +11,7 @@ import aiohttp import discord from discord.ext import commands -from bot.constants import Channels, Month, Tokens, WHITELISTED_CHANNELS +from bot.constants import Channels, Month, NEGATIVE_REPLIES, Tokens, WHITELISTED_CHANNELS from bot.utils.decorators import in_month, override_in_channel from bot.utils.persist import make_persistent @@ -180,7 +181,9 @@ class HacktoberStats(commands.Cog): prs = await self.get_october_prs(github_username) if prs is None: # Will be None if the user was not found - await ctx.send("GitHub user not found: " + github_username) + await ctx.send(embed=discord.Embed(title=random.choice(NEGATIVE_REPLIES), + description=f"GitHub user `{github_username}` was not found.", + colour=discord.Colour.red())) return if prs: -- cgit v1.2.3 From 12a0832d9259b20eb622bcc4a0b5e2c2d766628a Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Sat, 31 Oct 2020 15:38:28 +0800 Subject: Fix capitalization of 'PR' in hacktoberstats.py --- bot/exts/halloween/hacktoberstats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index bb5a6325..a5ec4dc4 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -336,7 +336,7 @@ class HacktoberStats(commands.Cog): if hackto_topics[shortname]: outlist.append(itemdict) continue - # Fetch topics for the pr repo + # Fetch topics for the PR's repo topics_query_url = f"https://api.github.com/repos/{shortname}/topics" logging.debug(f"Fetching repo topics for {shortname} with url: {topics_query_url}") jsonresp2 = await HacktoberStats._fetch_url(topics_query_url, GITHUB_TOPICS_ACCEPT_HEADER) -- cgit v1.2.3 From 57800121c067d22396055e30e3e3a91899c9b4fa Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Sat, 31 Oct 2020 15:42:59 +0800 Subject: Continue loop if repo topics API request errored --- bot/exts/halloween/hacktoberstats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index a5ec4dc4..701ef0b3 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -342,7 +342,7 @@ class HacktoberStats(commands.Cog): jsonresp2 = await HacktoberStats._fetch_url(topics_query_url, GITHUB_TOPICS_ACCEPT_HEADER) if jsonresp2.get("names") is None: logging.error(f"Error fetching topics for {shortname}: {jsonresp2['message']}") - return [] + continue # Assume the repo doesn't have the `hacktoberfest` topic if API request errored # PRs after oct 3 that doesn't have 'hacktoberfest-accepted' label # must be in repo with 'hacktoberfest' topic -- cgit v1.2.3 From 4550d27308b17c6107f123469cf529c35318654a Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Thu, 3 Dec 2020 09:45:43 +0800 Subject: Remove unused pathlib.Path import --- bot/exts/halloween/hacktoberstats.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index ae949f53..a599af73 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -3,7 +3,6 @@ import random import re from collections import Counter from datetime import datetime, timedelta -from pathlib import Path from typing import List, Optional, Tuple, Union import aiohttp -- cgit v1.2.3 From 030a9996664cb6a52e027406a76d35c62e30a72a Mon Sep 17 00:00:00 2001 From: Hedy Li Date: Wed, 6 Jan 2021 17:42:37 +0800 Subject: Formatting and add full stop to docstring - bot/exts/halloween/hacktoberstats.py line 130, better readability - same file line 208-209 add full stop --- bot/exts/halloween/hacktoberstats.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index a599af73..a1c55922 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -127,9 +127,13 @@ class HacktoberStats(commands.Cog): prs = await self.get_october_prs(github_username) if prs is None: # Will be None if the user was not found - await ctx.send(embed=discord.Embed(title=random.choice(NEGATIVE_REPLIES), - description=f"GitHub user `{github_username}` was not found.", - colour=discord.Colour.red())) + await ctx.send( + embed=discord.Embed( + title=random.choice(NEGATIVE_REPLIES), + description=f"GitHub user `{github_username}` was not found.", + colour=discord.Colour.red() + ) + ) return if prs: @@ -205,8 +209,8 @@ class HacktoberStats(commands.Cog): "number": int } - Otherwise, return empty list - None will be returned when the GitHub user was not found + Otherwise, return empty list. + None will be returned when the GitHub user was not found. """ logging.info(f"Fetching Hacktoberfest Stats for GitHub user: '{github_username}'") base_url = "https://api.github.com/search/issues?q=" -- cgit v1.2.3 From 8e54fab377c7f798259bbf217afc8c3f68c9fb0f Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 8 Jan 2021 00:04:44 +0000 Subject: Get and renew V4 OAuth token --- bot/constants.py | 5 +++-- bot/exts/evergreen/game.py | 53 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 8 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index f6da272e..e638dfa1 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -174,7 +174,7 @@ class Emojis: christmas_tree = "\U0001F384" check = "\u2611" envelope = "\U0001F4E8" - trashcan = "<:trashcan:637136429717389331>" + trashcan = "<:trashcan:796854840293589003>" ok_hand = ":ok_hand:" dice_1 = "<:dice_1:755891608859443290>" @@ -257,7 +257,8 @@ class Tokens(NamedTuple): youtube = environ.get("YOUTUBE_API_KEY") tmdb = environ.get("TMDB_API_KEY") nasa = environ.get("NASA_API_KEY") - igdb = environ.get("IGDB_API_KEY") + igdb_client_id = environ.get("IGDB_CLIENT_ID") + igdb_client_secret = environ.get("IGDB_CLIENT_SECRET") github = environ.get("GITHUB_TOKEN") diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index d0fd7a40..f5707a22 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -2,7 +2,8 @@ import difflib import logging import random import re -from datetime import datetime as dt +from asyncio import sleep +from datetime import datetime as dt, timedelta from enum import IntEnum from typing import Any, Dict, List, Optional, Tuple @@ -17,10 +18,22 @@ from bot.utils.decorators import with_role from bot.utils.pagination import ImagePaginator, LinePaginator # Base URL of IGDB API -BASE_URL = "https://api-v3.igdb.com" +BASE_URL = "https://api.igdb.com/v4" + +CLIENT_ID = Tokens.igdb_client_id +CLIENT_SECRET = Tokens.igdb_client_secret + +# URL to request API access token +OAUTH_URL = "https://id.twitch.tv/oauth2/token" + +OAUTH_PARAMS = { + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "grant_type": "client_credentials" +} HEADERS = { - "user-key": Tokens.igdb, + "Client-ID": CLIENT_ID, "Accept": "application/json" } @@ -136,7 +149,32 @@ class Games(Cog): self.genres: Dict[str, int] = {} - self.refresh_genres_task.start() + self.bot.loop.create_task(self.renew_access_token()) + + # self.refresh_genres_task.start() + + async def renew_access_token(self) -> None: + """Refeshes V4 access token 2 days before expiry.""" + while True: + async with self.http_session.post(OAUTH_URL, params=OAUTH_PARAMS) as resp: + result = await resp.json() + if resp.status != 200: + logger.error( + "Failed to renew IGDB access token, unloading Games cog." + f"OAuth response message: {result['message']}" + ) + self.bot.remove_cog('Games') + return + + self.access_token = result["access_token"] + + # Set next renewal to 2 days before expire time + next_renewal = result["expires_in"] - 60*60*24*2 + + time_delta = timedelta(seconds=next_renewal) + logger.info(f"Successfully renewed access token. Refreshing again in {time_delta}") + + await sleep(next_renewal) @tasks.loop(hours=24.0) async def refresh_genres_task(self) -> None: @@ -418,7 +456,10 @@ class Games(Cog): def setup(bot: Bot) -> None: """Add/Load Games cog.""" # Check does IGDB API key exist, if not, log warning and don't load cog - if not Tokens.igdb: - logger.warning("No IGDB API key. Not loading Games cog.") + if not Tokens.igdb_client_id: + logger.warning("No IGDB client ID. Not loading Games cog.") + return + if not Tokens.igdb_client_secret: + logger.warning("No IGDB client secret. Not loading Games cog.") return bot.add_cog(Games(bot)) -- cgit v1.2.3 From 4505ba3b07c75f531bb6aea0f16737f92330f37e Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 8 Jan 2021 00:07:31 +0000 Subject: Revert change to trashcan emoji --- 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 e638dfa1..cc342f3d 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -174,7 +174,7 @@ class Emojis: christmas_tree = "\U0001F384" check = "\u2611" envelope = "\U0001F4E8" - trashcan = "<:trashcan:796854840293589003>" + trashcan = "<:trashcan:637136429717389331>" ok_hand = ":ok_hand:" dice_1 = "<:dice_1:755891608859443290>" -- cgit v1.2.3 From 0fcfb1459c258bae4bf2895a16559210e44c675c Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 8 Jan 2021 00:09:16 +0000 Subject: Un-comment out task start --- bot/exts/evergreen/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index f5707a22..fe5fc612 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -151,7 +151,7 @@ class Games(Cog): self.bot.loop.create_task(self.renew_access_token()) - # self.refresh_genres_task.start() + self.refresh_genres_task.start() async def renew_access_token(self) -> None: """Refeshes V4 access token 2 days before expiry.""" -- cgit v1.2.3 From 542cdc023629513c6d280e2bf96d5d554baa78d9 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 8 Jan 2021 16:43:39 +0000 Subject: Move renewal window to a const --- bot/exts/evergreen/game.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index fe5fc612..be4fcbc9 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -23,6 +23,9 @@ BASE_URL = "https://api.igdb.com/v4" CLIENT_ID = Tokens.igdb_client_id CLIENT_SECRET = Tokens.igdb_client_secret +# The number of seconds before expiry that we attempt to re-fetch a new access token +ACCESS_TOKEN_RENEWAL_WINDOW = 60*60*24*2 + # URL to request API access token OAUTH_URL = "https://id.twitch.tv/oauth2/token" @@ -168,8 +171,8 @@ class Games(Cog): self.access_token = result["access_token"] - # Set next renewal to 2 days before expire time - next_renewal = result["expires_in"] - 60*60*24*2 + # Attempt to renew before the token expires + next_renewal = result["expires_in"] - ACCESS_TOKEN_RENEWAL_WINDOW time_delta = timedelta(seconds=next_renewal) logger.info(f"Successfully renewed access token. Refreshing again in {time_delta}") -- cgit v1.2.3 From 4d68fb6e1be9fd7d1287e2f1650f88b1e447f69d Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 8 Jan 2021 18:40:50 +0000 Subject: Use current token if we can't get a new one --- bot/exts/evergreen/game.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index be4fcbc9..2abd7555 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -35,7 +35,7 @@ OAUTH_PARAMS = { "grant_type": "client_credentials" } -HEADERS = { +BASE_HEADERS = { "Client-ID": CLIENT_ID, "Accept": "application/json" } @@ -151,25 +151,34 @@ class Games(Cog): self.http_session: ClientSession = bot.http_session self.genres: Dict[str, int] = {} + self.headers = BASE_HEADERS self.bot.loop.create_task(self.renew_access_token()) self.refresh_genres_task.start() async def renew_access_token(self) -> None: - """Refeshes V4 access token 2 days before expiry.""" + """Refeshes V4 access token a number of seconds before expiry. See `ACCESS_TOKEN_RENEWAL_WINDOW`.""" while True: async with self.http_session.post(OAUTH_URL, params=OAUTH_PARAMS) as resp: result = await resp.json() if resp.status != 200: - logger.error( - "Failed to renew IGDB access token, unloading Games cog." - f"OAuth response message: {result['message']}" - ) - self.bot.remove_cog('Games') + # If there is a valid access token continue to use that, + # otherwise unload cog. + if "access_token" in self.headers: + time_delta = timedelta(seconds=ACCESS_TOKEN_RENEWAL_WINDOW) + logger.error( + "Failed to renew IGDB access token. " + f"Current token will last for {time_delta} " + f"OAuth response message: {result['message']}" + ) + else: + logger.warning("Invalid OAuth credentials. Unloading Games cog.") + self.bot.remove_cog('Games') + return - self.access_token = result["access_token"] + self.headers["access_token"] = result["access_token"] # Attempt to renew before the token expires next_renewal = result["expires_in"] - ACCESS_TOKEN_RENEWAL_WINDOW @@ -197,7 +206,7 @@ class Games(Cog): async def _get_genres(self) -> None: """Create genres variable for games command.""" body = "fields name; limit 100;" - async with self.http_session.get(f"{BASE_URL}/genres", data=body, headers=HEADERS) as resp: + async with self.http_session.get(f"{BASE_URL}/genres", data=body, headers=self.headers) as resp: result = await resp.json() genres = {genre["name"].capitalize(): genre["id"] for genre in result} @@ -347,7 +356,7 @@ class Games(Cog): body = GAMES_LIST_BODY.format(**params) # Do request to IGDB API, create headers, URL, define body, return result - async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: + async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=self.headers) as resp: return await resp.json() async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: @@ -389,7 +398,7 @@ class Games(Cog): # Define request body of IGDB API request and do request body = SEARCH_BODY.format(**{"term": search_term}) - async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: + async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=self.headers) as resp: data = await resp.json() # Loop over games, format them to good format, make line and append this to total lines @@ -418,7 +427,7 @@ class Games(Cog): "offset": offset }) - async with self.http_session.get(url=f"{BASE_URL}/companies", data=body, headers=HEADERS) as resp: + async with self.http_session.get(url=f"{BASE_URL}/companies", data=body, headers=self.headers) as resp: return await resp.json() async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: -- cgit v1.2.3 From 3ba700e7b7c211d69193d3ba3d3e02927b448441 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 8 Jan 2021 20:44:43 +0000 Subject: Switch to post requests and start task at right time. --- bot/exts/evergreen/game.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index 2abd7555..680724c2 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -155,8 +155,6 @@ class Games(Cog): self.bot.loop.create_task(self.renew_access_token()) - self.refresh_genres_task.start() - async def renew_access_token(self) -> None: """Refeshes V4 access token a number of seconds before expiry. See `ACCESS_TOKEN_RENEWAL_WINDOW`.""" while True: @@ -165,7 +163,7 @@ class Games(Cog): if resp.status != 200: # If there is a valid access token continue to use that, # otherwise unload cog. - if "access_token" in self.headers: + if "Authorization" in self.headers: time_delta = timedelta(seconds=ACCESS_TOKEN_RENEWAL_WINDOW) logger.error( "Failed to renew IGDB access token. " @@ -178,7 +176,7 @@ class Games(Cog): return - self.headers["access_token"] = result["access_token"] + self.headers["Authorization"] = f"Bearer {result['access_token']}" # Attempt to renew before the token expires next_renewal = result["expires_in"] - ACCESS_TOKEN_RENEWAL_WINDOW @@ -186,6 +184,10 @@ class Games(Cog): time_delta = timedelta(seconds=next_renewal) logger.info(f"Successfully renewed access token. Refreshing again in {time_delta}") + # This will be true the first time this loop runs. + # Since we now have an access token, its safe to start this task. + if self.genres == {}: + self.refresh_genres_task.start() await sleep(next_renewal) @tasks.loop(hours=24.0) @@ -206,9 +208,8 @@ class Games(Cog): async def _get_genres(self) -> None: """Create genres variable for games command.""" body = "fields name; limit 100;" - async with self.http_session.get(f"{BASE_URL}/genres", data=body, headers=self.headers) as resp: + async with self.http_session.post(f"{BASE_URL}/genres", data=body, headers=self.headers) as resp: result = await resp.json() - genres = {genre["name"].capitalize(): genre["id"] for genre in result} # Replace complex names with names from ALIASES @@ -356,7 +357,7 @@ class Games(Cog): body = GAMES_LIST_BODY.format(**params) # Do request to IGDB API, create headers, URL, define body, return result - async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=self.headers) as resp: + async with self.http_session.post(url=f"{BASE_URL}/games", data=body, headers=self.headers) as resp: return await resp.json() async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: @@ -398,7 +399,7 @@ class Games(Cog): # Define request body of IGDB API request and do request body = SEARCH_BODY.format(**{"term": search_term}) - async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=self.headers) as resp: + async with self.http_session.post(url=f"{BASE_URL}/games", data=body, headers=self.headers) as resp: data = await resp.json() # Loop over games, format them to good format, make line and append this to total lines @@ -427,7 +428,7 @@ class Games(Cog): "offset": offset }) - async with self.http_session.get(url=f"{BASE_URL}/companies", data=body, headers=self.headers) as resp: + async with self.http_session.post(url=f"{BASE_URL}/companies", data=body, headers=self.headers) as resp: return await resp.json() async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: -- cgit v1.2.3 From 92f1ee13d71fd4f72edc8e24488115a2294421f8 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 8 Jan 2021 20:57:57 +0000 Subject: Add OAuth response to warning. --- bot/exts/evergreen/game.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index 680724c2..d37be0e2 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -171,7 +171,10 @@ class Games(Cog): f"OAuth response message: {result['message']}" ) else: - logger.warning("Invalid OAuth credentials. Unloading Games cog.") + logger.warning( + "Invalid OAuth credentials. Unloading Games cog. " + f"OAuth response message: {result['message']}" + ) self.bot.remove_cog('Games') return -- cgit v1.2.3 From 03cb65d7b08210f43fe991196546fb1d61c7dca4 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 9 Jan 2021 20:05:58 +0000 Subject: Actually use 256 colours --- bot/exts/evergreen/8bitify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/8bitify.py b/bot/exts/evergreen/8bitify.py index c048d9bf..54e68f80 100644 --- a/bot/exts/evergreen/8bitify.py +++ b/bot/exts/evergreen/8bitify.py @@ -19,7 +19,7 @@ class EightBitify(commands.Cog): @staticmethod def quantize(image: Image) -> Image: """Reduces colour palette to 256 colours.""" - return image.quantize(colors=32) + return image.quantize() @commands.command(name="8bitify") async def eightbit_command(self, ctx: commands.Context) -> None: -- cgit v1.2.3 From a6013bd678ed0b79684a707560ce052b18799052 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Fri, 15 Jan 2021 12:48:12 +0100 Subject: Remove sprint channels from the configuration. Now that the core dev sprint has ended, we can safely remove those. It caused the wrong channel message to be huge because of all the deleted channels. --- bot/constants.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index f6da272e..90e302d0 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -126,23 +126,6 @@ class Channels(NamedTuple): hacktoberfest_2020 = 760857070781071431 voice_chat = 412357430186344448 - # Core Dev Sprint channels - sprint_announcements = 755958119963557958 - sprint_information = 753338352136224798 - sprint_organisers = 753340132639375420 - sprint_general = 753340631538991305 - sprint_social1_cheese_shop = 758779754789863514 - sprint_social2_pet_shop = 758780951978573824 - sprint_escape_room = 761031075942105109 - sprint_stdlib = 758553316732698634 - sprint_asyncio = 762904152438472714 - sprint_typing = 762904690341838888 - sprint_discussion_capi = 758553358587527218 - sprint_discussion_triage = 758553458365300746 - sprint_discussion_design = 758553492662255616 - sprint_discussion_mentor = 758553536623280159 - sprint_documentation = 761038271127093278 - class Client(NamedTuple): name = "Sir Lancebot" @@ -295,23 +278,6 @@ WHITELISTED_CHANNELS = ( Channels.off_topic_1, Channels.off_topic_2, Channels.voice_chat, - - # Core Dev Sprint Channels - Channels.sprint_announcements, - Channels.sprint_information, - Channels.sprint_organisers, - Channels.sprint_general, - Channels.sprint_social1_cheese_shop, - Channels.sprint_social2_pet_shop, - Channels.sprint_escape_room, - Channels.sprint_stdlib, - Channels.sprint_asyncio, - Channels.sprint_typing, - Channels.sprint_discussion_capi, - Channels.sprint_discussion_triage, - Channels.sprint_discussion_design, - Channels.sprint_discussion_mentor, - Channels.sprint_documentation, ) GIT_SHA = environ.get("GIT_SHA", "foobar") -- cgit v1.2.3 From a17445129a66907e210844d3edaf2871a001b4a8 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Fri, 15 Jan 2021 18:19:40 +0300 Subject: Updates Constants Updates the constants file with the new channel ID, and renames both channels to match the new names. --- bot/constants.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 90e302d0..24765c2a 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -124,7 +124,8 @@ class Channels(NamedTuple): verification = 352442727016693763 python_discussion = 267624335836053506 hacktoberfest_2020 = 760857070781071431 - voice_chat = 412357430186344448 + voice_chat_0 = 412357430186344448 + voice_chat_1 = 799647045886541885 class Client(NamedTuple): @@ -277,7 +278,8 @@ WHITELISTED_CHANNELS = ( Channels.off_topic_0, Channels.off_topic_1, Channels.off_topic_2, - Channels.voice_chat, + Channels.voice_chat_0, + Channels.voice_chat_1, ) GIT_SHA = environ.get("GIT_SHA", "foobar") -- cgit v1.2.3 From dd8664c3b4ff8037b9e28d9935a2f99fe9463a7b Mon Sep 17 00:00:00 2001 From: soul crusher 2005 Date: Wed, 20 Jan 2021 16:18:43 +0530 Subject: Update trivia_quiz.json new questions --- bot/resources/evergreen/trivia_quiz.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'bot') diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json index 8f0a4114..a5e3a3e4 100644 --- a/bot/resources/evergreen/trivia_quiz.json +++ b/bot/resources/evergreen/trivia_quiz.json @@ -247,6 +247,18 @@ "question": "What is the capital of Iraq?", "answer": "Baghdad", "info": "Baghdad is the capital of Iraq. It has a population of 7 million people." + }, + { + "id": 136, + "question":"The United Nations headquarters is located at which city?", + "answer":"New York", + "info":"The United Nations is headquartered in New York City in a complex designed by a board of architects led by Wallace Harrison and built by the architectural firm Harrison & Abramovitz. The complex has served as the official headquarters of the United Nations since its completion in 1951." + }, + { + "id": 137, + "question":"At what year did Christopher Columbus discover America?", + "answer":"1492", + "info":"The explorer Christopher Columbus made four trips across the Atlantic Ocean from Spain: in 1492, 1493, 1498 and 1502. He was determined to find a direct water route west from Europe to Asia, but he never did. Instead, he stumbled upon the Americas" } ] } -- cgit v1.2.3 From d259741371af9d4a09ed97b3f0ddb3df25ce2ded Mon Sep 17 00:00:00 2001 From: Vivaan Parashar Date: Mon, 19 Oct 2020 13:48:48 +0530 Subject: Implement Spooky Name Rate game removed commented code added the delete command Changed the name from ratethespook to spooknamerate renamed file too rename ratethespook.json to spooknamerate.json Added more names from mockaroo added one user one reaction removed print statements fixed flake8 comments and prevented the user from deleting the word while polling fixed typo, added random messages, made each entry unique, fixed one user one reaction, made it for one day. I think I am done commented some code autopep8ed edited few lines of code added comments added some comments edited code edited code edited fixed syntax error fixed flake8 complaints fixed flake8 complaint fixed small error added a `word` command and informed user if they don't have a registered word fixed a small error where the first and last names weren't separated removed unecessary code changed word to name remove slash in multiline strings and remove unecessary comments. Also, lock the background loop to `OCTOBER` and make emojis into discord emoij form (:emoij_name:) fixed an accidental tab removed another unecessary comment remove more unecessary comments remove `.keys()` for dictionaries removed `len() > 0` for lists and dicts and changed emojis to '\N{}' form Fixed code so that return value is that specified and added fail-safes instead of `if` and `else`s f-stringify fixed borderline api abuse and missing space in defining word lint code multiple imports on one line and fix typo remove unecessary return, shorten var typehint remove 'Just' in suggestion and add extra line in json file. - unecessary comment and replace on with for in help embed description shorten emoij_message remove check in delete command use defaultdict sort imports group imports, add typehinting, remove unnecessary comments and docstring, remove redundant elses and returns sort imports refactor var wrds, use generator instead of list use typing.Dict[str, str] instead of dict and use .items() instead of get() add a comment remove reduntant comment Renamed variable to avoid conflicts add asyncio locking to prevent Runtime error add some comments lock all commands to OCTOBER enhance looping in checking messages add `cog_check` instead of limiting each command remove unused import remove test code and comments use fail fast rename function Make storage persistent and make sure announce_word does not go off everytime the bot restarts fixed typo make data persist, rename everything that has word in it to 'name' and make sure announce_name doesn't start off everytime the bot restarts. remove testing code which would cause a real mess if commited. Which I did commit. use a separate file for the name and first_time becuase re-dumping such a long file might take time make var for repetitive paths and change .day to .hour change scheduling logic lint code add cog_unload fix error in spooknamerate_names.json and fix the before_loop in spooknamerate.py revert accidental changes and remove commented code remove unused code refactor vars to caps and make emojis_val global edit docstring and make seasonalbot_commands to community_bot_commands make annotation correct and add check when channel is None for get_channel Add fullstop Loop directly over data Add a proper dash and fix linting Fix linting reverted to making EMOJIS_VAL global and fixed capitalization fix small error verify it is working and simplify import remove data files Use redis caching instead of JSON and rename remove empty title and description in embed and use discord's red color remove var typehint add Client.month_override for dev move ping function rename seasonalbot to sir-lancebot remove unnecessary newline fix line formatting move added_messages to global Add more info on the caches remove + alias improve formatting use str.format instead of func fix error directly used Channels.community_bot_commands get user from cache instead of actively fetching the user move help messages to constant add more info in err msg Apply suggestions from review Co-authored-by: ks129 <45097959+ks129@users.noreply.github.com> remove unnecessary comments remove another redundant comment improve formatting and use better var names hard-code a var Use get or fetch format Remove redundant commit Fix in_allowed_month for debugging remove extra space make channel name link to channel simplify uteration use msg.reactions directly rename r to reaction and directly use variable reformat code use from_dict instead of manually creating the Embed Remove commented code fix channel linking add some debugging support add some more info to the debug mode Directly use getLogger sort imports Remove (name) in function doctype Use SpookNameRate.debug everywhere Shrink function call to one line and remove extra info in comment Use fail fast in on_reaction_add use environment var for debug mode Set debug val to False by default Fix some line breaks that formatting with black had made and use fail fast Use custom environment variable instead of the global bot env var make bot reply and store info from the bot's reply instead of the user's message remove an accidental swp file fix the reaction not getting removed remove extra brackets use generators instead of lists fix logging statement simplify loop rename spooknamerate to spookynamerate Correct docstring Improve the name announcing code Ignore reaction of all bots rearrange or send "Name deleted" instead of "Message deleted" Add client prefix --- bot/exts/halloween/spookynamerate.py | 401 ++++ bot/resources/halloween/spookynamerate_names.json | 2206 +++++++++++++++++++++ 2 files changed, 2607 insertions(+) create mode 100644 bot/exts/halloween/spookynamerate.py create mode 100644 bot/resources/halloween/spookynamerate_names.json (limited to 'bot') diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py new file mode 100644 index 00000000..e2950343 --- /dev/null +++ b/bot/exts/halloween/spookynamerate.py @@ -0,0 +1,401 @@ +import asyncio +import json +import random +from collections import defaultdict +from datetime import datetime, timedelta +from logging import getLogger +from os import getenv +from pathlib import Path +from typing import Dict, Union + +from async_rediscache import RedisCache +from discord import Embed, Reaction, TextChannel, User +from discord.colour import Colour +from discord.ext import tasks +from discord.ext.commands import Bot, Cog, Context, group + +from bot.constants import Channels, Client, Colours, Month +from bot.utils.decorators import InMonthCheckFailure + +logger = getLogger(__name__) + +EMOJIS_VAL = { + "\N{Jack-O-Lantern}": 1, + "\N{Ghost}": 2, + "\N{Skull and Crossbones}": 3, + "\N{Zombie}": 4, + "\N{Face Screaming In Fear}": 5, +} +ADDED_MESSAGES = [ + "Let's see if you win?", + ":jack_o_lantern: SPOOKY :jack_o_lantern:", + "If you got it, haunt it.", + "TIME TO GET YOUR SPOOKY ON! :skull:", +] +PING = "<@{id}>" + +EMOJI_MESSAGE = "\n".join([f"- {emoji} {val}" for emoji, val in EMOJIS_VAL.items()]) +HELP_MESSAGE_DICT = { + "title": "Spooky Name Rate", + "description": f"Help for the `{Client.prefix}spookynamerate` command", + "color": Colours.soft_orange, + "fields": [ + { + "name": "How to play", + "value": ( + "Everyday, the bot will post a random name, which you will need to spookify using your creativity.\n" + "You can rate each message according to how scary it is.\n" + "At the end of the day, the author of the message with most reactions will be the winner of the day.\n" + f"On a scale of 1 to {len(EMOJIS_VAL)}, the reactions order:\n" + f"{EMOJI_MESSAGE}" + ), + "inline": False, + }, + { + "name": "How do I add my spookified name?", + "value": f"Simply type `{Client.prefix}spookynamerate add my name`", + "inline": False, + }, + { + "name": "How do I *delete* my spookified name?", + "value": f"Simply type `{Client.prefix}spookynamerate delete`", + "inline": False, + }, + ], +} + + +class SpookyNameRate(Cog): + """ + A game that asks the user to spookify or halloweenify a name that is given everyday. + + It sends a random name everyday. The user needs to try and spookify it to his best ability and + send that name back using the `spookynamerate add entry` command + """ + + # This cache stores the message id of each added word along with a dictionary which contains the name the author + # added, the author's id, and the author's score (which is 0 by default) + messages = RedisCache() + + # The data cache stores small information such as the current name that is going on and whether it is the first time + # the bot is running + data = RedisCache() + debug = getenv('SPOOKYNAMERATE_DEBUG', False) # Enable if you do not want to limit the commands to October or if + # you do not want to wait till 12 UTC. Note: if debug is enabled and you run `.cogs reload spookynamerate`, it + # will automatically start the scoring and announcing the result (without waiting for 12, so do not expect it to.). + # Also, it won't wait for the two hours (when the poll closes). + + def __init__(self, bot: Bot) -> None: + self.bot = bot + + names_data = self.load_json( + Path("bot", "resources", "halloween", "spookynamerate_names.json") + ) + self.first_names = names_data["first_names"] + self.last_names = names_data["last_names"] + # the names are from https://www.mockaroo.com/ + + self.name = None + + self.bot.loop.create_task(self.load_vars()) + + self.first_time = None + self.poll = False + self.announce_name.start() + self.checking_messages = asyncio.Lock() + # Define an asyncio.Lock() to make sure the dictionary isn't changed + # when checking the messages for duplicate emojis' + + async def load_vars(self) -> None: + """Loads the variables that couldn't be loaded in __init__.""" + self.first_time = await self.data.get("first_time", True) + self.name = await self.data.get("name") + + @group(name="spookynamerate", invoke_without_command=True) + async def spooky_name_rate(self, ctx: Context) -> None: + """Get help on the Spooky Name Rate game.""" + await ctx.send(embed=Embed.from_dict(HELP_MESSAGE_DICT)) + + @spooky_name_rate.command(name="list", aliases=["all", "entries"]) + async def list_entries(self, ctx: Context) -> None: + """Send all the entries up till now in a single embed.""" + await ctx.send(embed=await self.get_responses_list(final=False)) + + @spooky_name_rate.command(name="name") + async def tell_name(self, ctx: Context) -> None: + """Tell the current random name.""" + if not self.poll: + await ctx.send(f"The name is **{self.name}**") + return + + await ctx.send( + f"The name ~~is~~ was **{self.name}**. The poll has already started, so you cannot " + "add an entry." + ) + + @spooky_name_rate.command(name="add", aliases=["register"]) + async def add_name(self, ctx: Context, *, name: str) -> None: + """Use this command to add/register your spookified name.""" + if self.poll: + logger.info(f"{ctx.message.author} tried to add a name, but the poll had already started.") + await ctx.send("Sorry, the poll has started! You can try and participate in the next round though!") + return + + message = ctx.message + + for data in (json.loads(user_data) for _, user_data in await self.messages.items()): + if data["author"] == message.author.id: + await ctx.send( + "But you have already added an entry! Type " + f"`{self.bot.command_prefix}spookynamerate " + "delete` to delete it, and then you can add it again" + ) + return + + elif data["name"] == name: + await ctx.send("TOO LATE. Someone has already added this name.") + return + + msg = await (await self.get_channel()).send(f"{message.author.mention} added the name {name!r}!") + + await self.messages.set( + msg.id, + json.dumps( + { + "name": name, + "author": message.author.id, + "score": 0, + } + ), + ) + + for emoji in EMOJIS_VAL: + await msg.add_reaction(emoji) + + logger.info(f"{message.author} added the name {name!r}") + + @spooky_name_rate.command(name="delete") + async def delete_name(self, ctx: Context) -> None: + """Delete the user's name.""" + if self.poll: + await ctx.send("You can't delete your name since the poll has already started!") + return + for message_id, data in await self.messages.items(): + data = json.loads(data) + + if ctx.author.id == data["author"]: + await self.messages.delete(message_id) + await ctx.send(f'Name deleted successfully ({data["name"]!r})!') + return + + await ctx.send( + f"But you don't have an entry... :eyes: Type `{self.bot.command_prefix}spookynamerate add your entry`" + ) + + @Cog.listener() + async def on_reaction_add(self, reaction: Reaction, user: User) -> None: + """Ensures that each user adds maximum one reaction.""" + if user.bot or not await self.messages.contains(reaction.message.id): + return + + async with self.checking_messages: # Acquire the lock so that the dictionary isn't reset while iterating. + if reaction.emoji in EMOJIS_VAL: + # create a custom counter + reaction_counter = defaultdict(int) + for msg_reaction in reaction.message.reactions: + async for reaction_user in msg_reaction.users(): + if reaction_user == self.bot.user: + continue + reaction_counter[reaction_user] += 1 + + if reaction_counter[user] > 1: + await user.send( + "Sorry, you have already added a reaction, " + "please remove your reaction and try again." + ) + await reaction.remove(user) + return + + @tasks.loop(hours=24.0) + async def announce_name(self) -> None: + """Announces the name needed to spookify every 24 hours and the winner of the previous game.""" + if not self.in_allowed_month(): + return + + channel = await self.get_channel() + + if self.first_time: + await channel.send( + "Okkey... Welcome to the **Spooky Name Rate Game**! It's a relatively simple game.\n" + f"Everyday, a random name will be sent in <#{Channels.community_bot_commands}> " + "and you need to try and spookify it!\nRegister your name using " + f"`{self.bot.command_prefix}spookynamerate add spookified name`" + ) + + await self.data.set("first_time", False) + self.first_time = False + + else: + if await self.messages.items(): + await channel.send(embed=await self.get_responses_list(final=True)) + self.poll = True + if not SpookyNameRate.debug: + await asyncio.sleep(2 * 60 * 60) # sleep for two hours + + logger.info("Calculating score") + for message_id, data in await self.messages.items(): + data = json.loads(data) + + msg = await channel.fetch_message(message_id) + score = 0 + for reaction in msg.reactions: + reaction_value = EMOJIS_VAL.get(reaction.emoji, 0) # get the value of the emoji else 0 + score += reaction_value * (reaction.count - 1) # multiply by the num of reactions + # subtract one, since one reaction was done by the bot + + logger.debug(f"{self.bot.get_user(data['author'])} got a score of {score}") + data["score"] = score + await self.messages.set(message_id, json.dumps(data)) + + # Sort the winner messages + winner_messages = sorted( + ((msg_id, json.loads(usr_data)) for msg_id, usr_data in await self.messages.items()), + key=lambda x: x[1]["score"], + reverse=True, + ) + + winners = [] + for i, winner in enumerate(winner_messages): + winners.append(winner) + if len(winner_messages) > i + 1: + if winner_messages[i + 1][1]["score"] != winner[1]["score"]: + break + elif len(winner_messages) == (i + 1) + 1: # The next element is the last element + if winner_messages[i + 1][1]["score"] != winner[1]["score"]: + break + + # one iteration is complete + await channel.send("Today's Spooky Name Rate Game ends now, and the winner(s) is(are)...") + + async with channel.typing(): + await asyncio.sleep(1) # give the drum roll feel + + if not winners: # There are no winners (no participants) + await channel.send("Hmm... Looks like no one participated! :cry:") + return + + score = winners[0][1]["score"] + congratulations = "to all" if len(winners) > 1 else PING.format(id=winners[0][1]["author"]) + names = ", ".join(f'{win[1]["name"]} ({PING.format(id=win[1]["author"])})' for win in winners) + + # display winners, their names and scores + await channel.send( + f"Congratulations {congratulations}!\n" + f"You have a score of {score}!\n" + f"Your name{ 's were' if len(winners) > 1 else 'was'}:\n{names}" + ) + + # Send random party emojis + party = (random.choice([":partying_face:", ":tada:"]) for _ in range(random.randint(1, 10))) + await channel.send(" ".join(party)) + + async with self.checking_messages: # Acquire the lock to delete the messages + await self.messages.clear() # reset the messages + + # send the next name + self.name = f"{random.choice(self.first_names)} {random.choice(self.last_names)}" + await self.data.set("name", self.name) + + await channel.send( + "Let's move on to the next name!\nAnd the next name is...\n" + f"**{self.name}**!\nTry to spookify that... :smirk:" + ) + + self.poll = False # accepting responses + + @announce_name.before_loop + async def wait_till_scheduled_time(self) -> None: + """Waits till the next day's 12PM if crossed it, otherwise waits till the same day's 12PM.""" + if SpookyNameRate.debug: + return + + now = datetime.utcnow() + if now.hour < 12: + twelve_pm = now.replace(hour=12, minute=0, second=0, microsecond=0) + time_left = twelve_pm - now + await asyncio.sleep(time_left.seconds) + return + + tomorrow_12pm = now + timedelta(days=1) + tomorrow_12pm = tomorrow_12pm.replace(hour=12, minute=0, second=0, microsecond=0) + await asyncio.sleep((tomorrow_12pm - now).seconds) + + async def get_responses_list(self, final: bool = False) -> Embed: + """Returns an embed containing the responses of the people.""" + channel = await self.get_channel() + + embed = Embed(color=Colour.red()) + + if await self.messages.items(): + if final: + embed.title = "Spooky Name Rate is about to end!" + embed.description = ( + "This Spooky Name Rate round is about to end in 2 hours! You can review " + "the entries below! Have you rated other's names?" + ) + else: + embed.title = "All the spookified names!" + embed.description = "See a list of all the entries entered by everyone!" + else: + embed.title = "No one has added an entry yet..." + + for message_id, data in await self.messages.items(): + data = json.loads(data) + + embed.add_field( + name=(self.bot.get_user(data["author"]) or await self.bot.fetch_user(data["author"])).name, + value=f"[{(data)['name']}](https://discord.com/channels/{Client.guild}/{channel.id}/{message_id})", + ) + + return embed + + async def get_channel(self) -> Union[TextChannel, None]: + """Gets the sir-lancebot-channel after waiting until ready.""" + await self.bot.wait_until_ready() + channel = self.bot.get_channel( + Channels.community_bot_commands + ) or await self.bot.fetch_channel(Channels.community_bot_commands) + if not channel: + logger.warning("Bot is unable to get the #seasonalbot-commands channel. Please check the channel ID.") + return channel + + @staticmethod + def load_json(file: Path) -> Dict[str, str]: + """Loads a JSON file and returns its contents.""" + with file.open("r", encoding="utf-8") as f: + return json.load(f) + + @staticmethod + def in_allowed_month() -> bool: + """Returns whether running in the limited month.""" + if SpookyNameRate.debug: + return True + + if not Client.month_override: + return datetime.utcnow().month == Month.OCTOBER + return Client.month_override == Month.OCTOBER + + def cog_check(self, ctx: Context) -> bool: + """A command to check whether the command is being called in October.""" + if not self.in_allowed_month(): + raise InMonthCheckFailure("You can only use these commands in October!") + return True + + def cog_unload(self) -> None: + """Stops the announce_name task.""" + self.announce_name.cancel() + + +def setup(bot: Bot) -> None: + """Loads the SpookyNameRate Cog.""" + bot.add_cog(SpookyNameRate(bot)) diff --git a/bot/resources/halloween/spookynamerate_names.json b/bot/resources/halloween/spookynamerate_names.json new file mode 100644 index 00000000..7657880b --- /dev/null +++ b/bot/resources/halloween/spookynamerate_names.json @@ -0,0 +1,2206 @@ +{ + "first_names": [ + "Eberhard", + "Gladys", + "Joshua", + "Misty", + "Bondy", + "Constantine", + "Juliette", + "Dalis", + "Nap", + "Sandy", + "Inglebert", + "Sasha", + "Julietta", + "Christoforo", + "Del", + "Zelma", + "Vladimir", + "Wayland", + "Enos", + "Siobhan", + "Farrand", + "Ailee", + "Horatia", + "Gloriana", + "Britney", + "Shel", + "Lindsey", + "Francis", + "Elsa", + "Fred", + "Upton", + "Lothaire", + "Cara", + "Margarete", + "Wolfgang", + "Charin", + "Loydie", + "Aurelea", + "Sibel", + "Glenden", + "Julian", + "Roby", + "Gerri", + "Sandie", + "Twila", + "Shaylyn", + "Clyde", + "Dina", + "Chase", + "Caron", + "Carlin", + "Aida", + "Rhonda", + "Rebekkah", + "Charmian", + "Lindy", + "Obadiah", + "Willy", + "Matti", + "Melodie", + "Ira", + "Wilfrid", + "Berton", + "Denver", + "Clarette", + "Nicolas", + "Tawnya", + "Cynthy", + "Arman", + "Sherwood", + "Flemming", + "Berri", + "Beret", + "Aili", + "Hannie", + "Eadie", + "Tannie", + "Gilda", + "Walton", + "Nolly", + "Tonya", + "Meaghan", + "Timmi", + "Faina", + "Sarge", + "Britteny", + "Farlay", + "Carola", + "Skippy", + "Corrina", + "Hans", + "Courtnay", + "Taffy", + "Averill", + "Martie", + "Tobye", + "Broderic", + "Gardner", + "Lucky", + "Beverie", + "Ignaz", + "Siana", + "Marybelle", + "Leif", + "Baily", + "Pyotr", + "Myrtle", + "Darb", + "Gar", + "Vinni", + "Samson", + "Kinny", + "Briant", + "Verney", + "Del", + "Marion", + "Beniamino", + "Nona", + "Fay", + "Noreen", + "Maurizio", + "Nial", + "Mirabella", + "Melisa", + "Anatol", + "Halette", + "Johnathon", + "Antonietta", + "Germana", + "Towny", + "Shayne", + "Court", + "Merrile", + "Staffard", + "Odele", + "Gustav", + "Moyna", + "Warden", + "Craggie", + "Hurleigh", + "Hartley", + "Rustie", + "Raven", + "Farra", + "Leonidas", + "Jorrie", + "Maximilian", + "Augustin", + "Cordelia", + "Christoffer", + "Lana", + "Vittorio", + "Janith", + "Margaret", + "Bethanne", + "Brooke", + "Payton", + "Poul", + "Diahann", + "Andy", + "Garek", + "Isa", + "Dunn", + "Anny", + "Hillary", + "Andres", + "Winn", + "Gare", + "Ameline", + "Audre", + "Rodrigo", + "Anabal", + "Reuben", + "Cecil", + "Alexandro", + "Corny", + "Erek", + "William", + "Rudyard", + "Muffin", + "Allin", + "Emmit", + "Heindrick", + "Myrna", + "Kriste", + "Perry", + "Annmarie", + "Jasun", + "Esdras", + "Jobyna", + "Marian", + "Theodore", + "Dionisio", + "Efren", + "Clarita", + "Leilah", + "Modestia", + "Clem", + "Jemmy", + "Karol", + "Minni", + "Damien", + "Tessy", + "Roanne", + "Daniele", + "Camel", + "Charlot", + "Daron", + "Cherey", + "Ashil", + "Joel", + "Michell", + "Sukey", + "Micheil", + "Chev", + "Winny", + "Natale", + "Kendra", + "Bell", + "Darice", + "Beilul", + "Leonore", + "Abba", + "Warden", + "Bryna", + "Sammy", + "Brantley", + "Goldi", + "Meridith", + "Eleanor", + "Brear", + "Kristina", + "Muriel", + "Serge", + "Iver", + "Jonis", + "Ada", + "Marleen", + "Pavlov", + "Kellia", + "Abdel", + "Waylin", + "Ignazio", + "Tana", + "Kiley", + "Lynna", + "Peyton", + "Linoel", + "Patrice", + "Loria", + "Linda", + "Edna", + "Viki", + "Kelcy", + "Chelsae", + "Olga", + "Trace", + "Ethel", + "Giorgio", + "Geralda", + "Rosaline", + "Caralie", + "Duke", + "Sig", + "Seana", + "Boris", + "Jeanie", + "Stacee", + "Giffie", + "Myrta", + "Prescott", + "Roger", + "Ame", + "Lelia", + "Marthena", + "Mord", + "Tommi", + "Artemus", + "Wynn", + "Rodi", + "Denna", + "Joleen", + "Iris", + "Pascale", + "Cody", + "Kienan", + "Darline", + "Lanna", + "Chandra", + "Michel", + "Nanete", + "Rosana", + "Ondrea", + "Linette", + "Syd", + "Rhianon", + "Christiano", + "Moyna", + "Darbee", + "Chadd", + "Roselia", + "Niki", + "Flint", + "Natala", + "Merrie", + "Noelyn", + "Arvin", + "Vin", + "Khalil", + "Nance", + "Seward", + "Dagmar", + "Shanta", + "Noland", + "Vance", + "Kyla", + "Locke", + "Abagail", + "Guthrey", + "Thalia", + "Devlen", + "Parrnell", + "Leonard", + "Amber", + "Dell", + "Lolita", + "Revkah", + "Ronna", + "Ninnetta", + "Jobey", + "Larisa", + "Wendel", + "Sonnnie", + "Saul", + "Lem", + "Wang", + "Borg", + "Korie", + "Rosanna", + "Barnaby", + "Channa", + "Gordan", + "Wang", + "Dasi", + "Laurianne", + "Jo ann", + "Bond", + "Kean", + "Harwell", + "Abbey", + "Carlo", + "Hamil", + "Ameline", + "Tristam", + "Donn", + "Earle", + "Lanie", + "Maximilianus", + "Frieda", + "Noella", + "Orsa", + "Timmi", + "Linea", + "Claudina", + "Langsdon", + "Murdock", + "Cello", + "Lek", + "Viviyan", + "Candra", + "Erena", + "Shirline", + "Mariann", + "Keelby", + "Jacquelin", + "Clerissa", + "Davis", + "Ara", + "My", + "Andris", + "Drugi", + "Lynn", + "Andonis", + "Jamie", + "Cherise", + "Lonni", + "Reamonn", + "Cathee", + "Clarence", + "Joletta", + "Tanny", + "Gasparo", + "Heddie", + "Cullin", + "Sander", + "Emmalee", + "Gwendolin", + "Hayley", + "Mandie", + "Cassondra", + "Celestyna", + "Fanny", + "Alica", + "Vivyan", + "Kippy", + "Leandra", + "Jerry", + "Elspeth", + "Lexine", + "Tobie", + "Allin", + "Ambros", + "Ash", + "Conroy", + "Melonie", + "Aylmer", + "Maximo", + "Connie", + "Torre", + "Tammie", + "Corabella", + "Beau", + "Nancee", + "Ailbert", + "Florrie", + "Trevar", + "Tiffani", + "Dre", + "Eward", + "Hallie", + "Stesha", + "Ralina", + "Vinni", + "Bastien", + "Galvan", + "Romain", + "Yasmin", + "Theodoric", + "Maxy", + "Lesly", + "Gerald", + "Erskine", + "Joice", + "Theadora", + "Sheeree", + "Danit", + "Burr", + "Morten", + "Godfree", + "Lacey", + "Sandye", + "Louisa", + "Annora", + "Rochester", + "Saundra", + "Deeann", + "Aloisia", + "Oralle", + "Ree", + "Kaile", + "Rogerio", + "Graeme", + "Garald", + "Hulda", + "Deny", + "Bessy", + "Zarah", + "Melisande", + "Taffy", + "Jed", + "Bar", + "Jacki", + "Avictor", + "Damiano", + "Yasmeen", + "Geralda", + "Kermie", + "Verge", + "Cyril", + "Klara", + "Anna", + "Abey", + "Mariellen", + "Mirabel", + "Charmain", + "Carleton", + "Biddie", + "Junina", + "Cass", + "Jdavie", + "Laird", + "Olenka", + "Dion", + "Hedy", + "Haley", + "Stacy", + "Alis", + "Morena", + "Damita", + "Wynn", + "Kellia", + "Midge", + "Gerri", + "Symon", + "Markus", + "Brenn", + "Rancell", + "Marlon", + "Dulciana", + "Lemmy", + "Neale", + "Vladamir", + "Alasteir", + "Gilberta", + "Seumas", + "Ronda", + "Myrvyn", + "Gabey", + "Goldia", + "Lothaire", + "Averil", + "Marlo", + "Nanice", + "Bernadette", + "Nehemiah", + "Ivar", + "Natala", + "Dorthy", + "Melva", + "Alisha", + "Ruthann", + "Ray", + "Ariel", + "Gib", + "Pippo", + "Miner", + "Ardith", + "Letisha", + "Granger", + "Sue", + "Toby", + "Tallou", + "Stephi", + "Hunter", + "Terrell", + "Pail", + "Moise", + "Rosetta", + "Ira", + "Denyse", + "Jackie", + "Fons", + "Goldy", + "Rani", + "Bendick", + "Valentijn", + "Annabell", + "Ardith", + "Lesly", + "Almire", + "Emmalyn", + "Mechelle", + "Anna", + "Duff", + "Louise", + "Vivian", + "Farand", + "Sophi", + "Thedric", + "Vivien", + "Jere", + "Kassie", + "Andy", + "Helli", + "Ros", + "Babara", + "Othella", + "Shelton", + "Hector", + "Charmian", + "Rosamond", + "Maison", + "Magda", + "Gustave", + "Latisha", + "Erik", + "Gavin", + "Bobette", + "Masha", + "Collie", + "Kippie", + "Jillayne", + "Fairfax", + "Ulrika", + "Juliann", + "Joly", + "Aldus", + "Clarie", + "Aluin", + "Claudetta", + "Noella", + "Nichols", + "Rutger", + "Niall", + "Hunter", + "Hyacinthia", + "Eva", + "Humphrey", + "Randi", + "Leontyne", + "Bordy", + "Orin", + "Tobey", + "Aldis", + "Vernon", + "Griz", + "Dynah", + "Ann-marie", + "Inglebert", + "Gifford", + "Emeline", + "Shem", + "Sigvard", + "Mayne", + "Rhodia", + "Seward", + "Valencia", + "Babara", + "Cirstoforo", + "Nye", + "Merissa", + "Lucinda", + "Wynn", + "Vassili", + "Cletus", + "Felisha", + "Laural", + "William", + "Emmalynne", + "Angy", + "Charles", + "Jemmy", + "Edward", + "Millicent", + "Homer", + "Allie", + "Brandyn", + "Dannye", + "Hector", + "Fawne", + "Frayda", + "Issiah", + "Deana", + "Bearnard", + "Ken", + "Sinclare", + "Mallorie", + "Noby", + "Deonne", + "Brig", + "Ruy", + "Vivia", + "Nyssa", + "Ame", + "Carmen", + "Solly", + "Carolee", + "Felice", + "Claiborne", + "Layney", + "Raina", + "Tami", + "Dosi", + "Barth", + "Julita", + "Gardiner", + "Stesha", + "Geneva", + "Saudra", + "Ella", + "Welbie", + "Marya", + "Happy", + "Brandise", + "Jewell", + "Joana", + "Eddy", + "Buck", + "Leslie", + "Yolanda", + "Murdoch", + "Muffin", + "Myrna", + "Susi", + "Berthe", + "Debra", + "Kyla", + "Bron", + "Thurston", + "Case", + "Shelli", + "Danika", + "Charissa", + "Wylie", + "Corine", + "Caitrin", + "Atalanta", + "Vevay", + "Thekla", + "Inez", + "Pris", + "Zsazsa", + "Ardenia", + "Ole", + "Kelcy", + "Earl", + "Pierson", + "Opalina", + "Leta", + "Keefer", + "Conrado", + "Chen", + "Alys", + "Floyd", + "Kai", + "Warden", + "Peyton", + "Debora", + "Walton", + "Fionna", + "Kendra", + "Michail", + "Christa", + "Theodor", + "Avivah", + "Patric", + "Quinton", + "Fey", + "Lewiss", + "Loren", + "Nedi", + "Fergus", + "Jeanie", + "Liuka", + "Ashley", + "Ellsworth", + "Winslow", + "Land", + "Rooney", + "Kati", + "Joelie", + "Garner", + "Clarice", + "Clair", + "Heddi", + "Ivan", + "Enrichetta", + "Umberto", + "Alys", + "Marcellina", + "Elnore", + "Wilburt", + "Ami", + "Meridith", + "Devlin", + "Cicely", + "Nathanael", + "Rafi", + "Arluene", + "Erasmus", + "Tasia", + "Seumas", + "George", + "Fredrika", + "Jayne", + "Linus", + "Mathilde", + "Klarrisa", + "Willy", + "Rad", + "Rae", + "Wilfred", + "Amberly", + "Paulo", + "Robbi", + "Gladys", + "Mirilla", + "Danica", + "Montgomery", + "Bellina", + "Neill", + "Roddie", + "Sebastiano", + "Adrianne", + "Gilli", + "Rhodia", + "Orbadiah", + "Levy", + "Griswold", + "Millicent", + "Carry", + "Alexander", + "Carole", + "Othilie", + "Enrica", + "Corissa", + "Meaghan", + "Margret", + "Sheff", + "Walton", + "Tremain", + "Bear", + "Maximilian", + "Theodora", + "Fredric", + "Baudoin", + "Rees", + "Roldan", + "Mayor", + "Angelica", + "Clemente", + "Florencia", + "Lancelot", + "Valencia", + "Caddric", + "Frieda", + "Jarvis", + "Shamus", + "Kalindi", + "Allen", + "Maureen", + "Ax", + "Barbra", + "Craggy", + "Howie", + "Orson", + "Cammy", + "Sullivan", + "Marleen", + "Jarrad", + "Lucy", + "Catha", + "Guillemette", + "Birdie", + "Forrest", + "Luce", + "Myriam", + "Serge", + "Kali", + "Ruperto", + "Trisha", + "Shaylynn", + "Janella", + "Franciskus", + "Melinde", + "Effie", + "Letti", + "Roderic", + "Jandy", + "Michaelina", + "Mohammed", + "Dolorita", + "Elbertine", + "Esma", + "Emmett", + "Lucila", + "Joyann", + "Mufi", + "Karlotta", + "Vannie", + "Daphna", + "Blondie", + "Madelene", + "Tomkin", + "Kassie", + "Flynn", + "Zebadiah", + "Lauritz", + "Brian", + "Leah", + "Amalita", + "Corissa", + "Onfre", + "Shantee", + "Deena", + "Marena", + "Alejoa", + "Fania", + "Catha", + "Cherlyn", + "Gerrilee", + "Brook", + "Yardley", + "Karry", + "Dennis", + "Ingra", + "Damian", + "Alexandros", + "Romola", + "Grantley", + "Antons", + "Randal", + "Lorilee", + "Brier", + "Tyrone", + "Jennica", + "Deidre", + "Arlin", + "Marline", + "Lyell", + "Lorelei", + "Marius", + "Willy", + "Teddy", + "Grantham", + "Yelena", + "Jaimie", + "Brewer", + "Tess", + "Othelia", + "Bondy", + "Rebecka", + "Laurice", + "Jasen", + "Betty", + "Alverta", + "Pepita", + "Kandace", + "Loni", + "Doreen", + "Ketty", + "Ree", + "Danni", + "Zorah", + "Shayla", + "Ivy", + "Darin", + "Karie", + "Brittaney", + "Viole", + "Harlene", + "Jasun", + "Aime", + "Rickie", + "Heath", + "Andris", + "Vaughn", + "Giorgi", + "Maddalena", + "Shirley", + "Cherie", + "Zacharia", + "Darcey", + "Barbee", + "Ernest", + "Sher", + "Faustina", + "Nari", + "Gusella", + "Reginald", + "Zack", + "Michele", + "Gene", + "Lindy", + "Mirilla", + "Tudor", + "Tyler", + "Bernadina", + "Magdalen", + "Nollie", + "Coreen", + "Hoebart", + "Virginie", + "Waylin", + "Hank", + "Valenka", + "Sabine", + "Jesus", + "Annabell", + "Jesselyn", + "Marysa", + "Corbett", + "Carena", + "Bert", + "Tanhya", + "Alphonse", + "Johnette", + "Vince", + "Cordell", + "Ramonda", + "Trev", + "Glenna", + "Loy", + "Arni", + "Tedd", + "Tristam", + "Zelma", + "Emmeline", + "Ellswerth", + "Janeta", + "Hughie", + "Tarun", + "Enid", + "Rafe", + "Hal", + "Melissa", + "Layan", + "Sia", + "Horace", + "Derry", + "Kelsi", + "Zacharia", + "Tillie", + "Dillon", + "Maxwell", + "Shanai", + "Charlize", + "Usama", + "Nabeela", + "Emily-Jane", + "Martyn", + "Tre", + "Ioan", + "Elysia", + "Mikaeel", + "Danny", + "Ciaron", + "Ace", + "Amy-Louise", + "Gabrielle", + "Robbie", + "Thea", + "Gloria", + "Jana", + "Cole", + "Eamon", + "Samiyah", + "Ellie-Mai", + "Lawson", + "Gia", + "Merryn", + "Andre", + "Ansh", + "Kavita", + "Alasdair", + "Aamina", + "Donna", + "Dario", + "Sahra", + "Brittany", + "Shakeel", + "Taylor", + "Ellenor", + "Kacy", + "Gene", + "Hetty", + "Fletcher", + "Donte", + "Krisha", + "Everett", + "Leila", + "Aairah", + "Zander", + "Sakina", + "Sanaya", + "Nelly", + "Manon", + "Antonio", + "Aimie", + "Kyran", + "Daria", + "Tilly-Mae", + "Lisa", + "Ammaarah", + "Adina", + "Kaan", + "Torin", + "Sadie", + "Mia-Rose", + "Aadam", + "Phyllis", + "Jace", + "Fraser", + "Tamanna", + "Dahlia", + "Cristian", + "Maira", + "Lana", + "Lily-Mai", + "Barney", + "Beatrice", + "Tabitha", + "Anis", + "Heidi", + "Ahyan", + "Usaamah", + "Jolene", + "Melisa", + "Magdalena", + "Hina" + ], + "last_names": [ + "Silveston", + "Manson", + "Hoodlass", + "Auden", + "Speakman", + "Seavers", + "Sodeau", + "Gouth", + "Pickersail", + "Ferschke", + "Buzzing", + "Kinnar", + "Pemberton", + "Firebrace", + "Kornilyev", + "Linsley", + "Petyanin", + "McCobb", + "Disdel", + "Eskrick", + "Pringuer", + "Clavering", + "Sims", + "Lippitt", + "Springall", + "Spiteri", + "Dwyr", + "Tomas", + "Cleminson", + "Crowder", + "Juster", + "Leven", + "Doucette", + "Schimoni", + "Readwing", + "Karet", + "Reef", + "Welden", + "Bemand", + "Schulze", + "Bartul", + "Collihole", + "Thain", + "Bernhardt", + "Tolputt", + "Hedges", + "Lowne", + "Kobu", + "Cabrera", + "Gavozzi", + "Ghilardini", + "Leamon", + "Gadsden", + "Gregg", + "Tew", + "Bangle", + "Youster", + "Vince", + "Cristea", + "Ablott", + "Lightowlers", + "Kittredge", + "Armour", + "Bukowski", + "Knowlton", + "Juett", + "Santorini", + "Ends", + "Hawkings", + "Janowicz", + "Harry", + "Bougourd", + "Gillow", + "Whalebelly", + "Conneau", + "Mellows", + "Stolting", + "Stickells", + "Maryet", + "Echallie", + "Edgecombe", + "Orchart", + "Mowles", + "McGibbon", + "Titchen", + "Madgewick", + "Fairburne", + "Colgan", + "Chaudhry", + "Taks", + "Lorinez", + "Eixenberger", + "Burel", + "Chapleo", + "Margram", + "Purse", + "MacKay", + "Oxlade", + "Prahm", + "Wellbank", + "Blackborow", + "Woodbridge", + "Sodory", + "Vedmore", + "Beeckx", + "Newcomb", + "Ridel", + "Desporte", + "Jobling", + "Winear", + "Korneichuk", + "Aucott", + "Wawer", + "Aicheson", + "Hawkslee", + "Wynes", + "St. Quentin", + "McQuorkel", + "Hendrick", + "Rudsdale", + "Winsor", + "Thunders", + "Stonbridge", + "Perrie", + "D'Alessandro", + "Banasevich", + "Mc Elory", + "Cobbledick", + "Wreakes", + "Carnie", + "Pallister", + "Yeates", + "Hoovart", + "Doogood", + "Churn", + "Gillon", + "Nibley", + "Dusting", + "Melledy", + "O'Noland", + "Crosfeld", + "Pairpoint", + "Longson", + "Rodden", + "Foyston", + "Le Teve", + "Brumen", + "Pudsey", + "Klimentov", + "Agent", + "Seabert", + "Cramp", + "Bitcheno", + "Embery", + "Etheredge", + "Sheardown", + "McKune", + "Vearncomb", + "Lavington", + "Rylands", + "Derges", + "Olivetti", + "Matasov", + "Thrower", + "Jobin", + "Ramsell", + "Rude", + "Tregale", + "Bradforth", + "McQuarter", + "Walburn", + "Poad", + "Filtness", + "Carneck", + "Pavis", + "Pinchen", + "Polye", + "Abry", + "Radloff", + "McDugal", + "Loughton", + "Revitt", + "Baniard", + "Kovalski", + "Mapother", + "Hendrikse", + "Rickardsson", + "Featherbie", + "Harlow", + "Kruschov", + "McCrillis", + "Barabich", + "Peaker", + "Skamell", + "Gorges", + "Chance", + "Bresner", + "Profit", + "Swinfon", + "Goldson", + "Nunson", + "Tarling", + "Ruperti", + "Grimsell", + "Davey", + "Deetlof", + "Gave", + "Fawltey", + "Tyre", + "Whaymand", + "Trudgian", + "McAndrew", + "Aleksankov", + "Dimbleby", + "Beseke", + "Cleverley", + "Aberhart", + "Courtin", + "MacKellen", + "Johannesson", + "Churm", + "Laverock", + "Astbury", + "Canto", + "Nelles", + "Dormand", + "Blucher", + "Youngs", + "Dalrymple", + "M'Chirrie", + "Jansens", + "Golthorpp", + "Ibberson", + "Andriveau", + "Paulton", + "Parrington", + "Shergill", + "Bickerton", + "Hugonneau", + "Cornelissen", + "Spincks", + "Malkinson", + "Kettow", + "Wasiel", + "Skeat", + "Maynard", + "Goutcher", + "Cratchley", + "Loving", + "Averies", + "Cahillane", + "Alvarado", + "Truggian", + "Bravington", + "McGonigle", + "Crocombe", + "Slorance", + "Dukes", + "Nairns", + "Condict", + "Got", + "Flowerdew", + "Deboy", + "Death", + "Patroni", + "Colgrave", + "Polley", + "Spraging", + "Orteaux", + "Daskiewicz", + "Dunsmore", + "Forrington", + "De Gogay", + "Swires", + "Grimmert", + "Castells", + "Scraggs", + "Chase", + "Dixsee", + "Brennans", + "Gookes", + "MacQueen", + "Galbreth", + "Buttwell", + "Annear", + "Sutherley", + "Portis", + "Pashen", + "Blackbourn", + "Sedgemond", + "Huegett", + "Emms", + "Leifer", + "Paschek", + "Bynold", + "Mahony", + "Izacenko", + "Hadland", + "Sallows", + "Hamper", + "Godlee", + "Rablin", + "Emms", + "Zealy", + "Russi", + "Crassweller", + "Shotbolt", + "Van Der Weedenburg", + "MacGille", + "Carillo", + "Guerin", + "Cuolahan", + "Metzel", + "Martinovsky", + "Stoggles", + "Brameld", + "Coupland", + "Kaaskooper", + "Sallows", + "Rizzotto", + "Dike", + "O'Lochan", + "Spragg", + "Lavarack", + "MacNess", + "Swetenham", + "Dillet", + "Coffey", + "Meikle", + "Loynes", + "Josum", + "Adkin", + "Tompsett", + "Maclaine", + "Fippe", + "Bispo", + "Whittek", + "Rylett", + "Iveagh", + "Elgar", + "Casswell", + "Tilt", + "Macklin", + "Lillee", + "Hamshere", + "Coite", + "Dollard", + "Tiesman", + "Coltart", + "Stothert", + "Crosswaite", + "Padgett", + "Gleadle", + "Meedendorpe", + "Alexsandrovich", + "Williamson", + "Futty", + "Antwis", + "Romanski", + "Dionisetti", + "Dimitriev", + "Swalowe", + "Dewing", + "O'Driscoll", + "Jeandel", + "Summerly", + "Shoute", + "Trelevan", + "Matkin", + "Headey", + "Rosson", + "Dunn", + "Gunner", + "Stapells", + "Fratczak", + "McGillivray", + "Edis", + "Treuge", + "Haskayne", + "Perell", + "O'Fairy", + "Slisby", + "Axcell", + "Mattingley", + "Tumilty", + "Kibble", + "Lambert", + "Hassall", + "Simpkin", + "Nitti", + "Stiegar", + "Pavitt", + "Kerby", + "Ruzic", + "Westwick", + "Tonbye", + "Bocken", + "Kinforth", + "Wren", + "Attow", + "McComish", + "McNickle", + "Wildman", + "O'Corhane", + "Jewar", + "Caveau", + "Woodrooffe", + "Batson", + "Stayt", + "A'field", + "Domesday", + "Taberer", + "Gigg", + "Stanmore", + "Hanton", + "Roskell", + "Brasener", + "Stanbro", + "Cordy", + "O'Bradane", + "Hansberry", + "Erdes", + "Wagon", + "Jimmes", + "Ruffles", + "Wigginton", + "Haste", + "Rymill", + "Tomsett", + "Ambrosoli", + "Reidshaw", + "Nurcombe", + "Costigan", + "Berwick", + "Hinchon", + "Blissitt", + "Golston", + "Goullee", + "Hudspeth", + "Traher", + "Salandino", + "Fatscher", + "Davidov", + "Baukham", + "Mallan", + "Kilmurray", + "Dmych", + "Mair", + "Felmingham", + "Kedward", + "Leechman", + "Frank", + "Tremoulet", + "Manley", + "Newcom", + "Brandone", + "Cliffe", + "Shorte", + "Baalham", + "Fairhead", + "Sheal", + "Effnert", + "MacCaughey", + "Rizzolo", + "Linthead", + "Greenhouse", + "Clayson", + "Franca", + "Lambell", + "Egdal", + "Pringell", + "Penni", + "Train", + "Langfitt", + "Dady", + "Rannigan", + "Ledwidge", + "Summerton", + "D'Hooghe", + "Ary", + "Gooderick", + "Scarsbrooke", + "Janouch", + "Pond", + "Menichini", + "Crinidge", + "Sneesbie", + "Harflete", + "Ubsdell", + "Littleover", + "Vanne", + "Fassbender", + "Zellner", + "Gorce", + "McKeighan", + "Claffey", + "MacGarvey", + "Norwich", + "Antosch", + "Loughton", + "McCuthais", + "Arnaudi", + "Broz", + "Stert", + "McMechan", + "Texton", + "Bees", + "Couser", + "Easseby", + "McCorry", + "Fetterplace", + "Crankshaw", + "Spancock", + "Neasam", + "Bruckental", + "Badgers", + "Rodda", + "Bossingham", + "Crump", + "Jurgensen", + "Noyes", + "Scarman", + "Bakey", + "Swindin", + "Tolworthie", + "Vynehall", + "Shallcrass", + "Bazoge", + "Jonczyk", + "Eatherton", + "Finlason", + "Hembery", + "Lassetter", + "Soule", + "Baldocci", + "Thurman", + "Poppy", + "Eveque", + "Summerlad", + "Eberle", + "Pettecrew", + "Hitzmann", + "Allonby", + "Bodimeade", + "Catteroll", + "Wooldridge", + "Baines", + "Halloway", + "Doghartie", + "Bracher", + "Kynnd", + "Metherell", + "Routham", + "Fielder", + "Ashleigh", + "Aked", + "Kolakowski", + "Picardo", + "Murdy", + "Feacham", + "Lewin", + "Braben", + "Salaman", + "Letterick", + "Bovaird", + "Moriarty", + "Bertot", + "Cowan", + "Dionisi", + "Maybey", + "Joskowicz", + "Shoutt", + "Bernli", + "Dikles", + "Corringham", + "Shaw", + "Donovin", + "Merigeau", + "Pinckney", + "Queripel", + "Sampson", + "Benfell", + "Cansdell", + "Tasseler", + "Amthor", + "Nancekivell", + "Stock", + "Boltwood", + "Goreisr", + "Le Grand", + "Terrans", + "Knapp", + "Roseman", + "Gunstone", + "Hissie", + "Orto", + "Bell", + "Colam", + "Drust", + "Roseblade", + "Sulman", + "Jennaway", + "Joust", + "Curthoys", + "Cajkler", + "MacIllrick", + "Print", + "Coulthard", + "Lemmon", + "Bush", + "McMurrugh", + "Toping", + "Brute", + "Fryman", + "Bosomworth", + "Lawson", + "Lauder", + "Heinssen", + "Bittlestone", + "Brinson", + "Hambling", + "Vassman", + "Brookbank", + "Bolstridge", + "Leslie", + "Berndsen", + "Aindrais", + "Mogra", + "Wilson", + "Josefs", + "Norgan", + "Wong", + "le Keux", + "Hastwall", + "Bunson", + "Van", + "Waghorne", + "Ojeda", + "Boole", + "Winters", + "Gurge", + "Gallemore", + "Perulli", + "Dight", + "Di Filippo", + "Winsley", + "Chalcraft", + "Human", + "Laetham", + "Lennie", + "McSorley", + "Toolan", + "Brammar", + "Cadogan", + "Molloy", + "Shoveller", + "Vignaux", + "Hannaway", + "Sykora", + "Brealey", + "Harness", + "Profit", + "Goldsbury", + "Brands", + "Godmar", + "Binden", + "Kondratenya", + "Warsap", + "Rumble", + "Maudson", + "Demer", + "Laxtonne", + "Kmietsch", + "Colten", + "Raysdale", + "Gadd", + "Blanche", + "Viant", + "Daskiewicz", + "Macura", + "Crouch", + "Janicijevic", + "Oade", + "Fancourt", + "Dimitriev", + "Earnshaw", + "Wing", + "Fountain", + "Fearey", + "Nottram", + "Bescoby", + "Jeandeau", + "Mapowder", + "Iacobo", + "Rabjohns", + "Dean", + "Whiterod", + "Mathiasen", + "Josephson", + "Boc", + "Olivet", + "Yeardley", + "Labuschagne", + "Curmi", + "Rogger", + "Tesoe", + "Mellhuish", + "Malan", + "McArt", + "Ing", + "Renowden", + "Mellsop", + "Critchlow", + "Seedhouse", + "Tiffin", + "Chirm", + "Oldknow", + "Wolffers", + "Dainter", + "Bundy", + "Copplestone", + "Moses", + "Weedon", + "Borzone", + "Craigg", + "Pyrah", + "Shoorbrooke", + "Jeandeau", + "Halgarth", + "Bamlett", + "Greally", + "Abrahamovitz", + "Oger", + "Mandrake", + "Craigg", + "Stenning", + "Tommei", + "Mapother", + "Cree", + "Clandillon", + "Thorlby", + "Careswell", + "Woolnough", + "McMeekin", + "Woodman", + "Mougin", + "Burchill", + "Pegg", + "Morin", + "Eskriett", + "Gelderd", + "Latham", + "Siney", + "Freen", + "Walrond", + "Bell", + "Twigley", + "D'Souza", + "Anton", + "Doyle", + "Pieters", + "Rosenvasser", + "Mackneis", + "Brisse", + "Boffin", + "Rushe", + "Cozens", + "Bensusan", + "Plampin", + "Gauford", + "Lecky", + "Belton", + "Fleming", + "Gent", + "Bunclark", + "Climar", + "Milner", + "Karolovsky", + "Claesens", + "Oleksiak", + "Barkway", + "Glenister", + "Steynor", + "Hecks", + "Rollo", + "Elcoux", + "Altham", + "Veschambes", + "Livingstone", + "Miroy", + "Edy", + "Bendle", + "Widdall", + "Onions", + "Devita", + "McOwan", + "Ahearne", + "Wisniowski", + "Pask", + "Ciccottini", + "Parlatt", + "Gindghill", + "Marquess", + "Claworth", + "Veel", + "Fairbairn", + "Galletley", + "Glew", + "Gillice", + "Liddyard", + "Babin", + "Ryson", + "Kyteley", + "Toms", + "Downton", + "Mougel", + "Inglefield", + "Gaskins", + "Bradie", + "Stanbury", + "McMenamy", + "Cranstone", + "Thody", + "Iacovozzo", + "Theobalds", + "Perrins", + "Dyott", + "Hupe", + "Gelling", + "Eadington", + "Crumbie", + "Stainsby", + "Kolakowski", + "Norwich", + "Ehrat", + "Basnett", + "Marden", + "Godby", + "Kubacki", + "Wiles", + "Littrick", + "Chuck", + "Negus", + "Aisthorpe", + "Danelut", + "Helversen", + "McCombe", + "Dallender", + "Offner", + "Leser", + "Savin", + "Belcham", + "Pockett", + "Selway", + "Santostefano.", + "Telford", + "Presser", + "Haken", + "Wybourne", + "Reolfo", + "Mineghelli", + "Beverage", + "Grimsdike", + "Drogan", + "Bynert", + "Boothman", + "Postle", + "Baskwell", + "Branno", + "Hechlin", + "Geake", + "Morstatt", + "Towne", + "Phillott", + "Doumerc", + "Ladewig", + "Sexty", + "Sleigh", + "Simonaitis", + "Han", + "Crommett", + "Blowes", + "Floyde", + "Delgardo", + "Brounsell", + "Klimowski", + "Jaffray", + "Kingzeth", + "Pithie", + "Eriksson", + "Gudgin", + "Hamal", + "Hooks", + "Rosle", + "Braysher", + "O'Curneen", + "Millett", + "Woofinden", + "Lillistone", + "Broxis", + "Mochar", + "Drewell", + "Hedgeman", + "Wharf", + "Lambden", + "Lambol", + "Slowcock", + "Cicchillo", + "Trineman", + "Sinyard", + "Brandone", + "Masding", + "Britnell", + "Quinlan", + "Arnopp", + "Jeratt", + "Bantick", + "Craigs", + "Pantling", + "Klais", + "Pickvance", + "Goodwill", + "McGavin", + "Esslemont", + "Bakewell", + "Downer", + "Scallan", + "Ronchka", + "Scholcroft", + "Van Der Walt", + "Armfield", + "Chalker", + "Chinge", + "Yakubov", + "Folkerd", + "Manon", + "Gookey", + "Connold", + "Dusey", + "Muselli", + "Skala", + "Dibbin", + "Kreber", + "De Blasi", + "Drei", + "Argo", + "Maudson", + "Stanlick", + "Steinham", + "Dallewater", + "Litchmore", + "Mathie", + "Gook", + "Forrestor", + "Ferreira", + "Budd", + "Joskowitz", + "Whetnall", + "Beany", + "Keymar", + "Merrin", + "Waldera", + "O'Gleasane", + "Duiged", + "Cumo", + "Giddings", + "Craker", + "Olenov", + "Whayman", + "Raoux", + "Delete", + "McDell", + "Gauntlett", + "Gomby", + "Rottgers", + "Spraggon", + "Orth", + "Shortan", + "Lineen", + "Monkhouse", + "Di Domenico", + "Brinsden", + "MacCallister", + "Sieghard", + "Pheasant", + "Cloney", + "Igglesden", + "Checklin", + "Grosier", + "Garnett", + "Vasnetsov", + "Chsteney", + "Manifield", + "Coutts", + "Bagshawe", + "Pryn", + "Dunstall", + "Rowlings", + "Whines", + "Bish", + "Solomon", + "Mackay", + "Daugherty", + "Gutierrez", + "Goff", + "Villanueva", + "Heath", + "Serrano", + "Munro", + "Levine", + "Barrett", + "Bateman", + "Colon", + "Alford", + "Whitehouse", + "Mendoza", + "Keith", + "Orr", + "Shepherd", + "North", + "Steele", + "Morales", + "Shea", + "Olsen", + "Wormald", + "Torres", + "Haines", + "Kerr", + "Reeves", + "Bates", + "Potts", + "Foreman", + "Herrera", + "Mccoy", + "Fulton", + "Charles", + "Clay", + "Estes", + "Mata", + "Childs", + "Kendall", + "Wallace", + "Thorpe", + "Oconnell", + "Waters", + "Roth", + "Barker", + "Fritz", + "Singleton", + "Sharpe", + "Little", + "Oliver", + "Ayala", + "Khan", + "Braun", + "Dean", + "Stout", + "Adamson", + "Tate", + "Juarez", + "Pickett", + "Burke", + "Gordon", + "Mackenzie", + "Bloggs", + "Read", + "Britton", + "Jefferson", + "Lutz", + "Chen", + "Wagstaff", + "Coates", + "Gilliam", + "Mullins", + "Ryan", + "Moon", + "Thompson", + "Abbott", + "Cotton", + "Barajas", + "Chan", + "Bostock", + "Spencer", + "Sparrow", + "Robinson", + "Morrison", + "Aguirre", + "Clayton", + "Hope", + "Swanson", + "Ochoa", + "Ruiz", + "Truong", + "Gibbons", + "Daniel", + "Zimmerman", + "Flynn", + "Keeling", + "Greenaway", + "Edwards" + ] +} -- cgit v1.2.3 From c654f693eda0db6b12e8bafa6ef0354ca4f43245 Mon Sep 17 00:00:00 2001 From: aruna2019 Date: Thu, 21 Jan 2021 01:13:52 +0530 Subject: Fixed battleship reading uppercases incorrectly. --- bot/exts/evergreen/battleship.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index 9bc374e6..fa3fb35c 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -140,7 +140,7 @@ class Game: @staticmethod def get_square(grid: Grid, square: str) -> Square: """Grabs a square from a grid with an inputted key.""" - index = ord(square[0]) - ord("A") + index = ord(square[0].upper()) - ord("A") number = int(square[1:]) return grid[number-1][index] # -1 since lists are indexed from 0 -- cgit v1.2.3 From ee829251a1f83b8620a76e6c81fe907ada1e1cc1 Mon Sep 17 00:00:00 2001 From: Xithrius <15021300+Xithrius@users.noreply.github.com> Date: Sun, 24 Jan 2021 01:21:24 -0800 Subject: Put a space after the colons for #562 trivia. --- bot/resources/evergreen/trivia_quiz.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json index a5e3a3e4..faa3bc3b 100644 --- a/bot/resources/evergreen/trivia_quiz.json +++ b/bot/resources/evergreen/trivia_quiz.json @@ -250,15 +250,15 @@ }, { "id": 136, - "question":"The United Nations headquarters is located at which city?", - "answer":"New York", - "info":"The United Nations is headquartered in New York City in a complex designed by a board of architects led by Wallace Harrison and built by the architectural firm Harrison & Abramovitz. The complex has served as the official headquarters of the United Nations since its completion in 1951." + "question": "The United Nations headquarters is located at which city?", + "answer": "New York", + "info": "The United Nations is headquartered in New York City in a complex designed by a board of architects led by Wallace Harrison and built by the architectural firm Harrison & Abramovitz. The complex has served as the official headquarters of the United Nations since its completion in 1951." }, { "id": 137, - "question":"At what year did Christopher Columbus discover America?", - "answer":"1492", - "info":"The explorer Christopher Columbus made four trips across the Atlantic Ocean from Spain: in 1492, 1493, 1498 and 1502. He was determined to find a direct water route west from Europe to Asia, but he never did. Instead, he stumbled upon the Americas" + "question": "At what year did Christopher Columbus discover America?", + "answer": "1492", + "info": "The explorer Christopher Columbus made four trips across the Atlantic Ocean from Spain: in 1492, 1493, 1498 and 1502. He was determined to find a direct water route west from Europe to Asia, but he never did. Instead, he stumbled upon the Americas" } ] } -- cgit v1.2.3 From 948a64f3de06b946f7e4c3e1e730dd6849c58ca1 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 03:00:47 -0800 Subject: Refactored the xkcd command, added a refresher. --- bot/exts/evergreen/xkcd.py | 113 ++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 57 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 11477216..5c100bf0 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -1,72 +1,71 @@ import logging -import random +from random import randint +from typing import Dict, Optional, Union -import discord -from discord.ext import commands +from discord import Embed +from discord.ext import tasks +from discord.ext.commands import Cog, Context, command + +from bot.bot import Bot log = logging.getLogger(__name__) +URL = "https://xkcd.com/{0}/info.0.json" +LATEST = "https://xkcd.com/info.0.json" + -class XKCD(commands.Cog): - """A cog for posting the XKCD .""" +class XKCD(Cog): + """Retrieving XKCD comics.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot) -> None: self.bot = bot + self.latest_comic_info: Dict[str, Union[str, int]] = {} + self.get_latest_comic_info.start() - @commands.command(name="xkcd") - async def fetch_xkcd_comics(self, ctx: commands.Context, comic: str = "latest") -> None: - """Read your Fav XKCD comics.""" - if comic not in ["random", "latest"]: - url = f"https://xkcd.com/{comic}/info.0.json" - else: - url = "https://xkcd.com/info.0.json" - - # ---- random choice ----- - if comic == "random": - async with self.bot.http_session.get(url) as r: - json_data = await r.json() - random_pick = random.randint(1, int(json_data["num"])) - url = f"https://xkcd.com/{random_pick}/info.0.json" - - log.trace(f"Querying xkcd API: {url}") - async with self.bot.http_session.get(url) as r: - if r.status == "200": - json_data = await r.json() + def cog_unload(self) -> None: + """Cancels refreshing of the task for refreshing the most recent comic info.""" + self.get_latest_comic_info.cancel() + + @tasks.loop(minutes=30) + async def get_latest_comic_info(self) -> None: + """Refreshes latest comic's information ever 30 minutes. Also used for finding a random comic.""" + async with self.bot.http_session.get(LATEST) as resp: + if resp.status == 200: + self.latest_comic_info = await resp.json() else: - # ----- Exception handling | Guides to use ------ - log.warning(f"Received response {r.status} from: {url}") - # -- get the latest comic number --- - url = f"https://xkcd.com/info.0.json" - async with self.bot.http_session.get(url) as r: - latest_data = await r.json() - - # --- beautify response --- - latest_num = latest_data["num"] - resp = discord.Embed( - title="Guides | Usage", - description=f''' - .xkcd latest (Retrieves the latest comic) - .xkcd random (Retrieves random comic) - .xkcd number (Enter a comic number between 1 & {latest_num}) - ''' - ) - return await ctx.send(embed=resp) - - # --- response variables ---- - day, month, year = json_data["day"], json_data["month"], json_data["year"] - comic_number = json_data["num"] - - # ---- beautify response ---- - embed = discord.Embed( - title=json_data['title'], - description=json_data["alt"] - ) - embed.set_image(url=json_data['img']) - embed.set_footer(text=f"Post date : {day}-{month}-{year} | xkcd comics - {comic_number}") + log.debug(f"Failed to get latest XKCD comic information. Status code {resp.status}") + + @command(name="xkcd") + async def fetch_xkcd_comics(self, ctx: Context, comic: Optional[str]) -> None: + """ + Getting an xkcd comic's information along with the image. + + To get a random comic, don't type any number as an argument. To get the latest, enter 0. + """ + embed = Embed() + + comic = comic or randint(1, self.latest_comic_info['num']) + + if comic == "latest": + info = self.latest_comic_info + + else: + async with self.bot.http_session.get(URL.format(comic)) as resp: + if resp.status == 200: + info = await resp.json() + else: + embed.description = f"{resp.status}: Could not retrieve xkcd comic #{comic}." + log.debug(f"Retrieving xkcd comic #{comic} failed with status code {resp.status}.") + await ctx.send(embed=embed) + return + + embed.set_image(url=info["img"]) + date = f"{info['year']}/{info['month']}/{info['day']}" + embed.set_footer(text=f"{date} - #{comic}, \'{info['safe_title']}\'") await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: - """XKCD Cog load.""" +def setup(bot: Bot) -> None: + """Loading the XKCD cog.""" bot.add_cog(XKCD(bot)) -- cgit v1.2.3 From 075559cddd1ca3d99c5d85e4bc4f01687d845fda Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 03:17:40 -0800 Subject: Added footer comic number for random and latest. --- bot/exts/evergreen/xkcd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 5c100bf0..06c3b4a2 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -61,7 +61,7 @@ class XKCD(Cog): embed.set_image(url=info["img"]) date = f"{info['year']}/{info['month']}/{info['day']}" - embed.set_footer(text=f"{date} - #{comic}, \'{info['safe_title']}\'") + embed.set_footer(text=f"{date} - #{info['num']}, \'{info['safe_title']}\'") await ctx.send(embed=embed) -- cgit v1.2.3 From 2d52b09977b73b5f7583e914ffc5465321548f6a Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 03:23:34 -0800 Subject: Added soft red color if the command fails. --- bot/exts/evergreen/xkcd.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 06c3b4a2..b2f8879a 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -7,6 +7,7 @@ from discord.ext import tasks from discord.ext.commands import Cog, Context, command from bot.bot import Bot +from bot.constants import Colours log = logging.getLogger(__name__) @@ -55,6 +56,7 @@ class XKCD(Cog): info = await resp.json() else: embed.description = f"{resp.status}: Could not retrieve xkcd comic #{comic}." + embed.colour = Colours.soft_red log.debug(f"Retrieving xkcd comic #{comic} failed with status code {resp.status}.") await ctx.send(embed=embed) return -- cgit v1.2.3 From 295f0d33a4257d7d930a4da5ddf2f845f86ac730 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 03:59:09 -0800 Subject: Added handling for comic arguments and interactive comics. --- bot/exts/evergreen/xkcd.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index b2f8879a..cb61e5b8 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -1,4 +1,5 @@ import logging +import re from random import randint from typing import Dict, Optional, Union @@ -11,8 +12,8 @@ from bot.constants import Colours log = logging.getLogger(__name__) -URL = "https://xkcd.com/{0}/info.0.json" -LATEST = "https://xkcd.com/info.0.json" +COMIC_FORMAT = re.compile(r"latest|[0-9]+") +BASE_URL = "https://xkcd.com" class XKCD(Cog): @@ -30,7 +31,7 @@ class XKCD(Cog): @tasks.loop(minutes=30) async def get_latest_comic_info(self) -> None: """Refreshes latest comic's information ever 30 minutes. Also used for finding a random comic.""" - async with self.bot.http_session.get(LATEST) as resp: + async with self.bot.http_session.get(f"{BASE_URL}/info.0.json") as resp: if resp.status == 200: self.latest_comic_info = await resp.json() else: @@ -41,29 +42,42 @@ class XKCD(Cog): """ Getting an xkcd comic's information along with the image. - To get a random comic, don't type any number as an argument. To get the latest, enter 0. + To get a random comic, don't type any number as an argument. To get the latest, type 'latest'. """ - embed = Embed() + embed = Embed(title=f"XKCD comic #{self.latest_comic_info['num'] if comic == 'latest' else comic}") - comic = comic or randint(1, self.latest_comic_info['num']) + embed.colour = Colours.soft_red + + if (comic := re.match(COMIC_FORMAT, comic)) is None: + embed.description = "Inputted comic parameter should either be an integer or 'latest'." + await ctx.send(embed=embed) + return + + comic = comic.group(0) or randint(1, self.latest_comic_info['num']) if comic == "latest": info = self.latest_comic_info else: - async with self.bot.http_session.get(URL.format(comic)) as resp: + async with self.bot.http_session.get(f"{BASE_URL}/{comic}/info.0.json") as resp: if resp.status == 200: info = await resp.json() else: embed.description = f"{resp.status}: Could not retrieve xkcd comic #{comic}." - embed.colour = Colours.soft_red log.debug(f"Retrieving xkcd comic #{comic} failed with status code {resp.status}.") await ctx.send(embed=embed) return - embed.set_image(url=info["img"]) - date = f"{info['year']}/{info['month']}/{info['day']}" - embed.set_footer(text=f"{date} - #{info['num']}, \'{info['safe_title']}\'") + if info["img"][:-3] in ("jpg", "png", "gif"): + embed.set_image(url=info["img"]) + date = f"{info['year']}/{info['month']}/{info['day']}" + embed.set_footer(text=f"{date} - #{info['num']}, \'{info['safe_title']}\'") + embed.colour = Colours.soft_green + else: + embed.description = ( + "Selected comic is interactive, and cannot be displayed within an embed.\n" + f"Comic can be viewed [here](https://xkcd.com/{info['num']})" + ) await ctx.send(embed=embed) -- cgit v1.2.3 From f5ea1ff1a9013df7426abc3076f66d12e64cb6b8 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 04:00:38 -0800 Subject: Grammer formatting. --- bot/exts/evergreen/xkcd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index cb61e5b8..91006715 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -75,8 +75,8 @@ class XKCD(Cog): embed.colour = Colours.soft_green else: embed.description = ( - "Selected comic is interactive, and cannot be displayed within an embed.\n" - f"Comic can be viewed [here](https://xkcd.com/{info['num']})" + "The selected comic is interactive, and cannot be displayed within an embed.\n" + f"Comic can be viewed [here](https://xkcd.com/{info['num']})." ) await ctx.send(embed=embed) -- cgit v1.2.3 From 2c14deb0cd25ca8f1c80fc6e02d321dca1af75d1 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 04:24:09 -0800 Subject: Finished up optimization of statements. --- bot/exts/evergreen/xkcd.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 91006715..e387d3c8 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -44,16 +44,16 @@ class XKCD(Cog): To get a random comic, don't type any number as an argument. To get the latest, type 'latest'. """ - embed = Embed(title=f"XKCD comic #{self.latest_comic_info['num'] if comic == 'latest' else comic}") + embed = Embed(title=f"XKCD comic '{comic}'") embed.colour = Colours.soft_red - if (comic := re.match(COMIC_FORMAT, comic)) is None: + if comic and (comic := re.match(COMIC_FORMAT, comic)) is None: embed.description = "Inputted comic parameter should either be an integer or 'latest'." await ctx.send(embed=embed) return - comic = comic.group(0) or randint(1, self.latest_comic_info['num']) + comic = randint(1, self.latest_comic_info['num']) if comic is None else comic.group(0) if comic == "latest": info = self.latest_comic_info @@ -63,12 +63,15 @@ class XKCD(Cog): if resp.status == 200: info = await resp.json() else: + embed.title = f"XKCD comic #{comic}" embed.description = f"{resp.status}: Could not retrieve xkcd comic #{comic}." log.debug(f"Retrieving xkcd comic #{comic} failed with status code {resp.status}.") await ctx.send(embed=embed) return - if info["img"][:-3] in ("jpg", "png", "gif"): + embed.title = f"XKCD comic #{info['num']}" + + if info["img"][-3:] in ("jpg", "png", "gif"): embed.set_image(url=info["img"]) date = f"{info['year']}/{info['month']}/{info['day']}" embed.set_footer(text=f"{date} - #{info['num']}, \'{info['safe_title']}\'") -- cgit v1.2.3 From 62b1b277146c19ba441b5dcceec073489dd7178a Mon Sep 17 00:00:00 2001 From: Xithrius <15021300+Xithrius@users.noreply.github.com> Date: Sun, 24 Jan 2021 04:38:59 -0800 Subject: Changed comic argument error for fluency of reading. Co-authored-by: ChrisJL --- bot/exts/evergreen/xkcd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index e387d3c8..674ad4b0 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -49,7 +49,7 @@ class XKCD(Cog): embed.colour = Colours.soft_red if comic and (comic := re.match(COMIC_FORMAT, comic)) is None: - embed.description = "Inputted comic parameter should either be an integer or 'latest'." + embed.description = "Comic parameter should either be an integer or 'latest'." await ctx.send(embed=embed) return -- cgit v1.2.3 From a81d30f34dc4d4fa6e8550437e6d329a4da4e746 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 05:41:58 -0800 Subject: Removed newline between if/else statement. --- bot/exts/evergreen/xkcd.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 674ad4b0..d3224bfe 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -57,7 +57,6 @@ class XKCD(Cog): if comic == "latest": info = self.latest_comic_info - else: async with self.bot.http_session.get(f"{BASE_URL}/{comic}/info.0.json") as resp: if resp.status == 200: -- cgit v1.2.3