From c600c9b136e5618da1656615de7e4914bf0cfb35 Mon Sep 17 00:00:00 2001 From: RohanJnr Date: Sun, 26 Jan 2020 20:45:38 +0530 Subject: installed buttons and implemented embed pagination --- bot/seasons/evergreen/reddit.py | 130 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 bot/seasons/evergreen/reddit.py (limited to 'bot') diff --git a/bot/seasons/evergreen/reddit.py b/bot/seasons/evergreen/reddit.py new file mode 100644 index 00000000..79b40900 --- /dev/null +++ b/bot/seasons/evergreen/reddit.py @@ -0,0 +1,130 @@ +import asyncio +import logging +import random +import discord + +from collections import deque + +from discord.ext import commands +from discord.ext import buttons +from discord.ext.commands.cooldowns import BucketType + +log = logging.getLogger(__name__) + + +class Paginator(buttons.Paginator): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class Reddit(commands.Cog): + """Fetches reddit posts.""" + def __init__(self, bot): + self.bot = bot + self.img_cache = deque(maxlen=10) + # self.cache_clear_task = bot.loop.create_task(self.clear_cache()) + + # async def clear_cache(self): + # self.img_cache.clear() + # await asyncio.sleep(43200) # clear cache every 12 hours + + async def fetch(self, session, url): + params = { + 'limit': 50 + } + headers = { + 'User-Agent': 'Iceman' + } + + async with session.get(url=url, params=params, headers=headers) as response: + return await response.json() + + @commands.command(name='reddit') + @commands.cooldown(1, 10, BucketType.user) + async def get_reddit(self, ctx, subreddit='python', sort="hot"): + """ + Fetch reddit posts by using this command. + Gets a post from r/dndmemes by default. + """ + pages=[] + sort_list = ["hot", "new", "top", "rising"] + if sort.lower() not in sort_list: + await ctx.send(f"Invalid sorting: {sort}\nUsing default sorting: `Hot`") + sort = "hot" + + session = self.bot.http_session + data = await self.fetch(session, f'https://www.reddit.com/r/{subreddit}/{sort}/.json') + + try: + posts = data["data"]["children"] + except KeyError: + return await ctx.send('Subreddit not found!') + if not posts: + return await ctx.send('No posts available!') + + if posts[1]["data"]["over_18"] == True: + return await ctx.send("You cannot access this Subreddit.") + + upvote_emoji = self.bot.get_emoji(565557799040319508) + comment_emoji = self.bot.get_emoji(565576076483624960) + user_emoji = "🎅" + + embed_titles = discord.Embed(colour=0xf9f586) + embed_titles.title = f"Posts from {posts[0]['data']['subreddit']} Subreddit\n" + embed_titles.description = "" + + random_posts = [] + while True: + if len(random_posts) == 5: + break + rand_post = random.choice(posts) + if rand_post not in random_posts: + random_posts.append(rand_post) + + + for i, post in enumerate(random_posts, start=1): + post_title = post["data"]["title"][0:50] + post_url = post['data']['url'] + if post_title == "": + post_title = "No Title." + elif post_title == post_url: + post_title = "Title is itself a link." + + embed_titles.description += f"**{i}.**[{post_title}]({post_url})\n" + post_stats = ( + f'{upvote_emoji}{post["data"]["ups"]} ' + f'{comment_emoji}{post["data"]["num_comments"]} ' + f'{user_emoji}{post["data"]["author"]}\n\n' + ) + + embed_titles.description += post_stats + new_embed = discord.Embed() + new_embed.title = post_title + "\n" + new_embed.description = post_stats + "\n\n" + new_embed.description = post['data']['selftext'][0:100] + + if post["data"]["media_embed"] != {}: + content = post["data"]["media_embed"]["content"] + i1 = content.index("src") + i2 = content.index("frameborder") + print(content) + print(i1, i2) + imageURL = content[i1+4:i2] + print(imageURL) + # new_embed.set_image(url=imageURL) + + new_embed.url = post_url + pages.append(new_embed) + + pages.append(embed_titles) + pages.reverse() + embed = Paginator(embed=True, timeout=200, use_defaults=True, + extra_pages=pages) + + await embed.start(ctx) + + +def setup(bot): + bot.add_cog(Reddit(bot)) + log.debug('Loaded') -- cgit v1.2.3 From 64d1e9e2d3bfb2a2f296c7231ffcec5e0dc51ddf Mon Sep 17 00:00:00 2001 From: RohanJnr Date: Wed, 5 Feb 2020 20:24:33 +0530 Subject: finished implementing the reddit command --- bot/seasons/evergreen/reddit.py | 96 ++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 45 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/reddit.py b/bot/seasons/evergreen/reddit.py index 79b40900..90ca57af 100644 --- a/bot/seasons/evergreen/reddit.py +++ b/bot/seasons/evergreen/reddit.py @@ -1,35 +1,24 @@ -import asyncio import logging import random import discord -from collections import deque from discord.ext import commands -from discord.ext import buttons from discord.ext.commands.cooldowns import BucketType -log = logging.getLogger(__name__) - +from bot.pagination import ImagePaginator -class Paginator(buttons.Paginator): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) +log = logging.getLogger(__name__) class Reddit(commands.Cog): """Fetches reddit posts.""" def __init__(self, bot): self.bot = bot - self.img_cache = deque(maxlen=10) - # self.cache_clear_task = bot.loop.create_task(self.clear_cache()) - - # async def clear_cache(self): - # self.img_cache.clear() - # await asyncio.sleep(43200) # clear cache every 12 hours async def fetch(self, session, url): + """Send a get request to the reddit API and get json response""" params = { 'limit': 50 } @@ -45,7 +34,9 @@ class Reddit(commands.Cog): async def get_reddit(self, ctx, subreddit='python', sort="hot"): """ Fetch reddit posts by using this command. - Gets a post from r/dndmemes by default. + Gets a post from r/python by default. + Usage: + --> .reddit [subreddit_name] [hot/top/new] """ pages=[] sort_list = ["hot", "new", "top", "rising"] @@ -70,9 +61,8 @@ class Reddit(commands.Cog): comment_emoji = self.bot.get_emoji(565576076483624960) user_emoji = "🎅" - embed_titles = discord.Embed(colour=0xf9f586) - embed_titles.title = f"Posts from {posts[0]['data']['subreddit']} Subreddit\n" - embed_titles.description = "" + # embed_titles = discord.Embed(colour=0xf9f586) + embed_titles = "" random_posts = [] while True: @@ -82,6 +72,18 @@ class Reddit(commands.Cog): if rand_post not in random_posts: random_posts.append(rand_post) + # ----------------------------------------------------------- + # This code below is bound of change when the emojis are added. + + upvote_emoji = self.bot.get_emoji(674608910656733185) + comment_emoji = self.bot.get_emoji(674608751784755200) + user_emoji = self.bot.get_emoji(674608692418707467) + text_emoji = self.bot.get_emoji(674608836312825877) + video_emoji = self.bot.get_emoji(674608791450550272) + image_emoji = self.bot.get_emoji(674594916655169536) + reddit_emoji = self.bot.get_emoji(674087978628284417) + + # ------------------------------------------------------------ for i, post in enumerate(random_posts, start=1): post_title = post["data"]["title"][0:50] @@ -91,40 +93,44 @@ class Reddit(commands.Cog): elif post_title == post_url: post_title = "Title is itself a link." - embed_titles.description += f"**{i}.**[{post_title}]({post_url})\n" - post_stats = ( - f'{upvote_emoji}{post["data"]["ups"]} ' - f'{comment_emoji}{post["data"]["num_comments"]} ' - f'{user_emoji}{post["data"]["author"]}\n\n' - ) + # ------------------------------------------------------------------ + # Embed building. + + embed_titles += f"**{i}.[{post_title}]({post_url})**\n" + image_url = " " + post_stats = f"{text_emoji}" # Set default content type to text. + + if post["data"]["is_video"] is True or "youtube" in post_url.split("."): + # This means the content type in the post is a video. + post_stats = f"{video_emoji} " - embed_titles.description += post_stats - new_embed = discord.Embed() - new_embed.title = post_title + "\n" - new_embed.description = post_stats + "\n\n" - new_embed.description = post['data']['selftext'][0:100] + elif post_url.endswith("jpg") or post_url.endswith("png") or post_url.endswith("gif"): + # This means the content type in the post is an image. + post_stats = f"{image_emoji} " + image_url = post_url - if post["data"]["media_embed"] != {}: - content = post["data"]["media_embed"]["content"] - i1 = content.index("src") - i2 = content.index("frameborder") - print(content) - print(i1, i2) - imageURL = content[i1+4:i2] - print(imageURL) - # new_embed.set_image(url=imageURL) + votes = f'{upvote_emoji}{post["data"]["ups"]}' + comments = f'{comment_emoji}\u2002{ post["data"]["num_comments"]}' + post_stats += ( + f"\u2002{votes}\u2003" + f"{comments}" + f'\u2003{user_emoji}\u2002{post["data"]["author"]}\n' + ) + embed_titles += f"{post_stats}\n" + page_text = f"**[{post_title}]({post_url})**\n{post_stats}\n{post['data']['selftext'][0:200]}" - new_embed.url = post_url - pages.append(new_embed) + embed = discord.Embed() + page_tuple = (page_text, image_url) + pages.append(page_tuple) - pages.append(embed_titles) - pages.reverse() - embed = Paginator(embed=True, timeout=200, use_defaults=True, - extra_pages=pages) + # ------------------------------------------------------------------ - await embed.start(ctx) + pages.insert(0, (embed_titles, " ")) + embed.set_author(name=f"r/{posts[0]['data']['subreddit']} - {sort}", icon_url=reddit_emoji.url) + await ImagePaginator.paginate(pages, ctx, embed) def setup(bot): + """Load the Cog""" bot.add_cog(Reddit(bot)) log.debug('Loaded') -- cgit v1.2.3 From ec93125eab8a4ec8577f13f81d697a50824a5bd0 Mon Sep 17 00:00:00 2001 From: RohanJnr Date: Wed, 5 Feb 2020 20:44:45 +0530 Subject: uninstalled the buttons lib and corrected lint errors --- Pipfile | 1 - Pipfile.lock | 165 ++++++++------------------- bot/seasons/evergreen/reddit.py | 247 ++++++++++++++++++++-------------------- 3 files changed, 170 insertions(+), 243 deletions(-) (limited to 'bot') diff --git a/Pipfile b/Pipfile index 82f1103d..c066958e 100644 --- a/Pipfile +++ b/Pipfile @@ -11,7 +11,6 @@ discord-py = "~=1.2" fuzzywuzzy = "~=0.17" pillow = "~=6.2" pytz = "~=2019.2" -buttons = "*" [dev-packages] flake8 = "~=3.7" diff --git a/Pipfile.lock b/Pipfile.lock index af8b646e..ee1398e9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "769234aeffdf5a6e781dc783a903771f6100f80450244d5211f0f1a11ec79814" + "sha256": "40f23ea08504def8d3d5f56379820221088d93e9bf81d739850dc97ea8a4b7dc" }, "pipfile-spec": 6, "requires": { @@ -26,30 +26,20 @@ }, "aiohttp": { "hashes": [ - "sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55", - "sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed", - "sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10", - "sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5", - "sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1", - "sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939", - "sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390", - "sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa", - "sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc", - "sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5", - "sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d", - "sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf", - "sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6", - "sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72", - "sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12", - "sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366", - "sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4", - "sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300", - "sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d", - "sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303", - "sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6", - "sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889" - ], - "version": "==3.5.4" + "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", + "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", + "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", + "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", + "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", + "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", + "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", + "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", + "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", + "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", + "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", + "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" + ], + "version": "==3.6.2" }, "arrow": { "hashes": [ @@ -82,14 +72,6 @@ "index": "pypi", "version": "==4.8.2" }, - "buttons": { - "hashes": [ - "sha256:b58fe39c4995db9209443efb6799e22ae43a026d01989d84be033ce4d933cb96", - "sha256:ebf7349ff0e6173ac26f3cfdb92b9fe0c01e73c5dbe5e2874950ed4e6e45bb98" - ], - "index": "pypi", - "version": "==0.1.8" - }, "cffi": { "hashes": [ "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", @@ -137,16 +119,10 @@ }, "discord-py": { "hashes": [ - "sha256:7c843b523bb011062b453864e75c7b675a03faf573c58d14c9f096e85984329d" + "sha256:8bfe5628d31771744000f19135c386c74ac337479d7282c26cc1627b9d31f360" ], "index": "pypi", - "version": "==1.2.5" - }, - "discord.py": { - "hashes": [ - "sha256:7c843b523bb011062b453864e75c7b675a03faf573c58d14c9f096e85984329d" - ], - "version": "==1.2.5" + "version": "==1.3.1" }, "fuzzywuzzy": { "hashes": [ @@ -257,7 +233,6 @@ }, "pycparser": { "hashes": [ - "sha256:9c5019374bb4239e468774725c00be02377cff63cd3f5226af69461ecd5bcdd1", "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], "version": "==2.19" @@ -293,29 +268,30 @@ }, "websockets": { "hashes": [ - "sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136", - "sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6", - "sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1", - "sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538", - "sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4", - "sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908", - "sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0", - "sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d", - "sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c", - "sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d", - "sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c", - "sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb", - "sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf", - "sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e", - "sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96", - "sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584", - "sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484", - "sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d", - "sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559", - "sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff", - "sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454" - ], - "version": "==6.0" + "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", + "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", + "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", + "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", + "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", + "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", + "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", + "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", + "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", + "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", + "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", + "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", + "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", + "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", + "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", + "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", + "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", + "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", + "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", + "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", + "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", + "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" + ], + "version": "==8.1" }, "yarl": { "hashes": [ @@ -434,18 +410,10 @@ }, "identify": { "hashes": [ - "sha256:418f3b2313ac0b531139311a6b426854e9cbdfcfb6175447a5039aa6291d8b30", - "sha256:8ad99ed1f3a965612dcb881435bf58abcfbeb05e230bb8c352b51e8eac103360" - ], - "version": "==1.4.10" - }, - "importlib-metadata": { - "hashes": [ - "sha256:bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359", - "sha256:f17c015735e1a88296994c0697ecea7e11db24290941983b08c9feb30921e6d8" + "sha256:1222b648251bdcb8deb240b294f450fbf704c7984e08baa92507e4ea10b436d5", + "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96" ], - "markers": "python_version < '3.8'", - "version": "==1.4.0" + "version": "==1.4.11" }, "mccabe": { "hashes": [ @@ -454,18 +422,11 @@ ], "version": "==0.6.1" }, - "more-itertools": { - "hashes": [ - "sha256:1a2a32c72400d365000412fe08eb4a24ebee89997c18d3d147544f70f5403b39", - "sha256:c468adec578380b6281a114cb8a5db34eb1116277da92d7c46f904f0b52d3288" - ], - "version": "==8.1.0" - }, "nodeenv": { "hashes": [ - "sha256:561057acd4ae3809e665a9aaaf214afff110bbb6a6d5c8a96121aea6878408b3" + "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212" ], - "version": "==1.3.4" + "version": "==1.3.5" }, "pre-commit": { "hashes": [ @@ -533,46 +494,12 @@ ], "version": "==0.10.0" }, - "typed-ast": { - "hashes": [ - "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", - "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", - "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", - "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", - "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", - "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", - "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", - "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", - "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", - "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", - "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", - "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", - "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", - "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", - "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", - "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", - "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", - "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", - "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", - "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", - "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" - ], - "markers": "python_version < '3.8'", - "version": "==1.4.1" - }, "virtualenv": { "hashes": [ "sha256:0d62c70883c0342d59c11d0ddac0d954d0431321a41ab20851facf2b222598f3", "sha256:55059a7a676e4e19498f1aad09b8313a38fcc0cdbe4fdddc0e9b06946d21b4bb" ], "version": "==16.7.9" - }, - "zipp": { - "hashes": [ - "sha256:57147f6b0403b59f33fd357f169f860e031303415aeb7d04ede4839d23905ab8", - "sha256:7ae5ccaca427bafa9760ac3cd8f8c244bfc259794b5b6bb9db4dda2241575d09" - ], - "version": "==2.0.0" } } } diff --git a/bot/seasons/evergreen/reddit.py b/bot/seasons/evergreen/reddit.py index 90ca57af..8a7ebf26 100644 --- a/bot/seasons/evergreen/reddit.py +++ b/bot/seasons/evergreen/reddit.py @@ -1,8 +1,7 @@ import logging import random -import discord - +import discord from discord.ext import commands from discord.ext.commands.cooldowns import BucketType @@ -13,124 +12,126 @@ log = logging.getLogger(__name__) class Reddit(commands.Cog): - """Fetches reddit posts.""" - def __init__(self, bot): - self.bot = bot - - async def fetch(self, session, url): - """Send a get request to the reddit API and get json response""" - params = { - 'limit': 50 - } - headers = { - 'User-Agent': 'Iceman' - } - - async with session.get(url=url, params=params, headers=headers) as response: - return await response.json() - - @commands.command(name='reddit') - @commands.cooldown(1, 10, BucketType.user) - async def get_reddit(self, ctx, subreddit='python', sort="hot"): - """ - Fetch reddit posts by using this command. - Gets a post from r/python by default. - Usage: - --> .reddit [subreddit_name] [hot/top/new] - """ - pages=[] - sort_list = ["hot", "new", "top", "rising"] - if sort.lower() not in sort_list: - await ctx.send(f"Invalid sorting: {sort}\nUsing default sorting: `Hot`") - sort = "hot" - - session = self.bot.http_session - data = await self.fetch(session, f'https://www.reddit.com/r/{subreddit}/{sort}/.json') - - try: - posts = data["data"]["children"] - except KeyError: - return await ctx.send('Subreddit not found!') - if not posts: - return await ctx.send('No posts available!') - - if posts[1]["data"]["over_18"] == True: - return await ctx.send("You cannot access this Subreddit.") - - upvote_emoji = self.bot.get_emoji(565557799040319508) - comment_emoji = self.bot.get_emoji(565576076483624960) - user_emoji = "🎅" - - # embed_titles = discord.Embed(colour=0xf9f586) - embed_titles = "" - - random_posts = [] - while True: - if len(random_posts) == 5: - break - rand_post = random.choice(posts) - if rand_post not in random_posts: - random_posts.append(rand_post) - - # ----------------------------------------------------------- - # This code below is bound of change when the emojis are added. - - upvote_emoji = self.bot.get_emoji(674608910656733185) - comment_emoji = self.bot.get_emoji(674608751784755200) - user_emoji = self.bot.get_emoji(674608692418707467) - text_emoji = self.bot.get_emoji(674608836312825877) - video_emoji = self.bot.get_emoji(674608791450550272) - image_emoji = self.bot.get_emoji(674594916655169536) - reddit_emoji = self.bot.get_emoji(674087978628284417) - - # ------------------------------------------------------------ - - for i, post in enumerate(random_posts, start=1): - post_title = post["data"]["title"][0:50] - post_url = post['data']['url'] - if post_title == "": - post_title = "No Title." - elif post_title == post_url: - post_title = "Title is itself a link." - - # ------------------------------------------------------------------ - # Embed building. - - embed_titles += f"**{i}.[{post_title}]({post_url})**\n" - image_url = " " - post_stats = f"{text_emoji}" # Set default content type to text. - - if post["data"]["is_video"] is True or "youtube" in post_url.split("."): - # This means the content type in the post is a video. - post_stats = f"{video_emoji} " - - elif post_url.endswith("jpg") or post_url.endswith("png") or post_url.endswith("gif"): - # This means the content type in the post is an image. - post_stats = f"{image_emoji} " - image_url = post_url - - votes = f'{upvote_emoji}{post["data"]["ups"]}' - comments = f'{comment_emoji}\u2002{ post["data"]["num_comments"]}' - post_stats += ( - f"\u2002{votes}\u2003" - f"{comments}" - f'\u2003{user_emoji}\u2002{post["data"]["author"]}\n' - ) - embed_titles += f"{post_stats}\n" - page_text = f"**[{post_title}]({post_url})**\n{post_stats}\n{post['data']['selftext'][0:200]}" - - embed = discord.Embed() - page_tuple = (page_text, image_url) - pages.append(page_tuple) - - # ------------------------------------------------------------------ - - pages.insert(0, (embed_titles, " ")) - embed.set_author(name=f"r/{posts[0]['data']['subreddit']} - {sort}", icon_url=reddit_emoji.url) - await ImagePaginator.paginate(pages, ctx, embed) - - -def setup(bot): - """Load the Cog""" - bot.add_cog(Reddit(bot)) - log.debug('Loaded') + """Fetches reddit posts.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + async def fetch(self, url: str) -> dict: + """Send a get request to the reddit API and get json response.""" + session = self.bot.http_session + params = { + 'limit': 50 + } + headers = { + 'User-Agent': 'Iceman' + } + + async with session.get(url=url, params=params, headers=headers) as response: + return await response.json() + + @commands.command(name='reddit') + @commands.cooldown(1, 10, BucketType.user) + async def get_reddit(self, ctx: commands.Context, subreddit: str = 'python', sort: str = "hot") -> None: + """ + Fetch reddit posts by using this command. + + Gets a post from r/python by default. + Usage: + --> .reddit [subreddit_name] [hot/top/new] + """ + pages = [] + sort_list = ["hot", "new", "top", "rising"] + if sort.lower() not in sort_list: + await ctx.send(f"Invalid sorting: {sort}\nUsing default sorting: `Hot`") + sort = "hot" + + data = await self.fetch(f'https://www.reddit.com/r/{subreddit}/{sort}/.json') + + try: + posts = data["data"]["children"] + except KeyError: + return await ctx.send('Subreddit not found!') + if not posts: + return await ctx.send('No posts available!') + + if posts[1]["data"]["over_18"] is True: + return await ctx.send("You cannot access this Subreddit.") + + upvote_emoji = self.bot.get_emoji(565557799040319508) + comment_emoji = self.bot.get_emoji(565576076483624960) + user_emoji = "🎅" + + # embed_titles = discord.Embed(colour=0xf9f586) + embed_titles = "" + + random_posts = [] + while True: + if len(random_posts) == 5: + break + rand_post = random.choice(posts) + if rand_post not in random_posts: + random_posts.append(rand_post) + + # ----------------------------------------------------------- + # This code below is bound of change when the emojis are added. + + upvote_emoji = self.bot.get_emoji(674608910656733185) + comment_emoji = self.bot.get_emoji(674608751784755200) + user_emoji = self.bot.get_emoji(674608692418707467) + text_emoji = self.bot.get_emoji(674608836312825877) + video_emoji = self.bot.get_emoji(674608791450550272) + image_emoji = self.bot.get_emoji(674594916655169536) + reddit_emoji = self.bot.get_emoji(674087978628284417) + + # ------------------------------------------------------------ + + for i, post in enumerate(random_posts, start=1): + post_title = post["data"]["title"][0:50] + post_url = post['data']['url'] + if post_title == "": + post_title = "No Title." + elif post_title == post_url: + post_title = "Title is itself a link." + + # ------------------------------------------------------------------ + # Embed building. + + embed_titles += f"**{i}.[{post_title}]({post_url})**\n" + image_url = " " + post_stats = f"{text_emoji}" # Set default content type to text. + + if post["data"]["is_video"] is True or "youtube" in post_url.split("."): + # This means the content type in the post is a video. + post_stats = f"{video_emoji} " + + elif post_url.endswith("jpg") or post_url.endswith("png") or post_url.endswith("gif"): + # This means the content type in the post is an image. + post_stats = f"{image_emoji} " + image_url = post_url + + votes = f'{upvote_emoji}{post["data"]["ups"]}' + comments = f'{comment_emoji}\u2002{ post["data"]["num_comments"]}' + post_stats += ( + f"\u2002{votes}\u2003" + f"{comments}" + f'\u2003{user_emoji}\u2002{post["data"]["author"]}\n' + ) + embed_titles += f"{post_stats}\n" + page_text = f"**[{post_title}]({post_url})**\n{post_stats}\n{post['data']['selftext'][0:200]}" + + embed = discord.Embed() + page_tuple = (page_text, image_url) + pages.append(page_tuple) + + # ------------------------------------------------------------------ + + pages.insert(0, (embed_titles, " ")) + embed.set_author(name=f"r/{posts[0]['data']['subreddit']} - {sort}", icon_url=reddit_emoji.url) + await ImagePaginator.paginate(pages, ctx, embed) + + +def setup(bot: commands.Bot) -> None: + """Load the Cog.""" + bot.add_cog(Reddit(bot)) + log.debug('Loaded') -- cgit v1.2.3 From d823a3c582040e0d8b2f8f43ff8da752388bbd3d Mon Sep 17 00:00:00 2001 From: RohanJnr Date: Sun, 9 Feb 2020 11:31:58 +0530 Subject: Removed un-wanted code and using random.simple() to select 5 random unique reddit posts insted of using a while loop --- bot/seasons/evergreen/reddit.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/reddit.py b/bot/seasons/evergreen/reddit.py index 8a7ebf26..811f5781 100644 --- a/bot/seasons/evergreen/reddit.py +++ b/bot/seasons/evergreen/reddit.py @@ -56,22 +56,15 @@ class Reddit(commands.Cog): return await ctx.send('No posts available!') if posts[1]["data"]["over_18"] is True: - return await ctx.send("You cannot access this Subreddit.") - - upvote_emoji = self.bot.get_emoji(565557799040319508) - comment_emoji = self.bot.get_emoji(565576076483624960) - user_emoji = "🎅" + return await ctx.send( + "You cannot access this Subreddit as it is ment for those who " + "are 18 years or older." + ) - # embed_titles = discord.Embed(colour=0xf9f586) embed_titles = "" - random_posts = [] - while True: - if len(random_posts) == 5: - break - rand_post = random.choice(posts) - if rand_post not in random_posts: - random_posts.append(rand_post) + # Chooses k unique random elements from a population sequence or set. + random_posts = random.sample(posts, k=5) # ----------------------------------------------------------- # This code below is bound of change when the emojis are added. -- cgit v1.2.3 From e21938e663b4a210c059d3875ca4d3c92b76eeb7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Sun, 9 Feb 2020 13:26:37 +0100 Subject: Add correct emoji IDs to Evergreen reddit cog The emoji IDs in the Reddit cog were for emojis that are not available to Seasonal Bot. While I think a proper solution using constants is to be preferred, I've edited in IDs for emojis that can be used by the bot in the mean time. I've reused three of the emojis that we were already using for our Reddit webhook and added four new emojis to the `PyDis Emoji II` server. Reused emojis: :user: 638729835442602003 :upvotes: 638729835245731840 :comments: 638729835073765387 New emojis available on PyDis Emoji II: :reddit_logo: 676030265734332427 :reddit_post_photo: 676030265734201344 :reddit_post_text: 676030265910493204 :reddit_post_video: 676030265839190047 The latter four may need to be restyled to match the first three in the future. --- bot/seasons/evergreen/reddit.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/reddit.py b/bot/seasons/evergreen/reddit.py index 811f5781..32ca419a 100644 --- a/bot/seasons/evergreen/reddit.py +++ b/bot/seasons/evergreen/reddit.py @@ -69,13 +69,13 @@ class Reddit(commands.Cog): # ----------------------------------------------------------- # This code below is bound of change when the emojis are added. - upvote_emoji = self.bot.get_emoji(674608910656733185) - comment_emoji = self.bot.get_emoji(674608751784755200) - user_emoji = self.bot.get_emoji(674608692418707467) - text_emoji = self.bot.get_emoji(674608836312825877) - video_emoji = self.bot.get_emoji(674608791450550272) - image_emoji = self.bot.get_emoji(674594916655169536) - reddit_emoji = self.bot.get_emoji(674087978628284417) + upvote_emoji = self.bot.get_emoji(638729835245731840) + comment_emoji = self.bot.get_emoji(638729835073765387) + user_emoji = self.bot.get_emoji(638729835442602003) + text_emoji = self.bot.get_emoji(676030265910493204) + video_emoji = self.bot.get_emoji(676030265839190047) + image_emoji = self.bot.get_emoji(676030265734201344) + reddit_emoji = self.bot.get_emoji(676030265734332427) # ------------------------------------------------------------ -- cgit v1.2.3 From 90dc6da4c1460a4f6c5d3ef5ea8bb8a403e28785 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 12 Feb 2020 20:47:17 +0200 Subject: Added .movie command with what when specifing genre you get random movies, depending how much movies you define. This use TMDB API. --- bot/seasons/evergreen/movie.py | 481 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 481 insertions(+) create mode 100644 bot/seasons/evergreen/movie.py (limited to 'bot') diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py new file mode 100644 index 00000000..1256cb45 --- /dev/null +++ b/bot/seasons/evergreen/movie.py @@ -0,0 +1,481 @@ +import logging +import random +from os import environ +from typing import List, Tuple +from urllib.parse import urlencode + +from aiohttp import ClientSession +from discord import Embed +from discord.ext.commands import Bot, Cog, Context, group + +from bot.pagination import ImagePaginator + +# Get TMDB API key from .env +TMDB_API_KEY = environ.get('TMDB_API_KEY') + +# Define base URL of TMDB +BASE_URL = "https://api.themoviedb.org/3/" + +# Get logger +logger = logging.getLogger(__name__) + + +async def get_random_movies(client: ClientSession, + count: int, + genre_id: int, + genre_name: str,) \ + -> Tuple[List[Tuple[str, str]], Embed]: + """Get random movies by genre from TMDB.""" + pages = [] + + # Create embed + embed = Embed(title=f"Random {genre_name} Movies") + embed.set_footer(text='Powered by TMDB (themoviedb.org)') + + # Get random page between 1 and 600 + page = random.randint(1, 200) + + # Define TMDB request parameters + # (API key, exclusions, inclusions, sort) + params = { + "api_key": TMDB_API_KEY, + "language": "en-US", + "sort_by": "popularity.desc", + "include_adult": "false", + "include_video": "false", + "page": page, + "with_genres": str(genre_id) + } + + # Create request URL + url = BASE_URL + "discover/movie" + "?" + urlencode(params) + + # Do discover request to TMDB API and fetch information + async with client.get(url) as res: + try: + # Parse response data from JSON to dictionary + movie_list_data = await res.json() + + # Loop and fetch movies + for i in range(count): + # Get movie ID + movie_id = movie_list_data["results"][i]["id"] + + # Create movie params + movie_params = { + "api_key": TMDB_API_KEY, + "language": "en-US" + } + + # Generate URL + movie_url = BASE_URL + f"movie/{movie_id}?" + urlencode( + movie_params) + + # Fetch movie + async with client.get(movie_url) as m_res: + # Parse JSON to dict + movie_data = await m_res.json() + + # Create embed text + movie_text = "" + + # Add Title and tagline + movie_text += f"**{movie_data['title']}**\n" + if movie_data['tagline'] != "": + movie_text += movie_data['tagline'] + "\n\n" + else: + movie_text += "\n" + + # Add movie rating and release date + movie_text += f"**Rating:** {movie_data['vote_average']}/10 :star:\n" + movie_text += f"**Release Date:** {movie_data['release_date']}\n\n" + + # Add production title + movie_text += "__**Production Information**__\n" + + companies = movie_data['production_companies'] + countries = movie_data['production_countries'] + + # Add production information + movie_text += f"""**Made by:** {', '.join([comp['name'] + for comp in companies])}\n""" + movie_text += f"""**Made in:** {', '.join([country['name'] + for country in countries])}\n\n""" + + # Add Some Numbers title + movie_text += "__**Some Numbers**__\n" + + # Add Budget, Revenue and Duration + movie_text += f"**Budget:** ${movie_data['budget'] if movie_data['budget'] != 0 else '?'}\n" + movie_text += f"**Revenue:** ${movie_data['revenue'] if movie_data['revenue'] != 0 else '?'}\n" + movie_text += f"**Duration:** {movie_data['runtime']} minutes\n\n" + + # Add description + movie_text += movie_data['overview'] + + # Define Movie Image URL + movie_img_url = f"http://image.tmdb.org/t/p/w200{movie_data['poster_path']}" + + # Append this page to pages + pages.append((movie_text, movie_img_url)) + except KeyError as err: + # Create error message + msg = f"There was KeyError while executing HTTP request. API may " \ + f"down or API key may be incorrect, however, some movies " \ + f"have some missing fields, and this error will raise this " \ + f"too. Problematic Key: \n```{err}``` " + + # Create error embed + err_embed = Embed(title=":no_entry: Error :no_entry:") + + # Log error + logger.warning(msg) + + # Return error message + embed + return [(msg, "")], err_embed + + # Return all movies pages + return pages, embed + + +class Movie(Cog): + """Movie Cog contains movies command that grab random movies from TMDB.""" + + def __init__(self, bot: Bot): + self.bot = bot + self.http_session: ClientSession = bot.http_session + + @group(name='movies', invoke_without_command=True) + async def movies(self, ctx: Context) -> None: + """Get random movies by specifing genre.""" + await ctx.send_help('movies') + + @movies.command(name='action') + async def action(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Action genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 28, "Action") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='adventure') + async def adventure(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Adventure genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 12, "Adventure") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='animation') + async def animation(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Animation genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 16, "Animation") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='comedy') + async def comedy(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Comedy genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 35, "Comedy") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='crime') + async def crime(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Crime genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 80, "Crime") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='documentary') + async def documentary(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Documentary genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 99, "Documentary") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='drama') + async def drama(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Drama genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 18, "Drama") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='family') + async def family(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Drama genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 10751, "Family") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='fantasy') + async def fantasy(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Fantasy genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 14, "Fantasy") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='history') + async def history(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random History genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 36, "History") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='horror') + async def horror(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Horror genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 27, "Horror") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='music') + async def music(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Music genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 10402, "Music") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='mystery') + async def mystery(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Mystery genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 9648, "Mystery") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='romance') + async def romance(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Romance genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 10749, "Romance") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='science') + async def science(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Science Fiction genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 878, "Science Fiction") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='thriller') + async def thriller(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Thriller genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 53, + "Thriller") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + @movies.command(name='western') + async def western(self, ctx: Context, movies_amount: int = 5) -> None: + """Get random Western genre movies.""" + # Count can't be higher than 20, due one page return 20 items + # And also this can't be lower than 1, just logic + if movies_amount > 20: + await ctx.send("Max allowed amount of movies is 20.") + return + elif movies_amount < 1: + await ctx.send("You can't get less movies than 1.") + return + + # Get pages and embed + pages, embed = await get_random_movies(self.http_session, + movies_amount, 37, + "Western") + + # Paginate + await ImagePaginator.paginate(pages, ctx, embed) + + +def setup(bot: Bot) -> None: + """Load Movie Cog.""" + bot.add_cog(Movie(bot)) -- cgit v1.2.3 From bf80c41ee63b9f0dccc6fbb1b1e4aa9ee2b10eb1 Mon Sep 17 00:00:00 2001 From: ks123 Date: Fri, 14 Feb 2020 18:47:31 +0200 Subject: Moved .movies command genres from subcommands to if-elif-else statement, added alias .movie --- bot/seasons/evergreen/movie.py | 424 ++++++++++------------------------------- 1 file changed, 96 insertions(+), 328 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py index 1256cb45..96c43469 100644 --- a/bot/seasons/evergreen/movie.py +++ b/bot/seasons/evergreen/movie.py @@ -6,7 +6,7 @@ from urllib.parse import urlencode from aiohttp import ClientSession from discord import Embed -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Bot, Cog, Context, command from bot.pagination import ImagePaginator @@ -32,7 +32,7 @@ async def get_random_movies(client: ClientSession, embed = Embed(title=f"Random {genre_name} Movies") embed.set_footer(text='Powered by TMDB (themoviedb.org)') - # Get random page between 1 and 600 + # Get random page between 1 and 200 page = random.randint(1, 200) # Define TMDB request parameters @@ -145,334 +145,102 @@ class Movie(Cog): self.bot = bot self.http_session: ClientSession = bot.http_session - @group(name='movies', invoke_without_command=True) - async def movies(self, ctx: Context) -> None: - """Get random movies by specifing genre.""" - await ctx.send_help('movies') - - @movies.command(name='action') - async def action(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Action genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") + @command(name='movies', aliases=['movie']) + async def movies(self, ctx: Context, genre: str = "", amount: int = 5) -> None: + """ + Get random movies by specifing genre. + + Also support amount parameter, + that define how much movies will be shown. Default 5 + + Available genres: + - Action + - Adventure + - Animation + - Comedy + - Crime + - Documentary + - Drama + - Family + - Fantasy + - History + - Horror + - Music + - Mystery + - Romance + - Science + - Thriller + - Western + """ + # Check is there more than 20 movies specified, due TMDB return 20 movies + # per page, so this is max. Also you can't get less movies than 1, just logic + if amount > 20: + await ctx.send('You can\'t get more than 20 movies at once. (TMDB limits)') + return + elif amount < 1: + await ctx.send('You can\'t get less than 1 movies. Just logic.') + return + + # Make genre to lower characters due then users may use also like Genre + # or GENRE formats + genre = genre.lower() + + # Check genres, get pages and embed + if genre == 'action': + pages, embed = await get_random_movies(self.http_session, + amount, 28, "Action") + elif genre == 'adventure': + pages, embed = await get_random_movies(self.http_session, + amount, 12, "Adventure") + elif genre == 'animation': + pages, embed = await get_random_movies(self.http_session, + amount, 16, "Animation") + elif genre == 'comedy': + pages, embed = await get_random_movies(self.http_session, + amount, 35, "Comedy") + elif genre == 'crime': + pages, embed = await get_random_movies(self.http_session, + amount, 80, "Crime") + elif genre == 'documentary': + pages, embed = await get_random_movies(self.http_session, + amount, 99, "Documentary") + elif genre == 'drama': + pages, embed = await get_random_movies(self.http_session, + amount, 18, "Drama") + elif genre == 'family': + pages, embed = await get_random_movies(self.http_session, + amount, 10751, "Family") + elif genre == 'fantasy': + pages, embed = await get_random_movies(self.http_session, + amount, 14, "Fantasy") + elif genre == 'history': + pages, embed = await get_random_movies(self.http_session, + amount, 36, "History") + elif genre == 'horror': + pages, embed = await get_random_movies(self.http_session, + amount, 27, "Horror") + elif genre == 'music': + pages, embed = await get_random_movies(self.http_session, + amount, 10402, "Music") + elif genre == 'mystery': + pages, embed = await get_random_movies(self.http_session, + amount, 9648, "Mystery") + elif genre == 'romance': + pages, embed = await get_random_movies(self.http_session, + amount, 10749, "Romance") + elif genre == 'science': + pages, embed = await get_random_movies(self.http_session, + amount, 878, "Science Fiction") + elif genre == 'thriller': + pages, embed = await get_random_movies(self.http_session, + amount, 53, "Thriller") + elif genre == 'western': + pages, embed = await get_random_movies(self.http_session, + amount, 37, "Western") + else: + await ctx.send_help('movies') return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 28, "Action") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='adventure') - async def adventure(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Adventure genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 12, "Adventure") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='animation') - async def animation(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Animation genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 16, "Animation") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='comedy') - async def comedy(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Comedy genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 35, "Comedy") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='crime') - async def crime(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Crime genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 80, "Crime") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='documentary') - async def documentary(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Documentary genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 99, "Documentary") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='drama') - async def drama(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Drama genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 18, "Drama") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='family') - async def family(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Drama genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 10751, "Family") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='fantasy') - async def fantasy(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Fantasy genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 14, "Fantasy") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='history') - async def history(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random History genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 36, "History") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='horror') - async def horror(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Horror genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 27, "Horror") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='music') - async def music(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Music genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 10402, "Music") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='mystery') - async def mystery(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Mystery genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 9648, "Mystery") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='romance') - async def romance(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Romance genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 10749, "Romance") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='science') - async def science(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Science Fiction genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 878, "Science Fiction") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='thriller') - async def thriller(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Thriller genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 53, - "Thriller") - - # Paginate - await ImagePaginator.paginate(pages, ctx, embed) - - @movies.command(name='western') - async def western(self, ctx: Context, movies_amount: int = 5) -> None: - """Get random Western genre movies.""" - # Count can't be higher than 20, due one page return 20 items - # And also this can't be lower than 1, just logic - if movies_amount > 20: - await ctx.send("Max allowed amount of movies is 20.") - return - elif movies_amount < 1: - await ctx.send("You can't get less movies than 1.") - return - - # Get pages and embed - pages, embed = await get_random_movies(self.http_session, - movies_amount, 37, - "Western") - # Paginate await ImagePaginator.paginate(pages, ctx, embed) -- cgit v1.2.3 From 02aa5119a930c522fb56834c4b3727f0722d7b38 Mon Sep 17 00:00:00 2001 From: ks123 Date: Fri, 14 Feb 2020 19:21:29 +0200 Subject: Made .movies command genres getting ID and name check to Enum, also made things more dynamical. --- bot/seasons/evergreen/movie.py | 90 ++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 57 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py index 96c43469..8ae49cd2 100644 --- a/bot/seasons/evergreen/movie.py +++ b/bot/seasons/evergreen/movie.py @@ -1,5 +1,6 @@ import logging import random +from enum import Enum from os import environ from typing import List, Tuple from urllib.parse import urlencode @@ -20,6 +21,29 @@ BASE_URL = "https://api.themoviedb.org/3/" logger = logging.getLogger(__name__) +# Genres with TMDB API IDs +class MovieGenres(Enum): + """Genre names and IDs.""" + + Action = 28 + Adventure = 12 + Animation = 16 + Comedy = 35 + Crime = 80 + Documentary = 99 + Drama = 18 + Family = 10751 + Fantasy = 14 + History = 36 + Horror = 27 + Music = 10402 + Mystery = 9648 + Romance = 10749 + Science = 878 + Thriller = 53 + Western = 37 + + async def get_random_movies(client: ClientSession, count: int, genre_id: int, @@ -181,66 +205,18 @@ class Movie(Cog): await ctx.send('You can\'t get less than 1 movies. Just logic.') return - # Make genre to lower characters due then users may use also like Genre - # or GENRE formats - genre = genre.lower() - - # Check genres, get pages and embed - if genre == 'action': - pages, embed = await get_random_movies(self.http_session, - amount, 28, "Action") - elif genre == 'adventure': - pages, embed = await get_random_movies(self.http_session, - amount, 12, "Adventure") - elif genre == 'animation': - pages, embed = await get_random_movies(self.http_session, - amount, 16, "Animation") - elif genre == 'comedy': - pages, embed = await get_random_movies(self.http_session, - amount, 35, "Comedy") - elif genre == 'crime': - pages, embed = await get_random_movies(self.http_session, - amount, 80, "Crime") - elif genre == 'documentary': - pages, embed = await get_random_movies(self.http_session, - amount, 99, "Documentary") - elif genre == 'drama': - pages, embed = await get_random_movies(self.http_session, - amount, 18, "Drama") - elif genre == 'family': - pages, embed = await get_random_movies(self.http_session, - amount, 10751, "Family") - elif genre == 'fantasy': - pages, embed = await get_random_movies(self.http_session, - amount, 14, "Fantasy") - elif genre == 'history': - pages, embed = await get_random_movies(self.http_session, - amount, 36, "History") - elif genre == 'horror': - pages, embed = await get_random_movies(self.http_session, - amount, 27, "Horror") - elif genre == 'music': - pages, embed = await get_random_movies(self.http_session, - amount, 10402, "Music") - elif genre == 'mystery': - pages, embed = await get_random_movies(self.http_session, - amount, 9648, "Mystery") - elif genre == 'romance': - pages, embed = await get_random_movies(self.http_session, - amount, 10749, "Romance") - elif genre == 'science': - pages, embed = await get_random_movies(self.http_session, - amount, 878, "Science Fiction") - elif genre == 'thriller': - pages, embed = await get_random_movies(self.http_session, - amount, 53, "Thriller") - elif genre == 'western': - pages, embed = await get_random_movies(self.http_session, - amount, 37, "Western") - else: + # Capitalize genre for getting data from Enum + genre = genre.capitalize() + + # If invalid genre, send help message + if genre not in MovieGenres.__members__: await ctx.send_help('movies') return + # Get pages and embed of movies + pages, embed = await get_random_movies(self.http_session, amount, MovieGenres[genre].value, genre) + + # Send movies, paginate await ImagePaginator.paginate(pages, ctx, embed) -- cgit v1.2.3 From f0d1cf5a25bfb435a19eca5fb16da490596cc429 Mon Sep 17 00:00:00 2001 From: ks123 Date: Fri, 14 Feb 2020 19:52:12 +0200 Subject: Replaced check from Enum members to try-except block. --- bot/seasons/evergreen/movie.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py index 8ae49cd2..e7c4c73c 100644 --- a/bot/seasons/evergreen/movie.py +++ b/bot/seasons/evergreen/movie.py @@ -208,14 +208,13 @@ class Movie(Cog): # Capitalize genre for getting data from Enum genre = genre.capitalize() - # If invalid genre, send help message - if genre not in MovieGenres.__members__: + # Try to fetch pages and embed, when invalid genre, show help + try: + pages, embed = await get_random_movies(self.http_session, amount, MovieGenres[genre].value, genre) + except KeyError: await ctx.send_help('movies') return - # Get pages and embed of movies - pages, embed = await get_random_movies(self.http_session, amount, MovieGenres[genre].value, genre) - # Send movies, paginate await ImagePaginator.paginate(pages, ctx, embed) -- cgit v1.2.3 From cce99c584364ea749a542d5c8fcbe2af8ea3c075 Mon Sep 17 00:00:00 2001 From: ks123 Date: Sat, 15 Feb 2020 10:27:12 +0200 Subject: Moved get_random_movies to Movie cog and made this to smaller functions. --- bot/seasons/evergreen/movie.py | 269 ++++++++++++++++++++--------------------- 1 file changed, 128 insertions(+), 141 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py index e7c4c73c..fc0053fd 100644 --- a/bot/seasons/evergreen/movie.py +++ b/bot/seasons/evergreen/movie.py @@ -2,7 +2,7 @@ import logging import random from enum import Enum from os import environ -from typing import List, Tuple +from typing import Dict, List, Tuple from urllib.parse import urlencode from aiohttp import ClientSession @@ -25,141 +25,23 @@ logger = logging.getLogger(__name__) class MovieGenres(Enum): """Genre names and IDs.""" - Action = 28 - Adventure = 12 - Animation = 16 - Comedy = 35 - Crime = 80 - Documentary = 99 - Drama = 18 - Family = 10751 - Fantasy = 14 - History = 36 - Horror = 27 - Music = 10402 - Mystery = 9648 - Romance = 10749 - Science = 878 - Thriller = 53 - Western = 37 - - -async def get_random_movies(client: ClientSession, - count: int, - genre_id: int, - genre_name: str,) \ - -> Tuple[List[Tuple[str, str]], Embed]: - """Get random movies by genre from TMDB.""" - pages = [] - - # Create embed - embed = Embed(title=f"Random {genre_name} Movies") - embed.set_footer(text='Powered by TMDB (themoviedb.org)') - - # Get random page between 1 and 200 - page = random.randint(1, 200) - - # Define TMDB request parameters - # (API key, exclusions, inclusions, sort) - params = { - "api_key": TMDB_API_KEY, - "language": "en-US", - "sort_by": "popularity.desc", - "include_adult": "false", - "include_video": "false", - "page": page, - "with_genres": str(genre_id) - } - - # Create request URL - url = BASE_URL + "discover/movie" + "?" + urlencode(params) - - # Do discover request to TMDB API and fetch information - async with client.get(url) as res: - try: - # Parse response data from JSON to dictionary - movie_list_data = await res.json() - - # Loop and fetch movies - for i in range(count): - # Get movie ID - movie_id = movie_list_data["results"][i]["id"] - - # Create movie params - movie_params = { - "api_key": TMDB_API_KEY, - "language": "en-US" - } - - # Generate URL - movie_url = BASE_URL + f"movie/{movie_id}?" + urlencode( - movie_params) - - # Fetch movie - async with client.get(movie_url) as m_res: - # Parse JSON to dict - movie_data = await m_res.json() - - # Create embed text - movie_text = "" - - # Add Title and tagline - movie_text += f"**{movie_data['title']}**\n" - if movie_data['tagline'] != "": - movie_text += movie_data['tagline'] + "\n\n" - else: - movie_text += "\n" - - # Add movie rating and release date - movie_text += f"**Rating:** {movie_data['vote_average']}/10 :star:\n" - movie_text += f"**Release Date:** {movie_data['release_date']}\n\n" - - # Add production title - movie_text += "__**Production Information**__\n" - - companies = movie_data['production_companies'] - countries = movie_data['production_countries'] - - # Add production information - movie_text += f"""**Made by:** {', '.join([comp['name'] - for comp in companies])}\n""" - movie_text += f"""**Made in:** {', '.join([country['name'] - for country in countries])}\n\n""" - - # Add Some Numbers title - movie_text += "__**Some Numbers**__\n" - - # Add Budget, Revenue and Duration - movie_text += f"**Budget:** ${movie_data['budget'] if movie_data['budget'] != 0 else '?'}\n" - movie_text += f"**Revenue:** ${movie_data['revenue'] if movie_data['revenue'] != 0 else '?'}\n" - movie_text += f"**Duration:** {movie_data['runtime']} minutes\n\n" - - # Add description - movie_text += movie_data['overview'] - - # Define Movie Image URL - movie_img_url = f"http://image.tmdb.org/t/p/w200{movie_data['poster_path']}" - - # Append this page to pages - pages.append((movie_text, movie_img_url)) - except KeyError as err: - # Create error message - msg = f"There was KeyError while executing HTTP request. API may " \ - f"down or API key may be incorrect, however, some movies " \ - f"have some missing fields, and this error will raise this " \ - f"too. Problematic Key: \n```{err}``` " - - # Create error embed - err_embed = Embed(title=":no_entry: Error :no_entry:") - - # Log error - logger.warning(msg) - - # Return error message + embed - return [(msg, "")], err_embed - - # Return all movies pages - return pages, embed + Action = "28" + Adventure = "12" + Animation = "16" + Comedy = "35" + Crime = "80" + Documentary = "99" + Drama = "18" + Family = "10751" + Fantasy = "14" + History = "36" + Horror = "27" + Music = "10402" + Mystery = "9648" + Romance = "10749" + Science = "878" + Thriller = "53" + Western = "37" class Movie(Cog): @@ -199,25 +81,130 @@ class Movie(Cog): # Check is there more than 20 movies specified, due TMDB return 20 movies # per page, so this is max. Also you can't get less movies than 1, just logic if amount > 20: - await ctx.send('You can\'t get more than 20 movies at once. (TMDB limits)') + await ctx.send("You can't get more than 20 movies at once. (TMDB limits)") return elif amount < 1: - await ctx.send('You can\'t get less than 1 movies. Just logic.') + await ctx.send("You can't get less than 1 movies. Just logic.") return - # Capitalize genre for getting data from Enum + # Capitalize genre for getting data from Enum, get random page genre = genre.capitalize() + page = random.randint(1, 200) - # Try to fetch pages and embed, when invalid genre, show help + # Get movies list from TMDB, check is results key in result. When not, raise error. When genre not exist, + # show help. try: - pages, embed = await get_random_movies(self.http_session, amount, MovieGenres[genre].value, genre) + movies = await self.get_movies_list(self.http_session, MovieGenres[genre].value, page) except KeyError: await ctx.send_help('movies') return + if 'results' not in movies.keys(): + err_text = f'There was problem while fetching movies list. Problematic response:\n```{movies}```' + err = Embed(title=':no_entry: Error :no_entry:', description=err_text) + + await ctx.send(embed=err) + logger.warning(err_text) + + return + + # Get all pages and embed + pages = await self.get_pages(self.http_session, movies, amount) + embed = await self.get_embed(genre) # Send movies, paginate await ImagePaginator.paginate(pages, ctx, embed) + async def get_movies_list(self, client: ClientSession, genre_id: str, page: int) -> Dict: + """Return JSON of TMDB discover request.""" + # Define params of request + params = { + "api_key": TMDB_API_KEY, + "language": "en-US", + "sort_by": "popularity.desc", + "include_adult": "false", + "include_video": "false", + "page": page, + "with_genres": genre_id + } + + # Create URL + url = BASE_URL + "discover/movie?" + urlencode(params) + + # Make discover request to TMDB + async with client.get(url) as resp: + data = await resp.json() + + # Return response result + return data + + async def get_pages(self, client: ClientSession, movies: Dict, amount: int) -> (List[Tuple[str, str]]): + """Fetch all movie pages from movies dictionary. Return list of pages.""" + pages = [] + + for i in range(amount): + # Get movie ID, fetch movie information + movie_id = movies['results'][i]['id'] + movie = await self.get_movie(client, movie_id) + + # Create page, append it to pages + page, img = await self.create_page(movie) + pages.append((page, img)) + + return pages + + async def get_movie(self, client: ClientSession, movie: int) -> Dict: + """Get Movie by movie ID from TMDB. Return result dictionary.""" + # Define URL params, generate URL + params = { + "api_key": TMDB_API_KEY, + "language": "en-US" + } + url = BASE_URL + f"movie/{movie}?" + urlencode(params) + + # Do request, return result + async with client.get(url) as resp: + return await resp.json() + + async def create_page(self, movie: Dict) -> (str, str): + """Create page from TMDB movie request result. Return formatted page + image.""" + text = "" + + # Add title + tagline (if not empty) + text += f"**{movie['title']}**\n" + if movie['tagline'] != "": + text += f"{movie['tagline']}\n\n" + else: + text += "\n" + + # Add other information + text += f"**Rating:** {movie['vote_average']}/10 :star:\n" + text += f"**Release Date:** {movie['release_date']}\n\n" + + text += "__**Production Information**__\n" + + companies = movie['production_companies'] + countries = movie['production_countries'] + + text += f"**Made by:** {', '.join(company['name'] for company in companies)}\n" + text += f"**Made in:** {', '.join(country['name'] for country in countries)}\n\n" + + text += "__**Some Numbers**__\n" + + text += f"**Budget:** ${movie['budget'] if movie['budget'] != 0 else '?'}\n" + text += f"**Revenue:** ${movie['revenue'] if movie['revenue'] != 0 else '?'}\n" + text += f"**Duration:** {movie['runtime']} minutes\n\n" + + text += movie['overview'] + + img = f"http://image.tmdb.org/t/p/w200{movie['poster_path']}" + + # Return page content and image + return text, img + + async def get_embed(self, name: str) -> Embed: + """Return embed of random movies. Uses name in title.""" + return Embed(title=f'Random {name} Movies').set_footer(text='Powered by TMDB (themoviedb.org)') + def setup(bot: Bot) -> None: """Load Movie Cog.""" -- cgit v1.2.3 From eaaeadc76dd859e5d462d648169559b434f76883 Mon Sep 17 00:00:00 2001 From: ks123 Date: Sun, 16 Feb 2020 08:02:36 +0200 Subject: Small style fixes: removed unnecessary comments, made ifs easier readable, fixed type hints. --- bot/seasons/evergreen/movie.py | 43 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py index fc0053fd..949da665 100644 --- a/bot/seasons/evergreen/movie.py +++ b/bot/seasons/evergreen/movie.py @@ -2,7 +2,7 @@ import logging import random from enum import Enum from os import environ -from typing import Dict, List, Tuple +from typing import Any, Dict, List, Tuple from urllib.parse import urlencode from aiohttp import ClientSession @@ -17,13 +17,17 @@ TMDB_API_KEY = environ.get('TMDB_API_KEY') # Define base URL of TMDB BASE_URL = "https://api.themoviedb.org/3/" -# Get logger logger = logging.getLogger(__name__) +# Define movie params, that will be used for every movie request +MOVIE_PARAMS = { + "api_key": TMDB_API_KEY, + "language": "en-US" +} + -# Genres with TMDB API IDs class MovieGenres(Enum): - """Genre names and IDs.""" + """Movies Genre names and IDs.""" Action = "28" Adventure = "12" @@ -111,10 +115,9 @@ class Movie(Cog): pages = await self.get_pages(self.http_session, movies, amount) embed = await self.get_embed(genre) - # Send movies, paginate await ImagePaginator.paginate(pages, ctx, embed) - async def get_movies_list(self, client: ClientSession, genre_id: str, page: int) -> Dict: + async def get_movies_list(self, client: ClientSession, genre_id: str, page: int) -> Dict[str, Any]: """Return JSON of TMDB discover request.""" # Define params of request params = { @@ -127,26 +130,20 @@ class Movie(Cog): "with_genres": genre_id } - # Create URL url = BASE_URL + "discover/movie?" + urlencode(params) - # Make discover request to TMDB + # Make discover request to TMDB, return result async with client.get(url) as resp: - data = await resp.json() - - # Return response result - return data + return await resp.json() - async def get_pages(self, client: ClientSession, movies: Dict, amount: int) -> (List[Tuple[str, str]]): + async def get_pages(self, client: ClientSession, movies: Dict[str, Any], amount: int) -> List[Tuple[str, str]]: """Fetch all movie pages from movies dictionary. Return list of pages.""" pages = [] for i in range(amount): - # Get movie ID, fetch movie information movie_id = movies['results'][i]['id'] movie = await self.get_movie(client, movie_id) - # Create page, append it to pages page, img = await self.create_page(movie) pages.append((page, img)) @@ -154,24 +151,18 @@ class Movie(Cog): async def get_movie(self, client: ClientSession, movie: int) -> Dict: """Get Movie by movie ID from TMDB. Return result dictionary.""" - # Define URL params, generate URL - params = { - "api_key": TMDB_API_KEY, - "language": "en-US" - } - url = BASE_URL + f"movie/{movie}?" + urlencode(params) + url = BASE_URL + f"movie/{movie}?" + urlencode(MOVIE_PARAMS) - # Do request, return result async with client.get(url) as resp: return await resp.json() - async def create_page(self, movie: Dict) -> (str, str): + async def create_page(self, movie: Dict[str, Any]) -> Tuple[str, str]: """Create page from TMDB movie request result. Return formatted page + image.""" text = "" # Add title + tagline (if not empty) text += f"**{movie['title']}**\n" - if movie['tagline'] != "": + if movie['tagline']: text += f"{movie['tagline']}\n\n" else: text += "\n" @@ -190,8 +181,8 @@ class Movie(Cog): text += "__**Some Numbers**__\n" - text += f"**Budget:** ${movie['budget'] if movie['budget'] != 0 else '?'}\n" - text += f"**Revenue:** ${movie['revenue'] if movie['revenue'] != 0 else '?'}\n" + text += f"**Budget:** ${movie['budget'] if movie['budget'] else '?'}\n" + text += f"**Revenue:** ${movie['revenue'] if movie['revenue'] else '?'}\n" text += f"**Duration:** {movie['runtime']} minutes\n\n" text += movie['overview'] -- cgit v1.2.3 From 87cd31e1af470d5ac52beb1edbb9e552eed9ece1 Mon Sep 17 00:00:00 2001 From: F4zii Date: Sun, 16 Feb 2020 18:22:40 +0200 Subject: Fix the Pagination cog When using pagination in the Reddit cog, clicking the LAST_EMOJI would raise an exception like this: TypeError: unsupported operand type(s) for -: 'list' and 'int' This was resolved by taking the subtraction out of the len() function. --- bot/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/pagination.py b/bot/pagination.py index f1233482..1858bd2d 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -389,7 +389,7 @@ class ImagePaginator(Paginator): log.debug("Got last page reaction, but we're on the last page - ignoring") continue - current_page = len(paginator.pages - 1) + current_page = len(paginator.pages) - 1 reaction_type = "last" # Previous reaction press - [:arrow_left: ] -- cgit v1.2.3 From 4ba52054c86e293eeec8730d4d338dbb6f375522 Mon Sep 17 00:00:00 2001 From: F4zii Date: Sun, 16 Feb 2020 21:38:09 +0200 Subject: Paginator Migration - Emoji and actions Switched the emoji used to clear the reactions of a paginator [":x:"] With [":trashcan:"], Clicking on this emoji deletes the message --- bot/pagination.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'bot') diff --git a/bot/pagination.py b/bot/pagination.py index 1858bd2d..841e33eb 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -10,7 +10,7 @@ FIRST_EMOJI = "\u23EE" # [:track_previous:] LEFT_EMOJI = "\u2B05" # [:arrow_left:] RIGHT_EMOJI = "\u27A1" # [:arrow_right:] LAST_EMOJI = "\u23ED" # [:track_next:] -DELETE_EMOJI = "\u274c" # [:x:] +DELETE_EMOJI = "<:trashcan:637136429717389331>" # [:trashcan:] PAGINATION_EMOJI = [FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI] @@ -113,7 +113,7 @@ class LinePaginator(Paginator): # Reaction is on this message reaction_.message.id == message.id, # Reaction is one of the pagination emotes - reaction_.emoji in PAGINATION_EMOJI, + str(reaction_.emoji) in PAGINATION_EMOJI, # Note: DELETE_EMOJI is a string and not unicode # Reaction was not made by the Bot user_.id != ctx.bot.user.id, # There were no restrictions @@ -185,7 +185,7 @@ class LinePaginator(Paginator): log.debug("Timed out waiting for a reaction") break # We're done, no reactions for the last 5 minutes - if reaction.emoji == DELETE_EMOJI: + if str(reaction.emoji) == DELETE_EMOJI: # Note: DELETE_EMOJI is a string and not unicode log.debug("Got delete reaction") break @@ -261,8 +261,8 @@ class LinePaginator(Paginator): await message.edit(embed=embed) - log.debug("Ending pagination and removing all reactions...") - await message.clear_reactions() + log.debug("Ending pagination and deleting the message") + await message.delete() class ImagePaginator(Paginator): @@ -323,7 +323,7 @@ class ImagePaginator(Paginator): # Reaction is on the same message sent reaction_.message.id == message.id, # The reaction is part of the navigation menu - reaction_.emoji in PAGINATION_EMOJI, + str(reaction_.emoji) in PAGINATION_EMOJI, # Note: DELETE_EMOJI is a string and not unicode # The reactor is not a bot not member.bot )) @@ -369,8 +369,8 @@ class ImagePaginator(Paginator): # Deletes the users reaction await message.remove_reaction(reaction.emoji, user) - # Delete reaction press - [:x:] - if reaction.emoji == DELETE_EMOJI: + # Delete reaction press - [:trashcan:] + if str(reaction.emoji) == DELETE_EMOJI: # Note: DELETE_EMOJI is a string and not unicode log.debug("Got delete reaction") break @@ -424,5 +424,5 @@ class ImagePaginator(Paginator): await message.edit(embed=embed) - log.debug("Ending pagination and removing all reactions...") - await message.clear_reactions() + log.debug("Ending pagination and deleting the message") + await message.delete() -- cgit v1.2.3 From cd4841aa6407ab6bece89d12d9645dab56f18d89 Mon Sep 17 00:00:00 2001 From: F4zi <44242259+F4zi780@users.noreply.github.com> Date: Mon, 17 Feb 2020 11:35:14 +0200 Subject: Pagination migrations - Actions and emojis Clicking on [:trashcan:] broke the loop and deleted the message, instead, now we return after a message deletion and break when an `asyncio.TimeoutError` is raised (when a user fails to add reactions in time) --- bot/pagination.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/pagination.py b/bot/pagination.py index 841e33eb..b5bb1720 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -187,7 +187,7 @@ class LinePaginator(Paginator): if str(reaction.emoji) == DELETE_EMOJI: # Note: DELETE_EMOJI is a string and not unicode log.debug("Got delete reaction") - break + return await message.delete() if reaction.emoji == FIRST_EMOJI: await message.remove_reaction(reaction.emoji, user) @@ -261,8 +261,8 @@ class LinePaginator(Paginator): await message.edit(embed=embed) - log.debug("Ending pagination and deleting the message") - await message.delete() + log.debug("Ending pagination and clearing reactions...") + await message.clear_reactions() class ImagePaginator(Paginator): @@ -372,7 +372,7 @@ class ImagePaginator(Paginator): # Delete reaction press - [:trashcan:] if str(reaction.emoji) == DELETE_EMOJI: # Note: DELETE_EMOJI is a string and not unicode log.debug("Got delete reaction") - break + return await message.delete() # First reaction press - [:track_previous:] if reaction.emoji == FIRST_EMOJI: @@ -424,5 +424,5 @@ class ImagePaginator(Paginator): await message.edit(embed=embed) - log.debug("Ending pagination and deleting the message") - await message.delete() + log.debug("Ending pagination and clearing reactions...") + await message.clear_reactions() -- cgit v1.2.3 From c1fe1a989d1d9035bc32b1f6e789f2a82bbadab8 Mon Sep 17 00:00:00 2001 From: F4zi <44242259+F4zi780@users.noreply.github.com> Date: Mon, 17 Feb 2020 12:36:20 +0200 Subject: Pagination migrations - Data Structure Modified Changed the pagination emoji collection from list to tuple This change was suggested since this collection is constant --- bot/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/pagination.py b/bot/pagination.py index b5bb1720..a024e0d9 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -12,7 +12,7 @@ RIGHT_EMOJI = "\u27A1" # [:arrow_right:] LAST_EMOJI = "\u23ED" # [:track_next:] DELETE_EMOJI = "<:trashcan:637136429717389331>" # [:trashcan:] -PAGINATION_EMOJI = [FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI] +PAGINATION_EMOJI = (FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI) log = logging.getLogger(__name__) -- cgit v1.2.3 From 08c4d7f4883df94f6f63f27d1531d881cf33f1e5 Mon Sep 17 00:00:00 2001 From: F4zii Date: Mon, 17 Feb 2020 16:52:31 +0200 Subject: Paginator Migration - Added trashcan emoji to constants.py --- bot/constants.py | 1 + bot/pagination.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index eca4f67b..be92770d 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -88,6 +88,7 @@ class Emojis: christmas_tree = "\U0001F384" check = "\u2611" envelope = "\U0001F4E8" + trashcan = "<:trashcan:678677706652647506>" terning1 = "<:terning1:431249668983488527>" terning2 = "<:terning2:462339216987127808>" diff --git a/bot/pagination.py b/bot/pagination.py index a024e0d9..c77ad571 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -5,12 +5,13 @@ from typing import Iterable, List, Optional, Tuple from discord import Embed, Member, Reaction from discord.abc import User from discord.ext.commands import Context, Paginator +from bot.constants import Emojis FIRST_EMOJI = "\u23EE" # [:track_previous:] LEFT_EMOJI = "\u2B05" # [:arrow_left:] RIGHT_EMOJI = "\u27A1" # [:arrow_right:] LAST_EMOJI = "\u23ED" # [:track_next:] -DELETE_EMOJI = "<:trashcan:637136429717389331>" # [:trashcan:] +DELETE_EMOJI = Emojis.trashcan # [:trashcan:] PAGINATION_EMOJI = (FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI) -- cgit v1.2.3 From a43655831319ebeefc8038c2c4b1b981d6c5a759 Mon Sep 17 00:00:00 2001 From: F4zii Date: Mon, 17 Feb 2020 16:55:40 +0200 Subject: Paginator Migration - Added trashcan emoji to constants.py --- 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 be92770d..2c68f719 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -88,7 +88,7 @@ class Emojis: christmas_tree = "\U0001F384" check = "\u2611" envelope = "\U0001F4E8" - trashcan = "<:trashcan:678677706652647506>" + trashcan = "<:trashcan:637136429717389331>" terning1 = "<:terning1:431249668983488527>" terning2 = "<:terning2:462339216987127808>" -- cgit v1.2.3 From 7ec299251afd794d10e82e66fd9cb4c9c0f744ea Mon Sep 17 00:00:00 2001 From: F4zii Date: Mon, 17 Feb 2020 17:11:37 +0200 Subject: Paginator Migration - Added trashcan emoji to constants.py --- bot/constants.py | 1 + bot/pagination.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index eca4f67b..2c68f719 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -88,6 +88,7 @@ class Emojis: christmas_tree = "\U0001F384" check = "\u2611" envelope = "\U0001F4E8" + trashcan = "<:trashcan:637136429717389331>" terning1 = "<:terning1:431249668983488527>" terning2 = "<:terning2:462339216987127808>" diff --git a/bot/pagination.py b/bot/pagination.py index a024e0d9..9a7a0382 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -6,11 +6,13 @@ from discord import Embed, Member, Reaction from discord.abc import User from discord.ext.commands import Context, Paginator +from bot.constants import Emojis + FIRST_EMOJI = "\u23EE" # [:track_previous:] LEFT_EMOJI = "\u2B05" # [:arrow_left:] RIGHT_EMOJI = "\u27A1" # [:arrow_right:] LAST_EMOJI = "\u23ED" # [:track_next:] -DELETE_EMOJI = "<:trashcan:637136429717389331>" # [:trashcan:] +DELETE_EMOJI = Emojis.trashcan # [:trashcan:] PAGINATION_EMOJI = (FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI) -- cgit v1.2.3 From 3e10585525ccf6fca5c5174f1077d0f6ecbf0622 Mon Sep 17 00:00:00 2001 From: F4zii Date: Mon, 17 Feb 2020 17:29:02 +0200 Subject: Lint error - missing line --- bot/pagination.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/pagination.py b/bot/pagination.py index f8fa538c..9a7a0382 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -5,7 +5,6 @@ from typing import Iterable, List, Optional, Tuple from discord import Embed, Member, Reaction from discord.abc import User from discord.ext.commands import Context, Paginator -from bot.constants import Emojis from bot.constants import Emojis -- cgit v1.2.3 From afd087fb949d892e644b304676ff81791be8909d Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 17 Feb 2020 19:12:25 +0200 Subject: Added .movies genres|genre|g command. Made .movies command docstring smaller. Added warning loggings. Better Some Numbers section formatting. --- bot/constants.py | 1 + bot/seasons/evergreen/movie.py | 88 ++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 46 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index eca4f67b..0a9af1dd 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -132,6 +132,7 @@ class Tokens(NamedTuple): aoc_session_cookie = environ.get("AOC_SESSION_COOKIE") omdb = environ.get("OMDB_API_KEY") youtube = environ.get("YOUTUBE_API_KEY") + tmdb = environ.get("TMDB_API_KEY") # Default role combinations diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py index 949da665..4bc59655 100644 --- a/bot/seasons/evergreen/movie.py +++ b/bot/seasons/evergreen/movie.py @@ -1,19 +1,16 @@ import logging import random from enum import Enum -from os import environ from typing import Any, Dict, List, Tuple from urllib.parse import urlencode from aiohttp import ClientSession from discord import Embed -from discord.ext.commands import Bot, Cog, Context, command +from discord.ext.commands import Bot, Cog, Context, group +from bot.constants import Tokens from bot.pagination import ImagePaginator -# Get TMDB API key from .env -TMDB_API_KEY = environ.get('TMDB_API_KEY') - # Define base URL of TMDB BASE_URL = "https://api.themoviedb.org/3/" @@ -21,7 +18,7 @@ logger = logging.getLogger(__name__) # Define movie params, that will be used for every movie request MOVIE_PARAMS = { - "api_key": TMDB_API_KEY, + "api_key": Tokens.tmdb, "language": "en-US" } @@ -55,32 +52,12 @@ class Movie(Cog): self.bot = bot self.http_session: ClientSession = bot.http_session - @command(name='movies', aliases=['movie']) + @group(name='movies', aliases=['movie'], invoke_without_command=True) async def movies(self, ctx: Context, genre: str = "", amount: int = 5) -> None: """ - Get random movies by specifing genre. - - Also support amount parameter, - that define how much movies will be shown. Default 5 - - Available genres: - - Action - - Adventure - - Animation - - Comedy - - Crime - - Documentary - - Drama - - Family - - Fantasy - - History - - Horror - - Music - - Mystery - - Romance - - Science - - Thriller - - Western + Get random movies by specifying genre. Also support amount parameter, that define how much movies will be shown. + + Default 5. Use .movies genres to get all available genres. """ # Check is there more than 20 movies specified, due TMDB return 20 movies # per page, so this is max. Also you can't get less movies than 1, just logic @@ -91,25 +68,31 @@ class Movie(Cog): await ctx.send("You can't get less than 1 movies. Just logic.") return - # Capitalize genre for getting data from Enum, get random page + # Capitalize genre for getting data from Enum, get random page, send help when genre don't exist. genre = genre.capitalize() - page = random.randint(1, 200) - - # Get movies list from TMDB, check is results key in result. When not, raise error. When genre not exist, - # show help. try: - movies = await self.get_movies_list(self.http_session, MovieGenres[genre].value, page) + result = await self.get_movies_list(self.http_session, MovieGenres[genre].value, 1) except KeyError: await ctx.send_help('movies') return - if 'results' not in movies.keys(): - err_text = f'There was problem while fetching movies list. Problematic response:\n```{movies}```' - err = Embed(title=':no_entry: Error :no_entry:', description=err_text) - await ctx.send(embed=err) - logger.warning(err_text) + # Check is results is result. If not, throw error. + if "results" not in result.keys(): + err_msg = f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " \ + f"{result['status_message']}." + await ctx.send(err_msg) + logger.warning(err_msg) - return + # Get random page. Max page is last page where is movies with this genre. + page = random.randint(1, result["total_pages"]) + + # Get movies list from TMDB, check is results key in result. When not, raise error. + movies = await self.get_movies_list(self.http_session, MovieGenres[genre].value, page) + if 'results' not in movies.keys(): + err_msg = f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " \ + f"{result['status_message']}." + await ctx.send(err_msg) + logger.warning(err_msg) # Get all pages and embed pages = await self.get_pages(self.http_session, movies, amount) @@ -117,11 +100,16 @@ class Movie(Cog): await ImagePaginator.paginate(pages, ctx, embed) + @movies.command(name='genres', aliases=['genre', 'g']) + async def genres(self, ctx: Context) -> None: + """Show all currently available genres for .movies command.""" + await ctx.send(f"Current available genres: {', '.join('`' + genre.name + '`' for genre in MovieGenres)}") + async def get_movies_list(self, client: ClientSession, genre_id: str, page: int) -> Dict[str, Any]: """Return JSON of TMDB discover request.""" # Define params of request params = { - "api_key": TMDB_API_KEY, + "api_key": Tokens.tmdb, "language": "en-US", "sort_by": "popularity.desc", "include_adult": "false", @@ -181,9 +169,17 @@ class Movie(Cog): text += "__**Some Numbers**__\n" - text += f"**Budget:** ${movie['budget'] if movie['budget'] else '?'}\n" - text += f"**Revenue:** ${movie['revenue'] if movie['revenue'] else '?'}\n" - text += f"**Duration:** {movie['runtime']} minutes\n\n" + budget = f"{movie['budget']:,d}" if movie['budget'] else "?" + revenue = f"{movie['revenue']:,d}" if movie['revenue'] else "?" + + if movie['runtime'] is not None: + duration = divmod(movie['runtime'], 60) + else: + duration = ("?", "?") + + text += f"**Budget:** ${budget}\n" + text += f"**Revenue:** ${revenue}\n" + text += f"**Duration:** {f'{duration[0]} hour(s) {duration[1]} minute(s)'}\n\n" text += movie['overview'] -- cgit v1.2.3 From f9fbed2e2f1828b0c91e3c8348b51ae9b0022de5 Mon Sep 17 00:00:00 2001 From: Karlis S <45097959+ks129@users.noreply.github.com> Date: Tue, 18 Feb 2020 06:45:26 +0200 Subject: Fixed error message for less than 1 movie check. --- bot/seasons/evergreen/movie.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py index 4bc59655..e7671c59 100644 --- a/bot/seasons/evergreen/movie.py +++ b/bot/seasons/evergreen/movie.py @@ -65,7 +65,7 @@ class Movie(Cog): await ctx.send("You can't get more than 20 movies at once. (TMDB limits)") return elif amount < 1: - await ctx.send("You can't get less than 1 movies. Just logic.") + await ctx.send("You can't get less than 1 movie.") return # Capitalize genre for getting data from Enum, get random page, send help when genre don't exist. -- cgit v1.2.3 From 007d93c5e63a8167f3aa5158b85aca8599f3bda3 Mon Sep 17 00:00:00 2001 From: Karlis S <45097959+ks129@users.noreply.github.com> Date: Tue, 18 Feb 2020 17:25:01 +0200 Subject: Fixed comments in Movie cog --- bot/seasons/evergreen/movie.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/movie.py b/bot/seasons/evergreen/movie.py index e7671c59..3c5a312d 100644 --- a/bot/seasons/evergreen/movie.py +++ b/bot/seasons/evergreen/movie.py @@ -76,7 +76,7 @@ class Movie(Cog): await ctx.send_help('movies') return - # Check is results is result. If not, throw error. + # Check if "results" is in result. If not, throw error. if "results" not in result.keys(): err_msg = f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " \ f"{result['status_message']}." @@ -86,7 +86,7 @@ class Movie(Cog): # Get random page. Max page is last page where is movies with this genre. page = random.randint(1, result["total_pages"]) - # Get movies list from TMDB, check is results key in result. When not, raise error. + # Get movies list from TMDB, check if results key in result. When not, raise error. movies = await self.get_movies_list(self.http_session, MovieGenres[genre].value, page) if 'results' not in movies.keys(): err_msg = f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " \ -- cgit v1.2.3 From 6ccf0be796b63293f2dc4a8634fa6f834c2dd55a Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 23 Feb 2020 19:24:05 +0100 Subject: Calculate seconds as n_hours * seconds_in_hour This is a lot more readable than just 86400. --- bot/seasons/season.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/season.py b/bot/seasons/season.py index e7b7a69c..8fc68491 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -386,7 +386,7 @@ class SeasonManager(commands.Cog): while True: await asyncio.sleep(self.sleep_time) # Sleep until midnight - self.sleep_time = 86400 # Next time, sleep for 24 hours. + self.sleep_time = 24 * 3600 # Next time, sleep for 24 hours # If the season has changed, load it. new_season = get_season(date=datetime.datetime.utcnow()) -- cgit v1.2.3 From fe74128512b83e5a90fcfb5c655d9bc9fceab56a Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 23 Feb 2020 19:55:44 +0100 Subject: Add icon cycle frequency constant --- bot/constants.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index f0656926..52a4aa20 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -69,6 +69,7 @@ class Client(NamedTuple): token = environ.get("SEASONALBOT_TOKEN") debug = environ.get("SEASONALBOT_DEBUG", "").lower() == "true" season_override = environ.get("SEASON_OVERRIDE") + icon_cycle_frequency = 3 # N days to wait between cycling server icons within a single season class Colours: -- cgit v1.2.3 From 9330b404316cb00379f6eb5074b7ace842cedc8b Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 23 Feb 2020 20:09:26 +0100 Subject: Cycle icons within season in configured interval only The `load_seasons` task now has an internal state, which increments for every day. Before cycling the icon within the same season, first check whether we've waited the configured amount of days since the last cycle. Entering a new season, or changing the icon, resets the state to 0. This allows us to slow down the rate at which we cycle icons, addressing a bug where the icon wasn't loading for some users. --- bot/seasons/season.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/season.py b/bot/seasons/season.py index 8fc68491..763a08d2 100644 --- a/bot/seasons/season.py +++ b/bot/seasons/season.py @@ -383,18 +383,29 @@ class SeasonManager(commands.Cog): """Asynchronous timer loop to check for a new season every midnight.""" await self.bot.wait_until_ready() await self.season.load() + days_since_icon_change = 0 while True: await asyncio.sleep(self.sleep_time) # Sleep until midnight self.sleep_time = 24 * 3600 # Next time, sleep for 24 hours + days_since_icon_change += 1 + log.debug(f"Days since last icon change: {days_since_icon_change}") + # If the season has changed, load it. new_season = get_season(date=datetime.datetime.utcnow()) if new_season.name != self.season.name: self.season = new_season await self.season.load() + days_since_icon_change = 0 # Start counting afresh for the new season + + # Otherwise we check whether it's time for an icon cycle within the current season else: - await self.season.change_server_icon() + if days_since_icon_change == Client.icon_cycle_frequency: + await self.season.change_server_icon() + days_since_icon_change = 0 + else: + log.debug(f"Waiting {Client.icon_cycle_frequency - days_since_icon_change} more days to cycle icon") @with_role(Roles.moderator, Roles.admin, Roles.owner) @commands.command(name="season") -- cgit v1.2.3 From 829d32c75ae67d42da884dafc7bfc9476e9f086d Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 24 Feb 2020 18:48:09 +0200 Subject: Added .games command with all it's subcommands, added IGDB token requirement to constants.py. --- bot/constants.py | 1 + bot/seasons/evergreen/game.py | 338 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 bot/seasons/evergreen/game.py (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index f0656926..cb3228b6 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -134,6 +134,7 @@ class Tokens(NamedTuple): omdb = environ.get("OMDB_API_KEY") youtube = environ.get("YOUTUBE_API_KEY") tmdb = environ.get("TMDB_API_KEY") + igdb = environ.get("IGDB_API_KEY") # Default role combinations diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py new file mode 100644 index 00000000..d378c34e --- /dev/null +++ b/bot/seasons/evergreen/game.py @@ -0,0 +1,338 @@ +import asyncio +import random +from datetime import datetime +from enum import IntEnum +from typing import Any, Dict, List, Tuple + +from aiohttp import ClientSession +from discord import Embed +from discord.ext.commands import Bot, Cog, Context, group + +from bot.constants import Tokens +from bot.pagination import ImagePaginator, LinePaginator + +# Base URL of IGDB API +BASE_URL = "https://api-v3.igdb.com/" +IMAGE_BASE_URL = "https://images.igdb.com/igdb/image/upload/" + +HEADERS = { + "user-key": Tokens.igdb, + "Accept": "application/json" +} + + +class GameStatus(IntEnum): + """Game statuses in IGDB API.""" + + Released = 0 + Alpha = 2 + Beta = 3 + Early = 4 + Offline = 5 + Cancelled = 6 + Rumored = 7 + + +class AgeRatingCategories(IntEnum): + """IGDB API Age Rating categories IDs.""" + + ESRB = 1 + PEGI = 2 + + +class AgeRatings(IntEnum): + """PEGI/ESRB ratings IGDB API IDs.""" + + Three = 1 + Seven = 2 + Twelve = 3 + Sixteen = 4 + Eighteen = 5 + RP = 6 + EC = 7 + E = 8 + E10 = 9 + T = 10 + M = 11 + AO = 12 + + +class Games(Cog): + """Games Cog contains commands that collect data from IGDB.""" + + def __init__(self, bot: Bot): + self.bot = bot + self.http_session: ClientSession = bot.http_session + + # Initialize genres + asyncio.get_event_loop().create_task(self._get_genres()) + + async def _get_genres(self) -> None: + """Create genres variable for games command.""" + body = "fields name; limit 100;" + async with self.http_session.get(BASE_URL + "genres", data=body, headers=HEADERS) as resp: + result = await resp.json() + + genres = {genre['name'].capitalize(): genre['id'] for genre in result} + + self.genres = {} + + # Manual check genres, replace sentences with words + for genre in genres: + if genre == "Role-playing (rpg)": + self.genres["Role-playing"] = genres[genre] + self.genres["Rpg"] = genres[genre] + elif genre == "Turn-based strategy (tbs)": + self.genres["Turn-based-strategy"] = genres[genre] + self.genres["Tbs"] = genres[genre] + elif genre == "Real time strategy (rts)": + self.genres["Real-time-strategy"] = genres[genre] + self.genres["Rts"] = genres[genre] + elif genre == "Hack and slash/beat 'em up": + self.genres["Hack-and-slash"] = genres[genre] + else: + self.genres[genre] = genres[genre] + + @group(name='games', aliases=['game'], invoke_without_command=True) + async def games(self, ctx: Context, genre: str = "", amount: int = 5) -> None: + """ + Get random game(s) by genre from IGDB. Use .movies genres command to get all available genres. + + Also support amount parameter, what max is 25 and min 1, default 5. Use quotes ("") for genres with multiple + words. + """ + # Capitalize genre for check + genre = genre.capitalize() + + # Check for amounts, max is 25 and min 1 + if amount > 25: + await ctx.send("You can't get more than 25 games at once.") + return + elif amount < 1: + await ctx.send("You can't get less than 1 game.") + return + + # Get games listing, if genre don't exist, show help. + try: + games = await self.get_games_list(self.http_session, amount, self.genres[genre], + offset=random.randint(0, 150)) + except KeyError: + await ctx.send_help('games') + return + + # Create pages and paginate + pages = await self.get_pages(games) + + await ImagePaginator.paginate(pages, ctx, Embed(title=f'Random {genre} Games')) + + @games.command(name='top', aliases=['t']) + async def top(self, ctx: Context, amount: int = 10) -> None: + """ + Get current Top games in IGDB. + + Support amount parameter. Max is 25, min is 1. + """ + if amount > 25: + await ctx.send("You can't get more than top 25 games.") + return + elif amount < 1: + await ctx.send("You can't get less than 1 top game.") + return + + games = await self.get_games_list(self.http_session, amount, sort='total_rating desc', + additional_body="where total_rating >= 90; sort total_rating_count desc;") + + pages = await self.get_pages(games) + await ImagePaginator.paginate(pages, ctx, Embed(title=f'Top {amount} Games')) + + @games.command(name='genres', aliases=['genre', 'g']) + async def genres(self, ctx: Context) -> None: + """Get all available genres.""" + await ctx.send(f"Currently available genres: {', '.join(f'`{genre}`' for genre in self.genres)}") + + @games.command(name='search', aliases=['s']) + async def search(self, ctx: Context, *, search: str) -> None: + """Find games by name.""" + lines = await self.search_games(self.http_session, search) + + await LinePaginator.paginate((line for line in lines), ctx, Embed(title=f'Game Search Results: {search}')) + + @games.command(name='company', aliases=['companies']) + async def company(self, ctx: Context, amount: int = 5) -> None: + """ + Get random Game Companies companies from IGDB API. + + Support amount parameter. Max is 25, min is 1. + """ + if amount > 25: + await ctx.send("You can't get more than 25 companies at once.") + return + elif amount < 1: + await ctx.send("You can't get less than 1 company.") + return + + companies = await self.get_companies_list(self.http_session, amount, random.randint(0, 150)) + pages = await self.get_company_pages(companies) + + await ImagePaginator.paginate(pages, ctx, Embed(title='Random Game Companies')) + + async def get_games_list(self, + client: ClientSession, + limit: int, + genre: str = None, + sort: str = None, + additional_body: str = "", + offset: int = 0) \ + -> List[Dict[str, Any]]: + """Get Games List from IGDB API.""" + # Create body of IGDB API request, define fields, sorting, offset, limit and genre + body = "fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, " + body += "status, involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, " + body += "total_rating_count;\n" + + body += f"sort {sort};\n" if sort else '' + body += f"offset {offset};\n" + body += f"limit {limit};\n" + + body += f"where genres = ({genre});" if genre else '' + body += additional_body + + # Do request to IGDB API, create headers, URL, define body, return result + async with client.get( + url=BASE_URL + "games", + data=body, + headers=HEADERS + ) as resp: + return await resp.json() + + async def get_pages(self, data: List[Dict[str, Any]]) -> List[Tuple[str, str]]: + """Generate all game pages, do additional requests to IGDB API.""" + pages = [] + [pages.append(await self.create_page(game)) for game in data] + return pages + + async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: + """Create content of Game Page.""" + # Create page content variable, what will be returned + page = "" + + # If game have cover, generate URL of Cover, if not, let url empty + if 'cover' in data: + url = f"{IMAGE_BASE_URL}t_cover_big/{data['cover']['image_id']}.jpg" + else: + url = "" + + # Add title with hyperlink and check for storyline + page += f"**[{data['name']}]({data['url']})**\n" + page += data['summary'] + "\n\n" if 'summary' in data else "\n" + + # Add release date if key is in game information + if 'first_release_date' in data: + page += f"**Release Date:** {datetime.utcfromtimestamp(data['first_release_date']).date()}\n" + + # Add other information + page += f"**Rating:** {'{0:.2f}'.format(data['total_rating']) if 'total_rating' in data else '?'}/100 " + page += f":star: (based on {data['total_rating_count'] if 'total_rating_count' in data else '?'})\n" + + page += f"**Platforms:** " + page += f"{', '.join(pf['name'] for pf in data['platforms']) if 'platforms' in data else '?'}\n" + + page += f"**Status:** {GameStatus(data['status']).name if 'status' in data else '?'}\n" + + if 'age_ratings' in data: + rating = f"""{', '.join(AgeRatingCategories(age['category']).name + ' ' + AgeRatings(age['rating']).name + for age in data['age_ratings'])}""" + page += f"**Age Ratings:** {rating}\n" + + if 'involved_companies' in data: + companies = ', '.join(co['company']['name'] for co in data['involved_companies']) + else: + companies = "?" + page += f"**Made by:** {companies}\n" + + page += "\n" + page += data['storyline'] if 'storyline' in data else '' + + return page, url + + async def search_games(self, client: ClientSession, search: str) -> List[str]: + """Search game from IGDB API by string, return listing of pages.""" + lines = [] + + # Define request body of IGDB API request and do request + body = f"""fields name, url, storyline, total_rating, total_rating_count; +search "{search}"; +limit 50;""" + + async with client.get( + url=BASE_URL + "games", + data=body, + headers=HEADERS) as resp: + data = await resp.json() + + # Loop over games, format them to good format, make line and append this to total lines + for game in data: + line = "" + + # Add games name and rating, also attach URL to title + line += f"**[{game['name']}]({game['url']})**\n" + line += f"""{'{0:.2f}'.format(game['total_rating'] if 'total_rating' in game.keys() else 0)}/100 :star: (by { + game['total_rating_count'] if 'total_rating_count' in game.keys() else '?'} users)""" + + lines.append(line) + + return lines + + async def get_companies_list(self, client: ClientSession, limit: int, offset: int = 0) -> List[Dict[str, Any]]: + """Get random Game Companies from IGDB API.""" + # Create request body, define included fields, limit and offset, do request + body = f"""fields name, url, start_date, logo.image_id, developed.name, published.name, description; +limit {limit}; +offset {offset};""" + + async with client.get( + url=BASE_URL + "companies", + data=body, + headers=HEADERS + ) as resp: + return await resp.json() + + async def get_company_pages(self, data: List[Dict[str, Any]]) -> List[Tuple[str, str]]: + """Get all good formatted pages about Game Companies.""" + pages = [] + + # Loop over companies, add them to pages listing and return pages + [pages.append(await self.create_company_page(co)) for co in data] + + return pages + + async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: + """Create good formatted Game Company page.""" + page = "" + + # Generate URL of company logo + url = f"{IMAGE_BASE_URL}t_logo_med/{data['logo']['image_id'] if 'logo' in data.keys() else ''}.png" + + # Add name and description of company, attach URL to title + page += f"[{data['name']}]({data['url']})\n" + page += data['description'] + "\n\n" if 'description' in data.keys() else '\n' + + # Add other information + if 'start_date' in data.keys(): + founded = datetime.utcfromtimestamp(data['start_date']).date() + else: + founded = "?" + page += f"**Founded:** {founded}\n" + + page += "**Developed:** " + page += f"{', '.join(game['name'] for game in data['developed']) if 'developed' in data.keys() else '?'}\n" + + page += "**Published:** " + page += f"{', '.join(game['name'] for game in data['published']) if 'published' in data.keys() else '?'}" + + return page, url + + +def setup(bot: Bot) -> None: + """Add/Load Games cog.""" + bot.add_cog(Games(bot)) -- cgit v1.2.3 From 21ea3e012f29dd9a645b1678461e6444e5efb5b0 Mon Sep 17 00:00:00 2001 From: Karlis S <45097959+ks129@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:08:30 +0200 Subject: Remove keys() from total_rating count (Games Cog) Co-Authored-By: Thomas Petersson --- bot/seasons/evergreen/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index d378c34e..5f6846c2 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -277,7 +277,7 @@ limit 50;""" # Add games name and rating, also attach URL to title line += f"**[{game['name']}]({game['url']})**\n" line += f"""{'{0:.2f}'.format(game['total_rating'] if 'total_rating' in game.keys() else 0)}/100 :star: (by { - game['total_rating_count'] if 'total_rating_count' in game.keys() else '?'} users)""" + game['total_rating_count'] if 'total_rating_count' in game else '?'} users)""" lines.append(line) -- cgit v1.2.3 From bd5a4d00a7e6ee3cccc8db0654d681737a71bbad Mon Sep 17 00:00:00 2001 From: Karlis S <45097959+ks129@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:08:30 +0200 Subject: Added .games command with all it's subcommands, added IGDB token requirement to constants.py. --- bot/seasons/evergreen/game.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 5f6846c2..2f701fd6 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -121,7 +121,7 @@ class Games(Cog): return # Create pages and paginate - pages = await self.get_pages(games) + pages = [await self.create_page(game) for game in games] await ImagePaginator.paginate(pages, ctx, Embed(title=f'Random {genre} Games')) @@ -172,7 +172,7 @@ class Games(Cog): return companies = await self.get_companies_list(self.http_session, amount, random.randint(0, 150)) - pages = await self.get_company_pages(companies) + pages = [await self.create_company_page(co) for co in companies] await ImagePaginator.paginate(pages, ctx, Embed(title='Random Game Companies')) @@ -205,12 +205,6 @@ class Games(Cog): ) as resp: return await resp.json() - async def get_pages(self, data: List[Dict[str, Any]]) -> List[Tuple[str, str]]: - """Generate all game pages, do additional requests to IGDB API.""" - pages = [] - [pages.append(await self.create_page(game)) for game in data] - return pages - async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create content of Game Page.""" # Create page content variable, what will be returned @@ -240,8 +234,8 @@ class Games(Cog): page += f"**Status:** {GameStatus(data['status']).name if 'status' in data else '?'}\n" if 'age_ratings' in data: - rating = f"""{', '.join(AgeRatingCategories(age['category']).name + ' ' + AgeRatings(age['rating']).name - for age in data['age_ratings'])}""" + rating = ', '.join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" + for age in data['age_ratings']) page += f"**Age Ratings:** {rating}\n" if 'involved_companies' in data: @@ -297,15 +291,6 @@ offset {offset};""" ) as resp: return await resp.json() - async def get_company_pages(self, data: List[Dict[str, Any]]) -> List[Tuple[str, str]]: - """Get all good formatted pages about Game Companies.""" - pages = [] - - # Loop over companies, add them to pages listing and return pages - [pages.append(await self.create_company_page(co)) for co in data] - - return pages - async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create good formatted Game Company page.""" page = "" -- cgit v1.2.3 From f22eaa7148030303e8cbf27ab92c6011d423e8b7 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Tue, 25 Feb 2020 10:12:54 -0500 Subject: Update devlog channel constant The log channels have become one --- bot/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 52a4aa20..006cf77f 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -35,7 +35,7 @@ class Channels(NamedTuple): bot = 267659945086812160 checkpoint_test = 422077681434099723 devalerts = 460181980097675264 - devlog = int(environ.get("CHANNEL_DEVLOG", 548438471685963776)) + devlog = int(environ.get("CHANNEL_DEVLOG", 622895325144940554)) devtest = 414574275865870337 help_0 = 303906576991780866 help_1 = 303906556754395136 -- cgit v1.2.3 From c1bc07f5676ba96a2f4fd458fb0c8c19e4eccda7 Mon Sep 17 00:00:00 2001 From: ks123 Date: Fri, 28 Feb 2020 12:55:19 +0200 Subject: (Games Cog): Moved layouts, request bodies and URLs to Templates. Added token check on load. Other small code improvisations. --- bot/seasons/evergreen/game.py | 375 ++++++++++++++++++++++++------------------ 1 file changed, 213 insertions(+), 162 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 2f701fd6..b52c1b93 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -1,25 +1,104 @@ -import asyncio +import difflib +import logging import random -from datetime import datetime +import textwrap +from datetime import datetime as dt from enum import IntEnum -from typing import Any, Dict, List, Tuple +from string import Template +from typing import Any, Dict, List, Optional, Tuple from aiohttp import ClientSession from discord import Embed -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import SeasonalBot from bot.constants import Tokens from bot.pagination import ImagePaginator, LinePaginator # Base URL of IGDB API -BASE_URL = "https://api-v3.igdb.com/" -IMAGE_BASE_URL = "https://images.igdb.com/igdb/image/upload/" +BASE_URL = "https://api-v3.igdb.com" HEADERS = { "user-key": Tokens.igdb, "Accept": "application/json" } +logger = logging.getLogger(__name__) + +# --------- +# TEMPLATES +# --------- + +# Body templates +# Request body template for get_games_list +GAMES_LIST_BODY = Template( + textwrap.dedent(""" + fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, status, + involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, total_rating_count; + ${sort} ${limit} ${offset} ${genre} ${additional} + """) +) + +# Request body template for get_companies_list +COMPANIES_LIST_BODY = Template( + textwrap.dedent(""" + fields name, url, start_date, logo.image_id, developed.name, published.name, description; + offset ${offset}; + limit ${limit}; + """) +) + +# Request body template for games search +SEARCH_BODY = Template('fields name, url, storyline, total_rating, total_rating_count; limit 50; search "${term}";') + +# Pages templates +# Game embed layout +GAME_PAGE = Template( + textwrap.dedent(""" + **[${name}](${url})** + ${description} + **Release Date:** ${release_date} + **Rating:** ${rating}/100 :star: (based on ${rating_count} ratings) + **Platforms:** ${platforms} + **Status:** ${status} + **Age Ratings:** ${age_ratings} + **Made by:** ${made_by} + + ${storyline} + """) +) + +# .games company command page layout +COMPANY_PAGE = Template( + textwrap.dedent(""" + **[${name}](${url})** + ${description} + **Founded:** ${founded} + **Developed:** ${developed} + **Published:** ${published} + """) +) + +# For .games search command line layout +GAME_SEARCH_LINE = Template( + textwrap.dedent(""" + **[${name}](${url})** + ${rating}/100 :star: (based on ${rating_count} ratings) + """) +) + +# URL templates +COVER_URL = Template("https://images.igdb.com/igdb/image/upload/t_cover_big/${image_id}.jpg") +LOGO_URL = Template("https://images.igdb.com/igdb/image/upload/t_logo_med/${image_id}.png") + +# Create aliases for complex genre names +ALIASES = { + "Role-playing (rpg)": ["Role-playing", "Rpg"], + "Turn-based strategy (tbs)": ["Turn-based-strategy", "Tbs"], + "Real time strategy (rts)": ["Real-time-strategy", "Rts"], + "Hack and slash/beat 'em up": ["Hack-and-slash"] +} + class GameStatus(IntEnum): """Game statuses in IGDB API.""" @@ -60,264 +139,236 @@ class AgeRatings(IntEnum): class Games(Cog): """Games Cog contains commands that collect data from IGDB.""" - def __init__(self, bot: Bot): + def __init__(self, bot: SeasonalBot): self.bot = bot self.http_session: ClientSession = bot.http_session # Initialize genres - asyncio.get_event_loop().create_task(self._get_genres()) + bot.loop.create_task(self._get_genres()) async def _get_genres(self) -> None: """Create genres variable for games command.""" body = "fields name; limit 100;" - async with self.http_session.get(BASE_URL + "genres", data=body, headers=HEADERS) as resp: + async with self.http_session.get(f"{BASE_URL}/genres", data=body, headers=HEADERS) as resp: result = await resp.json() - genres = {genre['name'].capitalize(): genre['id'] for genre in result} + genres = {genre["name"].capitalize(): genre["id"] for genre in result} self.genres = {} - # Manual check genres, replace sentences with words + # Replace complex names with names from ALIASES for genre in genres: - if genre == "Role-playing (rpg)": - self.genres["Role-playing"] = genres[genre] - self.genres["Rpg"] = genres[genre] - elif genre == "Turn-based strategy (tbs)": - self.genres["Turn-based-strategy"] = genres[genre] - self.genres["Tbs"] = genres[genre] - elif genre == "Real time strategy (rts)": - self.genres["Real-time-strategy"] = genres[genre] - self.genres["Rts"] = genres[genre] - elif genre == "Hack and slash/beat 'em up": - self.genres["Hack-and-slash"] = genres[genre] + if genre in ALIASES: + for alias in ALIASES[genre]: + self.genres[alias] = genres[genre] else: self.genres[genre] = genres[genre] - @group(name='games', aliases=['game'], invoke_without_command=True) - async def games(self, ctx: Context, genre: str = "", amount: int = 5) -> None: + @group(name="games", aliases=["game"], invoke_without_command=True) + async def games(self, ctx: Context, genre: Optional[str] = None, amount: int = 5) -> None: """ - Get random game(s) by genre from IGDB. Use .movies genres command to get all available genres. + Get random game(s) by genre from IGDB. Use .games genres command to get all available genres. Also support amount parameter, what max is 25 and min 1, default 5. Use quotes ("") for genres with multiple words. """ + # When user didn't specified genre, send help message + if genre is None: + await ctx.send_help("games") + return + # Capitalize genre for check genre = genre.capitalize() # Check for amounts, max is 25 and min 1 - if amount > 25: - await ctx.send("You can't get more than 25 games at once.") - return - elif amount < 1: - await ctx.send("You can't get less than 1 game.") + if not 1 <= amount <= 25: + await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") return - # Get games listing, if genre don't exist, show help. + # Get games listing, if genre don't exist, show error message with possibilities. try: - games = await self.get_games_list(self.http_session, amount, self.genres[genre], + games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) except KeyError: - await ctx.send_help('games') + possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres)) + await ctx.send(f"Invalid genre `{genre}`. {f'Maybe you meant `{possibilities}`?' if possibilities else ''}") return # Create pages and paginate pages = [await self.create_page(game) for game in games] - await ImagePaginator.paginate(pages, ctx, Embed(title=f'Random {genre} Games')) + await ImagePaginator.paginate(pages, ctx, Embed(title=f"Random {genre} Games")) - @games.command(name='top', aliases=['t']) + @games.command(name="top", aliases=["t"]) async def top(self, ctx: Context, amount: int = 10) -> None: """ Get current Top games in IGDB. Support amount parameter. Max is 25, min is 1. """ - if amount > 25: - await ctx.send("You can't get more than top 25 games.") - return - elif amount < 1: - await ctx.send("You can't get less than 1 top game.") + if not 1 <= amount <= 25: + await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") return - games = await self.get_games_list(self.http_session, amount, sort='total_rating desc', + games = await self.get_games_list(amount, sort="total_rating desc", additional_body="where total_rating >= 90; sort total_rating_count desc;") - pages = await self.get_pages(games) - await ImagePaginator.paginate(pages, ctx, Embed(title=f'Top {amount} Games')) + pages = [await self.create_page(game) for game in games] + await ImagePaginator.paginate(pages, ctx, Embed(title=f"Top {amount} Games")) - @games.command(name='genres', aliases=['genre', 'g']) + @games.command(name="genres", aliases=["genre", "g"]) async def genres(self, ctx: Context) -> None: """Get all available genres.""" await ctx.send(f"Currently available genres: {', '.join(f'`{genre}`' for genre in self.genres)}") - @games.command(name='search', aliases=['s']) - async def search(self, ctx: Context, *, search: str) -> None: + @games.command(name="search", aliases=["s"]) + async def search(self, ctx: Context, *, search_term: str) -> None: """Find games by name.""" - lines = await self.search_games(self.http_session, search) + lines = await self.search_games(search_term) - await LinePaginator.paginate((line for line in lines), ctx, Embed(title=f'Game Search Results: {search}')) + await LinePaginator.paginate((line for line in lines), ctx, Embed(title=f"Game Search Results: {search_term}")) - @games.command(name='company', aliases=['companies']) + @games.command(name="company", aliases=["companies"]) async def company(self, ctx: Context, amount: int = 5) -> None: """ Get random Game Companies companies from IGDB API. Support amount parameter. Max is 25, min is 1. """ - if amount > 25: - await ctx.send("You can't get more than 25 companies at once.") - return - elif amount < 1: - await ctx.send("You can't get less than 1 company.") + if not 1 <= amount <= 25: + await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") return - companies = await self.get_companies_list(self.http_session, amount, random.randint(0, 150)) + companies = await self.get_companies_list(amount, random.randint(0, 150)) pages = [await self.create_company_page(co) for co in companies] - await ImagePaginator.paginate(pages, ctx, Embed(title='Random Game Companies')) + await ImagePaginator.paginate(pages, ctx, Embed(title="Random Game Companies")) async def get_games_list(self, - client: ClientSession, - limit: int, - genre: str = None, - sort: str = None, + amount: int, + genre: Optional[str] = None, + sort: Optional[str] = None, additional_body: str = "", - offset: int = 0) \ - -> List[Dict[str, Any]]: - """Get Games List from IGDB API.""" - # Create body of IGDB API request, define fields, sorting, offset, limit and genre - body = "fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, " - body += "status, involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, " - body += "total_rating_count;\n" - - body += f"sort {sort};\n" if sort else '' - body += f"offset {offset};\n" - body += f"limit {limit};\n" + offset: int = 0 + ) -> List[Dict[str, Any]]: + """ + Get list of games from IGDB API by parameters that is provided. - body += f"where genres = ({genre});" if genre else '' - body += additional_body + Amount param show how much movies this get, genre is genre ID and at least one genre in game must this when + provided. Sort is sorting by specific field and direction, ex. total_rating desc/asc (total_rating is field, + desc/asc is direction). Additional_body is field where you can pass extra search parameters. Offset show start + position in API. + """ + # Create body of IGDB API request, define fields, sorting, offset, limit and genre + params = { + "sort": f"sort {sort};" if sort else "", + "limit": f"limit {amount};", + "offset": f"offset {offset};" if offset else "", + "genre": f"where genres = ({genre});" if genre else "", + "additional": additional_body + } + body = GAMES_LIST_BODY.substitute(params) # Do request to IGDB API, create headers, URL, define body, return result - async with client.get( - url=BASE_URL + "games", - data=body, - headers=HEADERS - ) as resp: + async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: return await resp.json() async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create content of Game Page.""" - # Create page content variable, what will be returned - page = "" - - # If game have cover, generate URL of Cover, if not, let url empty - if 'cover' in data: - url = f"{IMAGE_BASE_URL}t_cover_big/{data['cover']['image_id']}.jpg" - else: - url = "" - - # Add title with hyperlink and check for storyline - page += f"**[{data['name']}]({data['url']})**\n" - page += data['summary'] + "\n\n" if 'summary' in data else "\n" - - # Add release date if key is in game information - if 'first_release_date' in data: - page += f"**Release Date:** {datetime.utcfromtimestamp(data['first_release_date']).date()}\n" - - # Add other information - page += f"**Rating:** {'{0:.2f}'.format(data['total_rating']) if 'total_rating' in data else '?'}/100 " - page += f":star: (based on {data['total_rating_count'] if 'total_rating_count' in data else '?'})\n" - - page += f"**Platforms:** " - page += f"{', '.join(pf['name'] for pf in data['platforms']) if 'platforms' in data else '?'}\n" - - page += f"**Status:** {GameStatus(data['status']).name if 'status' in data else '?'}\n" - - if 'age_ratings' in data: - rating = ', '.join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" - for age in data['age_ratings']) - page += f"**Age Ratings:** {rating}\n" - - if 'involved_companies' in data: - companies = ', '.join(co['company']['name'] for co in data['involved_companies']) - else: - companies = "?" - page += f"**Made by:** {companies}\n" - - page += "\n" - page += data['storyline'] if 'storyline' in data else '' + # Create cover image URL from template + url = COVER_URL.substitute({"image_id": data["cover"]["image_id"] if "cover" in data else ""}) + + # Get release date separately with checking + release_date = dt.utcfromtimestamp(data["first_release_date"]).date() if "first_release_date" in data else "?" + + # Create Age Ratings value + rating = ", ".join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" + for age in data["age_ratings"]) if "age_ratings" in data else "?" + + companies = ", ".join(comp["company"]["name"] for comp in data["involved_companies"]) \ + if "involved_companies" in data else "?" + + # Create formatting for template page + formatting = { + "name": data["name"], + "url": data["url"], + "description": f"{data['summary']}\n\n" if "summary" in data else "\n", + "release_date": release_date, + "rating": round(data["total_rating"] if "total_rating" in data else 0, 2), + "rating_count": data["total_rating_count"] if "total_rating_count" in data else "?", + "platforms": ", ".join(platform["name"] for platform in data["platforms"]) if "platforms" in data else "?", + "status": GameStatus(data["status"]).name if "status" in data else "?", + "age_ratings": rating, + "made_by": companies, + "storyline": data["storyline"] if "storyline" in data else "" + } + page = GAME_PAGE.substitute(formatting) return page, url - async def search_games(self, client: ClientSession, search: str) -> List[str]: + async def search_games(self, search_term: str) -> List[str]: """Search game from IGDB API by string, return listing of pages.""" lines = [] # Define request body of IGDB API request and do request - body = f"""fields name, url, storyline, total_rating, total_rating_count; -search "{search}"; -limit 50;""" - - async with client.get( - url=BASE_URL + "games", - data=body, - headers=HEADERS) as resp: + body = SEARCH_BODY.substitute({"term": search_term}) + + async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: data = await resp.json() # Loop over games, format them to good format, make line and append this to total lines for game in data: - line = "" - - # Add games name and rating, also attach URL to title - line += f"**[{game['name']}]({game['url']})**\n" - line += f"""{'{0:.2f}'.format(game['total_rating'] if 'total_rating' in game.keys() else 0)}/100 :star: (by { - game['total_rating_count'] if 'total_rating_count' in game else '?'} users)""" - + formatting = { + "name": game["name"], + "url": game["url"], + "rating": round(game["total_rating"] if "total_rating" in game else 0, 2), + "rating_count": game["total_rating_count"] if "total_rating" in game else "?" + } + line = GAME_SEARCH_LINE.substitute(formatting) lines.append(line) return lines - async def get_companies_list(self, client: ClientSession, limit: int, offset: int = 0) -> List[Dict[str, Any]]: + async def get_companies_list(self, limit: int, offset: int = 0) -> List[Dict[str, Any]]: """Get random Game Companies from IGDB API.""" - # Create request body, define included fields, limit and offset, do request - body = f"""fields name, url, start_date, logo.image_id, developed.name, published.name, description; -limit {limit}; -offset {offset};""" - - async with client.get( - url=BASE_URL + "companies", - data=body, - headers=HEADERS - ) as resp: + # Create request body from template + body = COMPANIES_LIST_BODY.substitute({ + "limit": limit, + "offset": offset + }) + + async with self.http_session.get(url=f"{BASE_URL}/companies", data=body, headers=HEADERS) as resp: return await resp.json() async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create good formatted Game Company page.""" - page = "" - # Generate URL of company logo - url = f"{IMAGE_BASE_URL}t_logo_med/{data['logo']['image_id'] if 'logo' in data.keys() else ''}.png" - - # Add name and description of company, attach URL to title - page += f"[{data['name']}]({data['url']})\n" - page += data['description'] + "\n\n" if 'description' in data.keys() else '\n' + url = LOGO_URL.substitute({"image_id": data["logo"]["image_id"] if "logo" in data else ""}) - # Add other information - if 'start_date' in data.keys(): - founded = datetime.utcfromtimestamp(data['start_date']).date() - else: - founded = "?" - page += f"**Founded:** {founded}\n" + # Try to get found date of company + founded = dt.utcfromtimestamp(data["start_date"]).date() if "start_date" in data else "?" - page += "**Developed:** " - page += f"{', '.join(game['name'] for game in data['developed']) if 'developed' in data.keys() else '?'}\n" + # Generate list of games, that company have developed or published + developed = ", ".join(game["name"] for game in data["developed"]) if "developed" in data else "?" + published = ", ".join(game["name"] for game in data["published"]) if "published" in data else "?" - page += "**Published:** " - page += f"{', '.join(game['name'] for game in data['published']) if 'published' in data.keys() else '?'}" + formatting = { + "name": data["name"], + "url": data["url"], + "description": f"{data['description']}\n\n" if "description" in data else "\n", + "founded": founded, + "developed": developed, + "published": published + } + page = COMPANY_PAGE.substitute(formatting) return page, url -def setup(bot: Bot) -> None: +def setup(bot: SeasonalBot) -> 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.") + return bot.add_cog(Games(bot)) -- cgit v1.2.3 From 10b63edcefa3dc53c833c0ce383b003823f7fa7c Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Sun, 1 Mar 2020 18:57:39 -0500 Subject: Strip references to seasonalbot chat Redirect any output to seasonalbot commands, where relevant --- bot/constants.py | 1 - bot/seasons/easter/__init__.py | 2 +- bot/seasons/easter/egg_facts.py | 2 +- bot/seasons/evergreen/issues.py | 5 ++--- bot/seasons/halloween/candy_collection.py | 8 ++++---- bot/seasons/halloween/halloween_facts.py | 2 +- bot/seasons/pride/__init__.py | 2 +- bot/seasons/pride/pride_facts.py | 2 +- bot/seasons/valentines/be_my_valentine.py | 2 +- docker-compose.yml | 1 - 10 files changed, 12 insertions(+), 15 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 006cf77f..d406bbd0 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -52,7 +52,6 @@ class Channels(NamedTuple): off_topic_2 = 463035268514185226 python = 267624335836053506 reddit = 458224812528238616 - seasonalbot_chat = int(environ.get("CHANNEL_SEASONALBOT_CHAT", 542272993192050698)) seasonalbot_commands = int(environ.get("CHANNEL_SEASONALBOT_COMMANDS", 607247579608121354)) seasonalbot_voice = int(environ.get("CHANNEL_SEASONALBOT_VOICE", 606259004230074378)) staff_lounge = 464905259261755392 diff --git a/bot/seasons/easter/__init__.py b/bot/seasons/easter/__init__.py index 1d77b6a6..dd60bf5c 100644 --- a/bot/seasons/easter/__init__.py +++ b/bot/seasons/easter/__init__.py @@ -16,7 +16,7 @@ class Easter(SeasonBase): • You may see stuff like an Easter themed esoteric challenge, a celebration of Earth Day, or Easter-related micro-events for you to join. Stay tuned! - If you'd like to contribute, head on over to <#542272993192050698> and we will help you get + If you'd like to contribute, head on over to <#635950537262759947> and we will help you get started. It doesn't matter if you're new to open source or Python, if you'd like to help, we will find you a task and teach you what you need to know. """ diff --git a/bot/seasons/easter/egg_facts.py b/bot/seasons/easter/egg_facts.py index 9e6fb1cb..e66e25a3 100644 --- a/bot/seasons/easter/egg_facts.py +++ b/bot/seasons/easter/egg_facts.py @@ -34,7 +34,7 @@ class EasterFacts(commands.Cog): async def send_egg_fact_daily(self) -> None: """A background task that sends an easter egg fact in the event channel everyday.""" - channel = self.bot.get_channel(Channels.seasonalbot_chat) + channel = self.bot.get_channel(Channels.seasonalbot_commands) while True: embed = self.make_embed() await channel.send(embed=embed) diff --git a/bot/seasons/evergreen/issues.py b/bot/seasons/evergreen/issues.py index c7501a5d..fba5b174 100644 --- a/bot/seasons/evergreen/issues.py +++ b/bot/seasons/evergreen/issues.py @@ -3,11 +3,10 @@ import logging import discord from discord.ext import commands -from bot.constants import Channels, Colours, Emojis, WHITELISTED_CHANNELS +from bot.constants import Colours, Emojis, WHITELISTED_CHANNELS from bot.decorators import override_in_channel log = logging.getLogger(__name__) -ISSUE_WHITELIST = WHITELISTED_CHANNELS + (Channels.seasonalbot_chat,) BAD_RESPONSE = { 404: "Issue/pull request not located! Please enter a valid number!", @@ -22,7 +21,7 @@ class Issues(commands.Cog): self.bot = bot @commands.command(aliases=("pr",)) - @override_in_channel(ISSUE_WHITELIST) + @override_in_channel(WHITELISTED_CHANNELS) async def issue( self, ctx: commands.Context, number: int, repository: str = "seasonalbot", user: str = "python-discord" ) -> None: diff --git a/bot/seasons/halloween/candy_collection.py b/bot/seasons/halloween/candy_collection.py index 64da7ced..490609dd 100644 --- a/bot/seasons/halloween/candy_collection.py +++ b/bot/seasons/halloween/candy_collection.py @@ -41,7 +41,7 @@ class CandyCollection(commands.Cog): if message.author.bot: return # ensure it's hacktober channel - if message.channel.id != Channels.seasonalbot_chat: + if message.channel.id != Channels.seasonalbot_commands: return # do random check for skull first as it has the lower chance @@ -64,7 +64,7 @@ class CandyCollection(commands.Cog): return # check to ensure it is in correct channel - if message.channel.id != Channels.seasonalbot_chat: + if message.channel.id != Channels.seasonalbot_commands: return # if its not a candy or skull, and it is one of 10 most recent messages, @@ -124,7 +124,7 @@ class CandyCollection(commands.Cog): ten_recent = [] recent_msg_id = max( message.id for message in self.bot._connection._messages - if message.channel.id == Channels.seasonalbot_chat + if message.channel.id == Channels.seasonalbot_commands ) channel = await self.hacktober_channel() @@ -155,7 +155,7 @@ class CandyCollection(commands.Cog): async def hacktober_channel(self) -> discord.TextChannel: """Get #hacktoberbot channel from its ID.""" - return self.bot.get_channel(id=Channels.seasonalbot_chat) + return self.bot.get_channel(id=Channels.seasonalbot_commands) async def remove_reactions(self, reaction: discord.Reaction) -> None: """Remove all candy/skull reactions.""" diff --git a/bot/seasons/halloween/halloween_facts.py b/bot/seasons/halloween/halloween_facts.py index f8610bd3..94730d9e 100644 --- a/bot/seasons/halloween/halloween_facts.py +++ b/bot/seasons/halloween/halloween_facts.py @@ -40,7 +40,7 @@ class HalloweenFacts(commands.Cog): @commands.Cog.listener() async def on_ready(self) -> None: """Get event Channel object and initialize fact task loop.""" - self.channel = self.bot.get_channel(Channels.seasonalbot_chat) + self.channel = self.bot.get_channel(Channels.seasonalbot_commands) self.bot.loop.create_task(self._fact_publisher_task()) def random_fact(self) -> Tuple[int, str]: diff --git a/bot/seasons/pride/__init__.py b/bot/seasons/pride/__init__.py index 75e90b2a..08df2fa1 100644 --- a/bot/seasons/pride/__init__.py +++ b/bot/seasons/pride/__init__.py @@ -16,7 +16,7 @@ class Pride(SeasonBase): • [Pride issues are now available for SeasonalBot on the repo](https://git.io/pythonpride). • You may see Pride-themed esoteric challenges and other microevents. - If you'd like to contribute, head on over to <#542272993192050698> and we will help you get + If you'd like to contribute, head on over to <#635950537262759947> and we will help you get started. It doesn't matter if you're new to open source or Python, if you'd like to help, we will find you a task and teach you what you need to know. """ diff --git a/bot/seasons/pride/pride_facts.py b/bot/seasons/pride/pride_facts.py index b705bfb4..5c19dfd0 100644 --- a/bot/seasons/pride/pride_facts.py +++ b/bot/seasons/pride/pride_facts.py @@ -33,7 +33,7 @@ class PrideFacts(commands.Cog): async def send_pride_fact_daily(self) -> None: """Background task to post the daily pride fact every day.""" - channel = self.bot.get_channel(Channels.seasonalbot_chat) + channel = self.bot.get_channel(Channels.seasonalbot_commands) while True: await self.send_select_fact(channel, datetime.utcnow()) await asyncio.sleep(24 * 60 * 60) diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index a073e1bd..de97cb4e 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -96,7 +96,7 @@ class BeMyValentine(commands.Cog): emoji_1, emoji_2 = self.random_emoji() lovefest_role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) - channel = self.bot.get_channel(Channels.seasonalbot_chat) + channel = self.bot.get_channel(Channels.seasonalbot_commands) valentine, title = self.valentine_check(valentine_type) if user is None: diff --git a/docker-compose.yml b/docker-compose.yml index f2f4b056..30e8a109 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,6 @@ services: # - SEASONALBOT_ADMIN_ROLE_ID= # - CHANNEL_ANNOUNCEMENTS= # - CHANNEL_DEVLOG= - # - CHANNEL_SEASONALBOT_CHAT= # - SEASON_OVERRIDE= volumes: -- cgit v1.2.3 From b0fb34d553663fd71121a005a9b6f7651da8e90a Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 2 Mar 2020 17:46:11 +0200 Subject: (Games Cog): Fixed and added content to docstrings. --- bot/seasons/evergreen/game.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index b52c1b93..46fdc689 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -254,7 +254,7 @@ class Games(Cog): """ Get list of games from IGDB API by parameters that is provided. - Amount param show how much movies this get, genre is genre ID and at least one genre in game must this when + Amount param show how much games this get, genre is genre ID and at least one genre in game must this when provided. Sort is sorting by specific field and direction, ex. total_rating desc/asc (total_rating is field, desc/asc is direction). Additional_body is field where you can pass extra search parameters. Offset show start position in API. @@ -330,7 +330,12 @@ class Games(Cog): return lines async def get_companies_list(self, limit: int, offset: int = 0) -> List[Dict[str, Any]]: - """Get random Game Companies from IGDB API.""" + """ + Get random Game Companies from IGDB API. + + Limit is parameter, that show how much movies this should return, offset show in which position should API start + returning results. + """ # Create request body from template body = COMPANIES_LIST_BODY.substitute({ "limit": limit, -- cgit v1.2.3 From d4bbf9a7a548341c77f55039e4a7689e1f1ee6ac Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 2 Mar 2020 17:51:02 +0200 Subject: (Games Cog): Added comments about offsets, use keyword parameters for get_companies_list. --- bot/seasons/evergreen/game.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 46fdc689..08068c23 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -186,6 +186,8 @@ class Games(Cog): return # Get games listing, if genre don't exist, show error message with possibilities. + # Offset must be random, due otherwise we will get always same result (offset show in which position should + # API start returning result) try: games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) @@ -239,7 +241,9 @@ class Games(Cog): await ctx.send("Your provided amount is out of range. Our minimum is 1 and maximum 25.") return - companies = await self.get_companies_list(amount, random.randint(0, 150)) + # Get companies listing. Provide limit for limiting how much companies will be returned. Get random offset to + # get (almost) every time different companies (offset show in which position should API start returning result) + companies = await self.get_companies_list(limit=amount, offset=random.randint(0, 150)) pages = [await self.create_company_page(co) for co in companies] await ImagePaginator.paginate(pages, ctx, Embed(title="Random Game Companies")) -- cgit v1.2.3 From 9e0662a1adbbcf291d3b935dd08dbf7cc68a6e16 Mon Sep 17 00:00:00 2001 From: ks123 Date: Mon, 2 Mar 2020 18:44:40 +0200 Subject: (Games Cog): Fixed companies list generating code (.games command). --- bot/seasons/evergreen/game.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 08068c23..b2114da8 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -289,8 +289,7 @@ class Games(Cog): rating = ", ".join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" for age in data["age_ratings"]) if "age_ratings" in data else "?" - companies = ", ".join(comp["company"]["name"] for comp in data["involved_companies"]) \ - if "involved_companies" in data else "?" + companies = [c["company"]["name"] for c in data["involved_companies"]] if "involved_companies" in data else "?" # Create formatting for template page formatting = { @@ -303,7 +302,7 @@ class Games(Cog): "platforms": ", ".join(platform["name"] for platform in data["platforms"]) if "platforms" in data else "?", "status": GameStatus(data["status"]).name if "status" in data else "?", "age_ratings": rating, - "made_by": companies, + "made_by": ", ".join(companies), "storyline": data["storyline"] if "storyline" in data else "" } page = GAME_PAGE.substitute(formatting) -- cgit v1.2.3 From c5ec3e129cad08a3b67cc396e445d92bdfac560e Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 09:19:55 +0200 Subject: (Games Cog): Fixed get_games_list calling formatting at L192 --- bot/seasons/evergreen/game.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index b2114da8..7a7dc02f 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -189,8 +189,7 @@ class Games(Cog): # Offset must be random, due otherwise we will get always same result (offset show in which position should # API start returning result) try: - games = await self.get_games_list(amount, self.genres[genre], - offset=random.randint(0, 150)) + games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) except KeyError: possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres)) await ctx.send(f"Invalid genre `{genre}`. {f'Maybe you meant `{possibilities}`?' if possibilities else ''}") -- cgit v1.2.3 From d89c37462b1917ea45dfa082a7b7b1daa09e4baa Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 09:22:41 +0200 Subject: (Games Cog): Fixed _get_genres function looping over genres (started using dict.items()) --- bot/seasons/evergreen/game.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 7a7dc02f..8f55a1f3 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -157,12 +157,12 @@ class Games(Cog): self.genres = {} # Replace complex names with names from ALIASES - for genre in genres: - if genre in ALIASES: - for alias in ALIASES[genre]: - self.genres[alias] = genres[genre] + for genre_name, genre in genres.items(): + if genre_name in ALIASES: + for alias in ALIASES[genre_name]: + self.genres[alias] = genre else: - self.genres[genre] = genres[genre] + self.genres[genre_name] = genre @group(name="games", aliases=["game"], invoke_without_command=True) async def games(self, ctx: Context, genre: Optional[str] = None, amount: int = 5) -> None: -- cgit v1.2.3 From 2fe0cdef64e6d6e49f583f9fd02c2f5f8b0a25ed Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 09:28:44 +0200 Subject: (Games Cog): Created task for fetching genres (every hour) --- bot/seasons/evergreen/game.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 8f55a1f3..727985a3 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple from aiohttp import ClientSession from discord import Embed +from discord.ext import tasks from discord.ext.commands import Cog, Context, group from bot.bot import SeasonalBot @@ -143,8 +144,12 @@ class Games(Cog): self.bot = bot self.http_session: ClientSession = bot.http_session - # Initialize genres - bot.loop.create_task(self._get_genres()) + self.refresh_genres_task.start() + + @tasks.loop(hours=1.0) + async def refresh_genres_task(self) -> None: + """Refresh genres in every hour.""" + await self._get_genres() async def _get_genres(self) -> None: """Create genres variable for games command.""" -- cgit v1.2.3 From 69941d874d708defaa20e30fe4afada37b72ad2a Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 09:54:15 +0200 Subject: (Games Cog): Added .games refresh|r command for refreshing genres. --- bot/seasons/evergreen/game.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 727985a3..de8d0109 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -13,7 +13,8 @@ from discord.ext import tasks from discord.ext.commands import Cog, Context, group from bot.bot import SeasonalBot -from bot.constants import Tokens +from bot.constants import STAFF_ROLES, Tokens +from bot.decorators import with_role from bot.pagination import ImagePaginator, LinePaginator # Base URL of IGDB API @@ -252,6 +253,17 @@ class Games(Cog): await ImagePaginator.paginate(pages, ctx, Embed(title="Random Game Companies")) + @with_role(*STAFF_ROLES) + @games.command(name="refresh", aliases=["r"]) + async def refresh_genres_command(self, ctx: Context) -> None: + """Refresh .games command genres.""" + try: + await self._get_genres() + except Exception as e: + await ctx.send(f"There was error while refreshing genres: `{e}`") + return + await ctx.send("Successfully refreshed genres.") + async def get_games_list(self, amount: int, genre: Optional[str] = None, -- cgit v1.2.3 From ae500f85bd664bd59d818866a89b3533bc9e94b1 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 10:06:48 +0200 Subject: (Games Cog): Added try block to genres refresh task. --- bot/seasons/evergreen/game.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index de8d0109..21c3b5d7 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -150,7 +150,12 @@ class Games(Cog): @tasks.loop(hours=1.0) async def refresh_genres_task(self) -> None: """Refresh genres in every hour.""" - await self._get_genres() + try: + await self._get_genres() + except Exception as e: + logger.warning(f"There was error while refreshing genres: {e}") + return + logger.info("Successfully refreshed genres.") async def _get_genres(self) -> None: """Create genres variable for games command.""" -- cgit v1.2.3 From 918c11fcba1a47f78ce17eb7278da294d95e314c Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 13:36:16 +0200 Subject: (Games Cog): Stop refreshing genres task when Cog unload --- bot/seasons/evergreen/game.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 21c3b5d7..97df2bdb 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -157,6 +157,11 @@ class Games(Cog): return logger.info("Successfully refreshed genres.") + def cog_unload(self) -> None: + """Cancel genres refreshing start when unloading Cog.""" + self.refresh_genres_task.cancel() + logger.info("Successfully stopped Genres Refreshing task.") + async def _get_genres(self) -> None: """Create genres variable for games command.""" body = "fields name; limit 100;" -- cgit v1.2.3 From 75c3024209f62c327f002cefed9b148e31b98479 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 13:49:55 +0200 Subject: (Games Cog): Remove too much empty lines in .games search command, simplify lines. --- bot/seasons/evergreen/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 97df2bdb..7774484e 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -243,7 +243,7 @@ class Games(Cog): """Find games by name.""" lines = await self.search_games(search_term) - await LinePaginator.paginate((line for line in lines), ctx, Embed(title=f"Game Search Results: {search_term}")) + await LinePaginator.paginate(lines, ctx, Embed(title=f"Game Search Results: {search_term}"), empty=False) @games.command(name="company", aliases=["companies"]) async def company(self, ctx: Context, amount: int = 5) -> None: -- cgit v1.2.3 From 4b2cc3910e535158626722a0168a7b2202b0d826 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 14:40:07 +0200 Subject: (Games Cog): Replaced - with space in genre aliases, added multiword genres support for .games command, modified docstring to explain this and added str.title() to embed title genre showing. --- bot/seasons/evergreen/game.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 7774484e..957e1195 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -95,10 +95,10 @@ LOGO_URL = Template("https://images.igdb.com/igdb/image/upload/t_logo_med/${imag # Create aliases for complex genre names ALIASES = { - "Role-playing (rpg)": ["Role-playing", "Rpg"], - "Turn-based strategy (tbs)": ["Turn-based-strategy", "Tbs"], - "Real time strategy (rts)": ["Real-time-strategy", "Rts"], - "Hack and slash/beat 'em up": ["Hack-and-slash"] + "Role-playing (rpg)": ["Role playing", "Rpg"], + "Turn-based strategy (tbs)": ["Turn based strategy", "Tbs"], + "Real time strategy (rts)": ["Real time strategy", "Rts"], + "Hack and slash/beat 'em up": ["Hack and slash"] } @@ -181,12 +181,13 @@ class Games(Cog): self.genres[genre_name] = genre @group(name="games", aliases=["game"], invoke_without_command=True) - async def games(self, ctx: Context, genre: Optional[str] = None, amount: int = 5) -> None: + async def games(self, ctx: Context, amount: Optional[int] = 5, *, genre: Optional[str] = None) -> None: """ Get random game(s) by genre from IGDB. Use .games genres command to get all available genres. - Also support amount parameter, what max is 25 and min 1, default 5. Use quotes ("") for genres with multiple - words. + Also support amount parameter, what max is 25 and min 1, default 5. Supported formats: + - .games + - .games """ # When user didn't specified genre, send help message if genre is None: @@ -194,7 +195,7 @@ class Games(Cog): return # Capitalize genre for check - genre = genre.capitalize() + genre = "".join(genre).capitalize() # Check for amounts, max is 25 and min 1 if not 1 <= amount <= 25: @@ -214,7 +215,7 @@ class Games(Cog): # Create pages and paginate pages = [await self.create_page(game) for game in games] - await ImagePaginator.paginate(pages, ctx, Embed(title=f"Random {genre} Games")) + await ImagePaginator.paginate(pages, ctx, Embed(title=f"Random {genre.title()} Games")) @games.command(name="top", aliases=["t"]) async def top(self, ctx: Context, amount: int = 10) -> None: -- cgit v1.2.3 From ea8484858d849212d8ce1ae191bc1d1178859cc2 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 15:09:54 +0200 Subject: (Games Cog): Moved self.genres to __init__ and added type hints. Added lower `difflib.get_close_matches` cutoff from 0.6 (default) to 0.4. --- bot/seasons/evergreen/game.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 957e1195..65598c17 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -145,6 +145,8 @@ class Games(Cog): self.bot = bot self.http_session: ClientSession = bot.http_session + self.genres: Dict[str, int] = {} + self.refresh_genres_task.start() @tasks.loop(hours=1.0) @@ -170,8 +172,6 @@ class Games(Cog): genres = {genre["name"].capitalize(): genre["id"] for genre in result} - self.genres = {} - # Replace complex names with names from ALIASES for genre_name, genre in genres.items(): if genre_name in ALIASES: @@ -208,7 +208,7 @@ class Games(Cog): try: games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) except KeyError: - possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres)) + possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres, cutoff=0.4)) await ctx.send(f"Invalid genre `{genre}`. {f'Maybe you meant `{possibilities}`?' if possibilities else ''}") return -- cgit v1.2.3 From 3b2926e50368c8cb547b7d93741401268980cb48 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 4 Mar 2020 15:43:58 +0200 Subject: (Games Cog): Moved `string.Template` to `str.format()`, applied changes everywhere. --- bot/seasons/evergreen/game.py | 88 ++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 51 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index 65598c17..bb48123d 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -1,10 +1,8 @@ import difflib import logging import random -import textwrap from datetime import datetime as dt from enum import IntEnum -from string import Template from typing import Any, Dict, List, Optional, Tuple from aiohttp import ClientSession @@ -33,65 +31,53 @@ logger = logging.getLogger(__name__) # Body templates # Request body template for get_games_list -GAMES_LIST_BODY = Template( - textwrap.dedent(""" - fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, status, - involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, total_rating_count; - ${sort} ${limit} ${offset} ${genre} ${additional} - """) +GAMES_LIST_BODY = ( + "fields cover.image_id, first_release_date, total_rating, name, storyline, url, platforms.name, status," + "involved_companies.company.name, summary, age_ratings.category, age_ratings.rating, total_rating_count;" + "{sort} {limit} {offset} {genre} {additional}" ) # Request body template for get_companies_list -COMPANIES_LIST_BODY = Template( - textwrap.dedent(""" - fields name, url, start_date, logo.image_id, developed.name, published.name, description; - offset ${offset}; - limit ${limit}; - """) +COMPANIES_LIST_BODY = ( + "fields name, url, start_date, logo.image_id, developed.name, published.name, description;" + "offset {offset}; limit {limit};" ) # Request body template for games search -SEARCH_BODY = Template('fields name, url, storyline, total_rating, total_rating_count; limit 50; search "${term}";') +SEARCH_BODY = 'fields name, url, storyline, total_rating, total_rating_count; limit 50; search "{term}";' # Pages templates # Game embed layout -GAME_PAGE = Template( - textwrap.dedent(""" - **[${name}](${url})** - ${description} - **Release Date:** ${release_date} - **Rating:** ${rating}/100 :star: (based on ${rating_count} ratings) - **Platforms:** ${platforms} - **Status:** ${status} - **Age Ratings:** ${age_ratings} - **Made by:** ${made_by} - - ${storyline} - """) +GAME_PAGE = ( + "**[{name}]({url})**\n" + "{description}" + "**Release Date:** {release_date}\n" + "**Rating:** {rating}/100 :star: (based on {rating_count} ratings)\n" + "**Platforms:** {platforms}\n" + "**Status:** {status}\n" + "**Age Ratings:** {age_ratings}\n" + "**Made by:** {made_by}\n\n" + "{storyline}" ) # .games company command page layout -COMPANY_PAGE = Template( - textwrap.dedent(""" - **[${name}](${url})** - ${description} - **Founded:** ${founded} - **Developed:** ${developed} - **Published:** ${published} - """) +COMPANY_PAGE = ( + "**[{name}]({url})**\n" + "{description}" + "**Founded:** {founded}\n" + "**Developed:** {developed}\n" + "**Published:** {published}" ) # For .games search command line layout -GAME_SEARCH_LINE = Template( - textwrap.dedent(""" - **[${name}](${url})** - ${rating}/100 :star: (based on ${rating_count} ratings) - """) +GAME_SEARCH_LINE = ( + "**[{name}]({url})**\n" + "{rating}/100 :star: (based on {rating_count} ratings)" ) # URL templates -COVER_URL = Template("https://images.igdb.com/igdb/image/upload/t_cover_big/${image_id}.jpg") -LOGO_URL = Template("https://images.igdb.com/igdb/image/upload/t_logo_med/${image_id}.png") +COVER_URL = "https://images.igdb.com/igdb/image/upload/t_cover_big/{image_id}.jpg" +LOGO_URL = "https://images.igdb.com/igdb/image/upload/t_logo_med/{image_id}.png" # Create aliases for complex genre names ALIASES = { @@ -298,7 +284,7 @@ class Games(Cog): "genre": f"where genres = ({genre});" if genre else "", "additional": additional_body } - body = GAMES_LIST_BODY.substitute(params) + 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: @@ -307,7 +293,7 @@ class Games(Cog): async def create_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create content of Game Page.""" # Create cover image URL from template - url = COVER_URL.substitute({"image_id": data["cover"]["image_id"] if "cover" in data else ""}) + url = COVER_URL.format(**{"image_id": data["cover"]["image_id"] if "cover" in data else ""}) # Get release date separately with checking release_date = dt.utcfromtimestamp(data["first_release_date"]).date() if "first_release_date" in data else "?" @@ -332,7 +318,7 @@ class Games(Cog): "made_by": ", ".join(companies), "storyline": data["storyline"] if "storyline" in data else "" } - page = GAME_PAGE.substitute(formatting) + page = GAME_PAGE.format(**formatting) return page, url @@ -341,7 +327,7 @@ class Games(Cog): lines = [] # Define request body of IGDB API request and do request - body = SEARCH_BODY.substitute({"term": search_term}) + body = SEARCH_BODY.format(**{"term": search_term}) async with self.http_session.get(url=f"{BASE_URL}/games", data=body, headers=HEADERS) as resp: data = await resp.json() @@ -354,7 +340,7 @@ class Games(Cog): "rating": round(game["total_rating"] if "total_rating" in game else 0, 2), "rating_count": game["total_rating_count"] if "total_rating" in game else "?" } - line = GAME_SEARCH_LINE.substitute(formatting) + line = GAME_SEARCH_LINE.format(**formatting) lines.append(line) return lines @@ -367,7 +353,7 @@ class Games(Cog): returning results. """ # Create request body from template - body = COMPANIES_LIST_BODY.substitute({ + body = COMPANIES_LIST_BODY.format(**{ "limit": limit, "offset": offset }) @@ -378,7 +364,7 @@ class Games(Cog): async def create_company_page(self, data: Dict[str, Any]) -> Tuple[str, str]: """Create good formatted Game Company page.""" # Generate URL of company logo - url = LOGO_URL.substitute({"image_id": data["logo"]["image_id"] if "logo" in data else ""}) + url = LOGO_URL.format(**{"image_id": data["logo"]["image_id"] if "logo" in data else ""}) # Try to get found date of company founded = dt.utcfromtimestamp(data["start_date"]).date() if "start_date" in data else "?" @@ -395,7 +381,7 @@ class Games(Cog): "developed": developed, "published": published } - page = COMPANY_PAGE.substitute(formatting) + page = COMPANY_PAGE.format(**formatting) return page, url -- cgit v1.2.3 From 6c1ce462cc8594c224cc8fe78e36557a6be44b9c Mon Sep 17 00:00:00 2001 From: Karlis S <45097959+ks129@users.noreply.github.com> Date: Wed, 4 Mar 2020 19:04:50 +0200 Subject: (Games Cog): Added space between game search result + removed cutoff in get_close_matches. --- bot/seasons/evergreen/game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/seasons/evergreen/game.py b/bot/seasons/evergreen/game.py index bb48123d..e6700937 100644 --- a/bot/seasons/evergreen/game.py +++ b/bot/seasons/evergreen/game.py @@ -72,7 +72,7 @@ COMPANY_PAGE = ( # For .games search command line layout GAME_SEARCH_LINE = ( "**[{name}]({url})**\n" - "{rating}/100 :star: (based on {rating_count} ratings)" + "{rating}/100 :star: (based on {rating_count} ratings)\n" ) # URL templates @@ -194,7 +194,7 @@ class Games(Cog): try: games = await self.get_games_list(amount, self.genres[genre], offset=random.randint(0, 150)) except KeyError: - possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres, cutoff=0.4)) + possibilities = "`, `".join(difflib.get_close_matches(genre, self.genres)) await ctx.send(f"Invalid genre `{genre}`. {f'Maybe you meant `{possibilities}`?' if possibilities else ''}") return -- cgit v1.2.3 From bbe81ebe0c27ea65fec1170648976d4fcd8c3f3c Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 4 Mar 2020 17:47:54 -0800 Subject: Update dependencies The pipfile will need to be re-locked to add a dependency so may as well re-pin some dependencies to reflect the more recent versions that will be used. discord.py was pinned to a patch version instead of a minor version to be on the safe side. Notable updates: * discord.py -> 1.3.2 * flake8-annotation -> 2.0 * pre-commit -> 2.1 --- Pipfile | 15 +- Pipfile.lock | 568 +++++++++++++++-------------- bot/seasons/evergreen/snakes/snakes_cog.py | 4 +- bot/seasons/halloween/hacktoberstats.py | 12 +- bot/seasons/valentines/be_my_valentine.py | 6 +- tox.ini | 2 +- 6 files changed, 317 insertions(+), 290 deletions(-) (limited to 'bot') diff --git a/Pipfile b/Pipfile index c066958e..be17343f 100644 --- a/Pipfile +++ b/Pipfile @@ -7,21 +7,22 @@ name = "pypi" aiodns = "~=2.0" arrow = "~=0.14" beautifulsoup4 = "~=4.8" -discord-py = "~=1.2" +discord-py = "~=1.3.2" fuzzywuzzy = "~=0.17" pillow = "~=6.2" pytz = "~=2019.2" [dev-packages] flake8 = "~=3.7" -flake8-annotations = "~=1.1" -flake8-bugbear = "~=19.8" -flake8-docstrings = "~=1.4" +flake8-annotations = "~=2.0" +flake8-bugbear = "~=20.1" +flake8-docstrings = "~=1.5" flake8-import-order = "~=0.18" -flake8-string-format = "~=0.2" -flake8-tidy-imports = "~=2.0" +flake8-string-format = "~=0.3" +flake8-tidy-imports = "~=4.0" flake8-todo = "~=0.7" -pre-commit = "~=1.18" +pep8-naming = "~=0.9" +pre-commit = "~=2.1" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 3252f36f..659a046c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "40f23ea08504def8d3d5f56379820221088d93e9bf81d739850dc97ea8a4b7dc" + "sha256": "4cd9801f890f8087b7a46b239264e6b09d4c29c35223118add96bed0af22b475" }, "pipfile-spec": 6, "requires": { @@ -26,38 +26,28 @@ }, "aiohttp": { "hashes": [ - "sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55", - "sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed", - "sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10", - "sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5", - "sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1", - "sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939", - "sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390", - "sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa", - "sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc", - "sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5", - "sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d", - "sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf", - "sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6", - "sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72", - "sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12", - "sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366", - "sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4", - "sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300", - "sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d", - "sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303", - "sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6", - "sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889" - ], - "version": "==3.5.4" + "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", + "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", + "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", + "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", + "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", + "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", + "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", + "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", + "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", + "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", + "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", + "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" + ], + "version": "==3.6.2" }, "arrow": { "hashes": [ - "sha256:01a16d8a93eddf86a29237f32ae36b29c27f047e79312eb4df5d55fd5a2b3183", - "sha256:e1a318a4c0b787833ae46302c02488b6eeef413c6a13324b3261ad320f21ec1e" + "sha256:5390e464e2c5f76971b60ffa7ee29c598c7501a294bc9f5e6dadcb251a5d027b", + "sha256:70729bcc831da496ca3cb4b7e89472c8e2d27d398908155e0796179f6d2d41ee" ], "index": "pypi", - "version": "==0.15.4" + "version": "==0.15.5" }, "async-timeout": { "hashes": [ @@ -75,50 +65,45 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:5279c36b4b2ec2cb4298d723791467e3000e5384a43ea0cdf5d45207c7e97169", - "sha256:6135db2ba678168c07950f9a16c4031822c6f4aec75a65e0a97bc5ca09789931", - "sha256:dcdef580e18a76d54002088602eba453eec38ebbcafafeaabd8cab12b6155d57" + "sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a", + "sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887", + "sha256:e1505eeed31b0f4ce2dbb3bc8eb256c04cc2b3b72af7d551a4ab6efd5cbe5dae" ], "index": "pypi", - "version": "==4.8.1" + "version": "==4.8.2" }, "cffi": { "hashes": [ - "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", - "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", - "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", - "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", - "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", - "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", - "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", - "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", - "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", - "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", - "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", - "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", - "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", - "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", - "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", - "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", - "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", - "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", - "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", - "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", - "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", - "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", - "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", - "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", - "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", - "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", - "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", - "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", - "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", - "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", - "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", - "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", - "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" - ], - "version": "==1.13.2" + "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", + "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", + "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", + "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", + "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", + "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", + "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", + "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", + "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", + "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", + "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", + "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", + "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", + "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", + "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", + "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", + "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", + "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", + "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", + "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", + "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", + "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", + "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", + "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", + "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", + "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", + "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", + "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" + ], + "version": "==1.14.0" }, "chardet": { "hashes": [ @@ -129,107 +114,124 @@ }, "discord-py": { "hashes": [ - "sha256:7c843b523bb011062b453864e75c7b675a03faf573c58d14c9f096e85984329d" + "sha256:7424be26b07b37ecad4404d9383d685995a0e0b3df3f9c645bdd3a4d977b83b4" ], "index": "pypi", - "version": "==1.2.5" + "version": "==1.3.2" }, "fuzzywuzzy": { "hashes": [ - "sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254", - "sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62" + "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8", + "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993" ], "index": "pypi", - "version": "==0.17.0" + "version": "==0.18.0" }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "multidict": { "hashes": [ - "sha256:07f9a6bf75ad675d53956b2c6a2d4ef2fa63132f33ecc99e9c24cf93beb0d10b", - "sha256:0ffe4d4d28cbe9801952bfb52a8095dd9ffecebd93f84bdf973c76300de783c5", - "sha256:1b605272c558e4c659dbaf0fb32a53bfede44121bcf77b356e6e906867b958b7", - "sha256:205a011e636d885af6dd0029e41e3514a46e05bb2a43251a619a6e8348b96fc0", - "sha256:250632316295f2311e1ed43e6b26a63b0216b866b45c11441886ac1543ca96e1", - "sha256:2bc9c2579312c68a3552ee816311c8da76412e6f6a9cf33b15152e385a572d2a", - "sha256:318aadf1cfb6741c555c7dd83d94f746dc95989f4f106b25b8a83dfb547f2756", - "sha256:42cdd649741a14b0602bf15985cad0dd4696a380081a3319cd1ead46fd0f0fab", - "sha256:5159c4975931a1a78bf6602bbebaa366747fce0a56cb2111f44789d2c45e379f", - "sha256:87e26d8b89127c25659e962c61a4c655ec7445d19150daea0759516884ecb8b4", - "sha256:891b7e142885e17a894d9d22b0349b92bb2da4769b4e675665d0331c08719be5", - "sha256:8d919034420378132d074bf89df148d0193e9780c9fe7c0e495e895b8af4d8a2", - "sha256:9c890978e2b37dd0dc1bd952da9a5d9f245d4807bee33e3517e4119c48d66f8c", - "sha256:a37433ce8cdb35fc9e6e47e1606fa1bfd6d70440879038dca7d8dd023197eaa9", - "sha256:c626029841ada34c030b94a00c573a0c7575fe66489cde148785b6535397d675", - "sha256:cfec9d001a83dc73580143f3c77e898cf7ad78b27bb5e64dbe9652668fcafec7", - "sha256:efaf1b18ea6c1f577b1371c0159edbe4749558bfe983e13aa24d0a0c01e1ad7b" - ], - "version": "==4.6.1" + "sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1", + "sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35", + "sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928", + "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969", + "sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e", + "sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78", + "sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1", + "sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136", + "sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8", + "sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2", + "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e", + "sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4", + "sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5", + "sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd", + "sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab", + "sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20", + "sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3" + ], + "version": "==4.7.5" }, "pillow": { "hashes": [ - "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", - "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", - "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", - "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", - "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", - "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", - "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", - "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", - "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", - "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", - "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", - "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", - "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", - "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", - "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", - "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", - "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", - "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", - "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", - "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", - "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", - "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", - "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", - "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", - "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", - "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", - "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", - "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", - "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", - "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0" + "sha256:00e0bbe9923adc5cc38a8da7d87d4ce16cde53b8d3bba8886cb928e84522d963", + "sha256:03457e439d073770d88afdd90318382084732a5b98b0eb6f49454746dbaae701", + "sha256:0d5c99f80068f13231ac206bd9b2e80ea357f5cf9ae0fa97fab21e32d5b61065", + "sha256:1a3bc8e1db5af40a81535a62a591fafdb30a8a1b319798ea8052aa65ef8f06d2", + "sha256:2b4a94be53dff02af90760c10a2e3634c3c7703410f38c98154d5ce71fe63d20", + "sha256:3ba7d8f1d962780f86aa747fef0baf3211b80cb13310fff0c375da879c0656d4", + "sha256:3e81485cec47c24f5fb27acb485a4fc97376b2b332ed633867dc68ac3077998c", + "sha256:43ef1cff7ee57f9c8c8e6fa02a62eae9fa23a7e34418c7ce88c0e3fe09d1fb38", + "sha256:4adc3302df4faf77c63ab3a83e1a3e34b94a6a992084f4aa1cb236d1deaf4b39", + "sha256:535e8e0e02c9f1fc2e307256149d6ee8ad3aa9a6e24144b7b6e6fb6126cb0e99", + "sha256:5ccfcb0a34ad9b77ad247c231edb781763198f405a5c8dc1b642449af821fb7f", + "sha256:5dcbbaa3a24d091a64560d3c439a8962866a79a033d40eb1a75f1b3413bfc2bc", + "sha256:6e2a7e74d1a626b817ecb7a28c433b471a395c010b2a1f511f976e9ea4363e64", + "sha256:82859575005408af81b3e9171ae326ff56a69af5439d3fc20e8cb76cd51c8246", + "sha256:834dd023b7f987d6b700ad93dc818098d7eb046bd445e9992b3093c6f9d7a95f", + "sha256:87ef0eca169f7f0bc050b22f05c7e174a65c36d584428431e802c0165c5856ea", + "sha256:900de1fdc93764be13f6b39dc0dd0207d9ff441d87ad7c6e97e49b81987dc0f3", + "sha256:92b83b380f9181cacc994f4c983d95a9c8b00b50bf786c66d235716b526a3332", + "sha256:aa1b0297e352007ec781a33f026afbb062a9a9895bb103c8f49af434b1666880", + "sha256:aa4792ab056f51b49e7d59ce5733155e10a918baf8ce50f64405db23d5627fa2", + "sha256:b72c39585f1837d946bd1a829a4820ccf86e361f28cbf60f5d646f06318b61e2", + "sha256:bb7861e4618a0c06c40a2e509c1bea207eea5fd4320d486e314e00745a402ca5", + "sha256:bc149dab804291a18e1186536519e5e122a2ac1316cb80f506e855a500b1cdd4", + "sha256:c424d35a5259be559b64490d0fd9e03fba81f1ce8e5b66e0a59de97547351d80", + "sha256:cbd5647097dc55e501f459dbac7f1d0402225636deeb9e0a98a8d2df649fc19d", + "sha256:ccf16fe444cc43800eeacd4f4769971200982200a71b1368f49410d0eb769543", + "sha256:d3a98444a00b4643b22b0685dbf9e0ddcaf4ebfd4ea23f84f228adf5a0765bb2", + "sha256:d6b4dc325170bee04ca8292bbd556c6f5398d52c6149ca881e67daf62215426f", + "sha256:db9ff0c251ed066d367f53b64827cc9e18ccea001b986d08c265e53625dab950", + "sha256:e3a797a079ce289e59dbd7eac9ca3bf682d52687f718686857281475b7ca8e6a" ], "index": "pypi", - "version": "==6.2.1" + "version": "==6.2.2" }, "pycares": { "hashes": [ - "sha256:2ca080db265ea238dc45f997f94effb62b979a617569889e265c26a839ed6305", - "sha256:6f79c6afb6ce603009db2042fddc2e348ad093ece9784cbe2daa809499871a23", - "sha256:70918d06eb0603016d37092a5f2c0228509eb4e6c5a3faacb4184f6ab7be7650", - "sha256:755187d28d24a9ea63aa2b4c0638be31d65fbf7f0ce16d41261b9f8cb55a1b99", - "sha256:7baa4b1f2146eb8423ff8303ebde3a20fb444a60db761fba0430d104fe35ddbf", - "sha256:90b27d4df86395f465a171386bc341098d6d47b65944df46518814ae298f6cc6", - "sha256:9e090dd6b2afa65cb51c133883b2bf2240fd0f717b130b0048714b33fb0f47ce", - "sha256:a11b7d63c3718775f6e805d6464cb10943780395ab042c7e5a0a7a9f612735dd", - "sha256:b253f5dcaa0ac7076b79388a3ac80dd8f3bd979108f813baade40d3a9b8bf0bd", - "sha256:c7f4f65e44ba35e35ad3febc844270665bba21cfb0fb7d749434e705b556e087", - "sha256:cdb342e6a254f035bd976d95807a2184038fc088d957a5104dcaab8be602c093", - "sha256:cf08e164f8bfb83b9fe633feb56f2754fae6baefcea663593794fa0518f8f98c", - "sha256:df9bc694cf03673878ea8ce674082c5acd134991d64d6c306d4bd61c0c1df98f" - ], - "version": "==3.0.0" + "sha256:050f00b39ed77ea8a4e555f09417d4b1a6b5baa24bb9531a3e15d003d2319b3f", + "sha256:0a24d2e580a8eb567140d7b69f12cb7de90c836bd7b6488ec69394d308605ac3", + "sha256:0c5bd1f6f885a219d5e972788d6eef7b8043b55c3375a845e5399638436e0bba", + "sha256:11c628402cc8fc8ef461076d4e47f88afc1f8609989ebbff0dbffcd54c97239f", + "sha256:18dfd4fd300f570d6c4536c1d987b7b7673b2a9d14346592c5d6ed716df0d104", + "sha256:1917b82494907a4a342db420bc4dd5bac355a5fa3984c35ba9bf51422b020b48", + "sha256:1b90fa00a89564df059fb18e796458864cc4e00cb55e364dbf921997266b7c55", + "sha256:1d8d177c40567de78108a7835170f570ab04f09084bfd32df9919c0eaec47aa1", + "sha256:236286f81664658b32c141c8e79d20afc3d54f6e2e49dfc8b702026be7265855", + "sha256:2e4f74677542737fb5af4ea9a2e415ec5ab31aa67e7b8c3c969fdb15c069f679", + "sha256:48a7750f04e69e1f304f4332b755728067e7c4b1abe2760bba1cacd9ff7a847a", + "sha256:7d86e62b700b21401ffe7fd1bbfe91e08489416fecae99c6570ab023c6896022", + "sha256:7e2d7effd08d2e5a3cb95d98a7286ebab71ab2fbce84fa93cc2dd56caf7240dd", + "sha256:81edb016d9e43dde7473bc3999c29cdfee3a6b67308fed1ea21049f458e83ae0", + "sha256:96c90e11b4a4c7c0b8ff5aaaae969c5035493136586043ff301979aae0623941", + "sha256:9a0a1845f8cb2e62332bca0aaa9ad5494603ac43fb60d510a61d5b5b170d7216", + "sha256:a05bbfdfd41f8410a905a818f329afe7510cbd9ee65c60f8860a72b6c64ce5dc", + "sha256:a5089fd660f0b0d228b14cdaa110d0d311edfa5a63f800618dbf1321dcaef66b", + "sha256:c457a709e6f2befea7e2996c991eda6d79705dd075f6521593ba6ebc1485b811", + "sha256:c5cb72644b04e5e5abfb1e10a0e7eb75da6684ea0e60871652f348e412cf3b11", + "sha256:cce46dd4717debfd2aab79d6d7f0cbdf6b1e982dc4d9bebad81658d59ede07c2", + "sha256:cfdd1f90bcf373b00f4b2c55ea47868616fe2f779f792fc913fa82a3d64ffe43", + "sha256:d88a279cbc5af613f73e86e19b3f63850f7a2e2736e249c51995dedcc830b1bb", + "sha256:eba9a9227438da5e78fc8eee32f32eb35d9a50cf0a0bd937eb6275c7cc3015fe", + "sha256:eee7b6a5f5b5af050cb7d66ab28179287b416f06d15a8974ac831437fec51336", + "sha256:f41ac1c858687e53242828c9f59c2e7b0b95dbcd5bdd09c7e5d3c48b0f89a25a", + "sha256:f8deaefefc3a589058df1b177275f79233e8b0eeee6734cf4336d80164ecd022", + "sha256:fa78e919f3bd7d6d075db262aa41079b4c02da315c6043c6f43881e2ebcdd623", + "sha256:fadb97d2e02dabdc15a0091591a972a938850d79ddde23d385d813c1731983f0" + ], + "version": "==3.1.1" }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], - "version": "==2.19" + "version": "==2.20" }, "python-dateutil": { "hashes": [ @@ -248,74 +250,75 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "soupsieve": { "hashes": [ - "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5", - "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda" + "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae", + "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69" ], - "version": "==1.9.5" + "version": "==2.0" }, "websockets": { "hashes": [ - "sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136", - "sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6", - "sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1", - "sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538", - "sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4", - "sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908", - "sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0", - "sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d", - "sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c", - "sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d", - "sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c", - "sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb", - "sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf", - "sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e", - "sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96", - "sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584", - "sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484", - "sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d", - "sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559", - "sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff", - "sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454" - ], - "version": "==6.0" + "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", + "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", + "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", + "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", + "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", + "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", + "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", + "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", + "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", + "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", + "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", + "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", + "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", + "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", + "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", + "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", + "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", + "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", + "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", + "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", + "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", + "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" + ], + "version": "==8.1" }, "yarl": { "hashes": [ - "sha256:031e8f56cf085d3b3df6b6bce756369ea7052b82d35ea07b6045f209c819e0e5", - "sha256:074958fe4578ef3a3d0bdaf96bbc25e4c4db82b7ff523594776fcf3d3f16c531", - "sha256:2db667ee21f620b446a54a793e467714fc5a446fcc82d93a47e8bde01d69afab", - "sha256:326f2dbaaa17b858ae86f261ae73a266fd820a561fc5142cee9d0fc58448fbd7", - "sha256:32a3885f542f74d0f4f87057050c6b45529ebd79d0639f56582e741521575bfe", - "sha256:56126ef061b913c3eefecace3404ca88917265d0550b8e32bbbeab29e5c830bf", - "sha256:589ac1e82add13fbdedc04eb0a83400db728e5f1af2bd273392088ca90de7062", - "sha256:6076bce2ecc6ebf6c92919d77762f80f4c9c6ecc9c1fbaa16567ec59ad7d6f1d", - "sha256:63be649c535d18ab6230efbc06a07f7779cd4336a687672defe70c025349a47b", - "sha256:6642cbc92eaffa586180f669adc772f5c34977e9e849e93f33dc142351e98c9c", - "sha256:6fa05a25f2280e78a514041d4609d39962e7d51525f2439db9ad7a2ae7aac163", - "sha256:7ed006a220422c33ff0889288be24db56ff0a3008ffe9eaead58a690715ad09b", - "sha256:80c9c213803b50899460cc355f47e66778c3c868f448b7b7de5b1f1858c82c2a", - "sha256:8bae18e2129850e76969b57869dacc72a66cccdbeebce1a28d7f3d439c21a7a3", - "sha256:ab112fba996a8f48f427e26969f2066d50080df0c24007a8cc6d7ae865e19013", - "sha256:b1c178ef813940c9a5cbad42ab7b8b76ac08b594b0a6bad91063c968e0466efc", - "sha256:d6eff151c3b23a56a5e4f496805619bc3bdf4f749f63a7a95ad50e8267c17475" - ], - "version": "==1.4.1" + "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce", + "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6", + "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce", + "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae", + "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d", + "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f", + "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b", + "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b", + "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb", + "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462", + "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea", + "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70", + "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1", + "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a", + "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b", + "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080", + "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2" + ], + "version": "==1.4.2" } }, "develop": { - "aspy.yaml": { + "appdirs": { "hashes": [ - "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc", - "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45" + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" ], - "version": "==1.3.0" + "version": "==1.4.3" }, "attrs": { "hashes": [ @@ -326,10 +329,16 @@ }, "cfgv": { "hashes": [ - "sha256:edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144", - "sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289" + "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53", + "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513" ], - "version": "==2.0.1" + "version": "==3.1.0" + }, + "distlib": { + "hashes": [ + "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" + ], + "version": "==0.3.0" }, "entrypoints": { "hashes": [ @@ -338,6 +347,13 @@ ], "version": "==0.3" }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" + }, "flake8": { "hashes": [ "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", @@ -348,19 +364,19 @@ }, "flake8-annotations": { "hashes": [ - "sha256:6ac7ca1e706307686b60af8043ff1db31dc2cfc1233c8210d67a3d9b8f364736", - "sha256:b51131007000d67217608fa028a35ff80aa400b474e5972f1f99c2cf9d26bd2e" + "sha256:a38b44d01abd480586a92a02a2b0a36231ec42dcc5e114de78fa5db016d8d3f9", + "sha256:d5b0e8704e4e7728b352fa1464e23539ff2341ba11cc153b536fa2cf921ee659" ], "index": "pypi", - "version": "==1.1.0" + "version": "==2.0.1" }, "flake8-bugbear": { "hashes": [ - "sha256:d8c466ea79d5020cb20bf9f11cf349026e09517a42264f313d3f6fddb83e0571", - "sha256:ded4d282778969b5ab5530ceba7aa1a9f1b86fa7618fc96a19a1d512331640f8" + "sha256:a3ddc03ec28ba2296fc6f89444d1c946a6b76460f859795b35b77d4920a51b63", + "sha256:bd02e4b009fb153fe6072c31c52aeab5b133d508095befb2ffcf3b41c4823162" ], "index": "pypi", - "version": "==19.8.0" + "version": "==20.1.4" }, "flake8-docstrings": { "hashes": [ @@ -378,21 +394,28 @@ "index": "pypi", "version": "==0.18.1" }, + "flake8-polyfill": { + "hashes": [ + "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9", + "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda" + ], + "version": "==1.0.2" + }, "flake8-string-format": { "hashes": [ - "sha256:68ea72a1a5b75e7018cae44d14f32473c798cf73d75cbaed86c6a9a907b770b2", - "sha256:774d56103d9242ed968897455ef49b7d6de272000cfa83de5814273a868832f1" + "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2", + "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af" ], "index": "pypi", - "version": "==0.2.3" + "version": "==0.3.0" }, "flake8-tidy-imports": { "hashes": [ - "sha256:1c476aabc6e8db26dc75278464a3a392dba0ea80562777c5f13fd5cdf2646154", - "sha256:b3f5b96affd0f57cacb6621ed28286ce67edaca807757b51227043ebf7b136a1" + "sha256:8aa34384b45137d4cf33f5818b8e7897dc903b1d1e10a503fa7dd193a9a710ba", + "sha256:b26461561bcc80e8012e46846630ecf0aaa59314f362a94cb7800dfdb32fa413" ], "index": "pypi", - "version": "==2.0.0" + "version": "==4.0.0" }, "flake8-todo": { "hashes": [ @@ -403,18 +426,18 @@ }, "identify": { "hashes": [ - "sha256:4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017", - "sha256:d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e" + "sha256:1222b648251bdcb8deb240b294f450fbf704c7984e08baa92507e4ea10b436d5", + "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96" ], - "version": "==1.4.7" + "version": "==1.4.11" }, "importlib-metadata": { "hashes": [ - "sha256:b044f07694ef14a6683b097ba56bd081dbc7cdc7c7fe46011e499dfecc082f21", - "sha256:e6ac600a142cf2db707b1998382cc7fc3b02befb7273876e01b8ad10b9652742" + "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", + "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" ], "markers": "python_version < '3.8'", - "version": "==1.1.0" + "version": "==1.5.0" }, "mccabe": { "hashes": [ @@ -423,26 +446,27 @@ ], "version": "==0.6.1" }, - "more-itertools": { + "nodeenv": { "hashes": [ - "sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2", - "sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45" + "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212" ], - "version": "==8.0.0" + "version": "==1.3.5" }, - "nodeenv": { + "pep8-naming": { "hashes": [ - "sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a" + "sha256:45f330db8fcfb0fba57458c77385e288e7a3be1d01e8ea4268263ef677ceea5f", + "sha256:a33d38177056321a167decd6ba70b890856ba5025f0a8eca6a3eda607da93caf" ], - "version": "==1.3.3" + "index": "pypi", + "version": "==0.9.1" }, "pre-commit": { "hashes": [ - "sha256:9f152687127ec90642a2cc3e4d9e1e6240c4eb153615cb02aa1ad41d331cbb6e", - "sha256:c2e4810d2d3102d354947907514a78c5d30424d299dc0fe48f5aa049826e9b50" + "sha256:09ebe467f43ce24377f8c2f200fe3cd2570d328eb2ce0568c8e96ce19da45fa6", + "sha256:f8d555e31e2051892c7f7b3ad9f620bd2c09271d87e9eedb2ad831737d6211eb" ], "index": "pypi", - "version": "==1.20.0" + "version": "==2.1.1" }, "pycodestyle": { "hashes": [ @@ -453,10 +477,10 @@ }, "pydocstyle": { "hashes": [ - "sha256:04c84e034ebb56eb6396c820442b8c4499ac5eb94a3bda88951ac3dc519b6058", - "sha256:66aff87ffe34b1e49bff2dd03a88ce6843be2f3346b0c9814410d34987fbab59" + "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586", + "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5" ], - "version": "==4.0.1" + "version": "==5.0.2" }, "pyflakes": { "hashes": [ @@ -467,26 +491,26 @@ }, "pyyaml": { "hashes": [ - "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", - "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", - "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", - "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", - "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", - "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", - "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", - "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", - "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", - "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", - "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" + "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", + "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", + "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", + "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", + "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", + "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", + "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", + "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", + "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", + "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", + "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" ], - "version": "==5.2" + "version": "==5.3" }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "snowballstemmer": { "hashes": [ @@ -504,42 +528,44 @@ }, "typed-ast": { "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" - ], - "version": "==1.4.0" + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "markers": "python_version < '3.8'", + "version": "==1.4.1" }, "virtualenv": { "hashes": [ - "sha256:116655188441670978117d0ebb6451eb6a7526f9ae0796cc0dee6bd7356909b0", - "sha256:b57776b44f91511866594e477dd10e76a6eb44439cdd7f06dcd30ba4c5bd854f" + "sha256:0c04c7e8e0314470b4c2b43740ff68be1c62bb3fdef8309341ff1daea60d49d1", + "sha256:1f0369d068d9761b5c1ed7b44dad1ec124727eb10bc7f4aaefbba0cdca3bd924" ], - "version": "==16.7.8" + "version": "==20.0.8" }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==0.6.0" + "version": "==3.1.0" } } } diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py index 1ed38f86..09f5e250 100644 --- a/bot/seasons/evergreen/snakes/snakes_cog.py +++ b/bot/seasons/evergreen/snakes/snakes_cog.py @@ -617,8 +617,8 @@ class Snakes(Cog): text_color=text_color, bg_color=bg_color ) - png_bytesIO = utils.frame_to_png_bytes(image_frame) - file = File(png_bytesIO, filename='snek.png') + png_bytes = utils.frame_to_png_bytes(image_frame) + file = File(png_bytes, filename='snek.png') await ctx.send(file=file) @snakes_group.command(name='get') diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index b7b4122d..fbadb7cc 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -121,8 +121,8 @@ class HacktoberStats(commands.Cog): """ if self.link_json.exists(): logging.info(f"Loading linked GitHub accounts from '{self.link_json}'") - with open(self.link_json, 'r') as fID: - linked_accounts = json.load(fID) + with open(self.link_json, 'r') as file: + linked_accounts = json.load(file) logging.info(f"Loaded {len(linked_accounts)} linked GitHub accounts from '{self.link_json}'") return linked_accounts @@ -143,8 +143,8 @@ class HacktoberStats(commands.Cog): } """ logging.info(f"Saving linked_accounts to '{self.link_json}'") - with open(self.link_json, 'w') as fID: - json.dump(self.linked_accounts, fID, default=str) + with open(self.link_json, 'w') as file: + json.dump(self.linked_accounts, file, default=str) logging.info(f"linked_accounts saved to '{self.link_json}'") async def get_stats(self, ctx: commands.Context, github_username: str) -> None: @@ -309,11 +309,11 @@ class HacktoberStats(commands.Cog): n contribution(s) to [shortname](url) ... """ - baseURL = "https://www.github.com/" + base_url = "https://www.github.com/" contributionstrs = [] for repo in stats['top5']: n = repo[1] - contributionstrs.append(f"{n} {HacktoberStats._contributionator(n)} to [{repo[0]}]({baseURL}{repo[0]})") + contributionstrs.append(f"{n} {HacktoberStats._contributionator(n)} to [{repo[0]}]({base_url}{repo[0]})") return "\n".join(contributionstrs) diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index de97cb4e..ab8ea290 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -202,9 +202,9 @@ class BeMyValentine(commands.Cog): @staticmethod def random_emoji() -> Tuple[str, str]: """Return two random emoji from the module-defined constants.""" - EMOJI_1 = random.choice(HEART_EMOJIS) - EMOJI_2 = random.choice(HEART_EMOJIS) - return EMOJI_1, EMOJI_2 + emoji_1 = random.choice(HEART_EMOJIS) + emoji_2 = random.choice(HEART_EMOJIS) + return emoji_1, emoji_2 def random_valentine(self) -> Tuple[str, str]: """Grabs a random poem or a compliment (any message).""" diff --git a/tox.ini b/tox.ini index ee898b0d..af87e6fc 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ ignore= # Docstring Content D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417 # Type Annotations - TYP002,TYP003,TYP101,TYP102,TYP204,TYP206 + ANN002,ANN003,ANN101,ANN102,ANN204,ANN206 exclude= __pycache__,.cache, venv,.venv, -- cgit v1.2.3 From a6237bb9dfd7e9c28421f0b37d92796454b9c122 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 4 Mar 2020 18:16:04 -0800 Subject: Add more pre-commit hooks Hooks added: * check-merge-conflict - checks for files with merge conflict strings * check-toml - attempts to load all toml files to verify syntax * check-yaml - attempts to load all yaml files to verify syntax * end-of-file-fixer - ensures files end in a newline and only a newline * mixed-line-ending - replaces mixed line endings with LF * trailing-whitespace - trims trailing whitespace * python-check-blanket-noqa - enforces that noqa annotations always occur with specific codes Changes made to comply with new hooks: * Remove trailing whitespaces * Convert some CRLF files to LF * Remove noqa and add missing type annotations * Add missing newlines at end of files See: python-discord/organisation#138 --- .gitignore | 2 +- .pre-commit-config.yaml | 23 +++- CONTRIBUTING.md | 4 +- Pipfile | 2 +- README.md | 2 +- bot/resources/advent_of_code/about.json | 2 +- bot/resources/easter/april_fools_vids.json | 2 +- .../evergreen/game_recs/chrono_trigger.json | 2 +- .../evergreen/game_recs/digimon_world.json | 2 +- bot/resources/evergreen/game_recs/doom_2.json | 2 +- bot/resources/evergreen/game_recs/skyrim.json | 2 +- bot/resources/evergreen/magic8ball.json | 2 +- bot/resources/evergreen/trivia_quiz.json | 2 +- bot/resources/halloween/candy_collection.json | 2 +- bot/resources/halloween/halloweenify.json | 2 +- bot/resources/halloween/spooky_rating.json | 2 +- bot/resources/pride/anthems.json | 2 +- bot/resources/pride/drag_queen_names.json | 2 +- bot/resources/pride/facts.json | 2 +- bot/resources/snakes/snake_idioms.json | 2 +- bot/resources/snakes/snake_names.json | 2 +- bot/resources/snakes/special_snakes.json | 2 +- bot/resources/valentines/date_ideas.json | 2 - bot/resources/valentines/love_matches.json | 2 +- bot/resources/valentines/pickup_lines.json | 2 +- bot/resources/valentines/valenstates.json | 2 +- bot/resources/valentines/valentine_facts.json | 2 +- bot/resources/valentines/zodiac_compatibility.json | 2 +- bot/seasons/christmas/adventofcode.py | 2 +- bot/seasons/evergreen/bookmark.py | 130 ++++++++++----------- bot/seasons/halloween/hacktoberstats.py | 2 +- 31 files changed, 113 insertions(+), 100 deletions(-) (limited to 'bot') diff --git a/.gitignore b/.gitignore index 8a21b668..d95585b7 100644 --- a/.gitignore +++ b/.gitignore @@ -113,5 +113,5 @@ venv.bak/ .idea/ .DS_Store -# vscode +# vscode .vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 86035786..be57904e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,25 @@ repos: -- repo: local + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 hooks: - - id: flake8 + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.5.1 + hooks: + - id: python-check-blanket-noqa + - repo: local + hooks: + - id: flake8 name: Flake8 description: This hook runs flake8 within our project's pipenv environment. - entry: pipenv run lint + entry: pipenv run flake8 language: python types: [python] - require_serial: true \ No newline at end of file + require_serial: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd0c048f..ced5df70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ Seasonalbot utilizes [Pipenv](https://pipenv.readthedocs.io/en/latest/) for inst When pulling down changes from GitHub, remember to sync your environment using `pipenv sync --dev` to ensure you're using the most up-to-date versions the project's dependencies. ### Type Hinting -[PEP 484](https://www.python.org/dev/peps/pep-0484/) formally specifies type hints for Python functions, added to the Python Standard Library in version 3.5. Type hints are recognized by most modern code editing tools and provide useful insight into both the input and output types of a function, preventing the user from having to go through the codebase to determine these types. +[PEP 484](https://www.python.org/dev/peps/pep-0484/) formally specifies type hints for Python functions, added to the Python Standard Library in version 3.5. Type hints are recognized by most modern code editing tools and provide useful insight into both the input and output types of a function, preventing the user from having to go through the codebase to determine these types. For example: @@ -86,7 +86,7 @@ def foo(bar: int, baz: dict=None) -> bool: Does some things with some stuff. This function takes an index, `bar` and checks for its presence in the database `baz`, passed as a dictionary. - + Returns `False` if `baz` is not passed. """ ``` diff --git a/Pipfile b/Pipfile index be17343f..0c4b2353 100644 --- a/Pipfile +++ b/Pipfile @@ -29,5 +29,5 @@ python_version = "3.7" [scripts] start = "python -m bot" -lint = "flake8 bot" +lint = "pre-commit run --all-files" precommit = "pre-commit install" diff --git a/README.md b/README.md index f956cbaf..c1952286 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SeasonalBot +# SeasonalBot [![Build Status](https://dev.azure.com/python-discord/Python%20Discord/_apis/build/status/Seasonal%20Bot%20(Mainline))](https://dev.azure.com/python-discord/Python%20Discord/_build/latest?definitionId=3) [![Discord](https://img.shields.io/static/v1?label=Python%20Discord&logo=discord&message=%3E30k%20members&color=%237289DA&logoColor=white)](https://discord.gg/2B963hn) diff --git a/bot/resources/advent_of_code/about.json b/bot/resources/advent_of_code/about.json index b1d16a93..91ae6813 100644 --- a/bot/resources/advent_of_code/about.json +++ b/bot/resources/advent_of_code/about.json @@ -24,4 +24,4 @@ "value": "In addition to the global leaderboard, AoC also offers private leaderboards, where you can compete against a smaller group of friends!\n\nGet the join code using `.aoc join` and head over to AoC's [private leaderboard page](https://adventofcode.com/leaderboard/private) to join the PyDis private leaderboard!", "inline": false } -] \ No newline at end of file +] diff --git a/bot/resources/easter/april_fools_vids.json b/bot/resources/easter/april_fools_vids.json index dfc01b7b..5f0ba06e 100644 --- a/bot/resources/easter/april_fools_vids.json +++ b/bot/resources/easter/april_fools_vids.json @@ -122,4 +122,4 @@ } ] -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/game_recs/chrono_trigger.json b/bot/resources/evergreen/game_recs/chrono_trigger.json index 219c1e78..9720b977 100644 --- a/bot/resources/evergreen/game_recs/chrono_trigger.json +++ b/bot/resources/evergreen/game_recs/chrono_trigger.json @@ -4,4 +4,4 @@ "link": "https://rawg.io/games/chrono-trigger-1995", "image": "https://vignette.wikia.nocookie.net/chrono/images/2/24/Chrono_Trigger_cover.jpg", "author": "352635617709916161" -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/game_recs/digimon_world.json b/bot/resources/evergreen/game_recs/digimon_world.json index a2820f8e..c1cb4f37 100644 --- a/bot/resources/evergreen/game_recs/digimon_world.json +++ b/bot/resources/evergreen/game_recs/digimon_world.json @@ -4,4 +4,4 @@ "image": "https://www.mobygames.com/images/covers/l/437308-digimon-world-playstation-front-cover.jpg", "link": "https://rawg.io/games/digimon-world", "author": "352635617709916161" -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/game_recs/doom_2.json b/bot/resources/evergreen/game_recs/doom_2.json index e228e2b1..b60cc05f 100644 --- a/bot/resources/evergreen/game_recs/doom_2.json +++ b/bot/resources/evergreen/game_recs/doom_2.json @@ -4,4 +4,4 @@ "image": "https://upload.wikimedia.org/wikipedia/en/thumb/2/29/Doom_II_-_Hell_on_Earth_Coverart.png/220px-Doom_II_-_Hell_on_Earth_Coverart.png", "link": "https://rawg.io/games/doom-ii", "author": "352635617709916161" -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/game_recs/skyrim.json b/bot/resources/evergreen/game_recs/skyrim.json index 09f93563..ad86db31 100644 --- a/bot/resources/evergreen/game_recs/skyrim.json +++ b/bot/resources/evergreen/game_recs/skyrim.json @@ -4,4 +4,4 @@ "image": "https://upload.wikimedia.org/wikipedia/en/1/15/The_Elder_Scrolls_V_Skyrim_cover.png", "link": "https://rawg.io/games/the-elder-scrolls-v-skyrim", "author": "352635617709916161" -} \ No newline at end of file +} diff --git a/bot/resources/evergreen/magic8ball.json b/bot/resources/evergreen/magic8ball.json index 6fe86950..f5f1df62 100644 --- a/bot/resources/evergreen/magic8ball.json +++ b/bot/resources/evergreen/magic8ball.json @@ -19,4 +19,4 @@ "My sources say no", "Outlook not so good", "Very doubtful" -] \ No newline at end of file +] diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json index 48ee2ce4..6100ca62 100644 --- a/bot/resources/evergreen/trivia_quiz.json +++ b/bot/resources/evergreen/trivia_quiz.json @@ -29,7 +29,7 @@ "hints": ["The game was released in 1990.", "It was released on the SNES."], "question": "What was the first game Yoshi appeared in?", "answer": "Super Mario World" - } + } ], "general":[ { diff --git a/bot/resources/halloween/candy_collection.json b/bot/resources/halloween/candy_collection.json index bebcd4fc..9aa78a5f 100644 --- a/bot/resources/halloween/candy_collection.json +++ b/bot/resources/halloween/candy_collection.json @@ -1 +1 @@ -{"msg_reacted": [{"reaction": "\ud83c\udf6c", "msg_id": 514442189359546375, "won": true, "user_reacted": 95872159741644800}, {"reaction": "\ud83c\udf6c", "msg_id": 514442502460276740, "won": true, "user_reacted": 178876748224659457}], "records": [{"userid": 95872159741644800, "record": 1}, {"userid": 178876748224659457, "record": 1}]} \ No newline at end of file +{"msg_reacted": [{"reaction": "\ud83c\udf6c", "msg_id": 514442189359546375, "won": true, "user_reacted": 95872159741644800}, {"reaction": "\ud83c\udf6c", "msg_id": 514442502460276740, "won": true, "user_reacted": 178876748224659457}], "records": [{"userid": 95872159741644800, "record": 1}, {"userid": 178876748224659457, "record": 1}]} diff --git a/bot/resources/halloween/halloweenify.json b/bot/resources/halloween/halloweenify.json index 88c46bfc..af9204b2 100644 --- a/bot/resources/halloween/halloweenify.json +++ b/bot/resources/halloween/halloweenify.json @@ -79,4 +79,4 @@ "Pale Man": "https://i2.wp.com/macguff.in/wp-content/uploads/2016/10/Pans-Labyrinth-Movie-Header-Image.jpg?fit=630%2C400&ssl=1" } ] -} \ No newline at end of file +} diff --git a/bot/resources/halloween/spooky_rating.json b/bot/resources/halloween/spooky_rating.json index 1815befc..d9c8dcb7 100644 --- a/bot/resources/halloween/spooky_rating.json +++ b/bot/resources/halloween/spooky_rating.json @@ -44,4 +44,4 @@ "text": "You are the evillest creature in existence to scare anyone and everyone humanly possible. The reason your underlings are called mortals is that they die. With your help, they die a lot quicker. With all the evil power in the universe, you know what to do.", "image": "https://raw.githubusercontent.com/python-discord/seasonalbot/master/bot/resources/halloween/spookyrating/devil.jpeg" } -} \ No newline at end of file +} diff --git a/bot/resources/pride/anthems.json b/bot/resources/pride/anthems.json index d80d7558..fd8e8b92 100644 --- a/bot/resources/pride/anthems.json +++ b/bot/resources/pride/anthems.json @@ -452,4 +452,4 @@ "pop" ] } -] \ No newline at end of file +] diff --git a/bot/resources/pride/drag_queen_names.json b/bot/resources/pride/drag_queen_names.json index f63cdec3..023dff36 100644 --- a/bot/resources/pride/drag_queen_names.json +++ b/bot/resources/pride/drag_queen_names.json @@ -246,4 +246,4 @@ "Vivian Foxx", "Vye Vacius", "Zahara Dessert" -] \ No newline at end of file +] diff --git a/bot/resources/pride/facts.json b/bot/resources/pride/facts.json index f6597201..2151f5ca 100644 --- a/bot/resources/pride/facts.json +++ b/bot/resources/pride/facts.json @@ -31,4 +31,4 @@ "As of 2019-10-02, there are 17 states in the United States of America where queer people can be fired for being queer. In most other states, there is minimal protection offered, often only for public employees.", "In 1985, an official Star Trek novel was published with scenes depicting Kirk and Spock as lovers. These parts were largely removed, which made the original into a collector's item." ] -} \ No newline at end of file +} diff --git a/bot/resources/snakes/snake_idioms.json b/bot/resources/snakes/snake_idioms.json index 37148c42..ecbeb6ff 100644 --- a/bot/resources/snakes/snake_idioms.json +++ b/bot/resources/snakes/snake_idioms.json @@ -272,4 +272,4 @@ { "idiom": "photosnek" } -] \ No newline at end of file +] diff --git a/bot/resources/snakes/snake_names.json b/bot/resources/snakes/snake_names.json index 8ba9dbd7..25832550 100644 --- a/bot/resources/snakes/snake_names.json +++ b/bot/resources/snakes/snake_names.json @@ -2167,4 +2167,4 @@ "name": "Titanboa", "scientific": "Titanoboa" } -] \ No newline at end of file +] diff --git a/bot/resources/snakes/special_snakes.json b/bot/resources/snakes/special_snakes.json index 8159f914..46214f66 100644 --- a/bot/resources/snakes/special_snakes.json +++ b/bot/resources/snakes/special_snakes.json @@ -13,4 +13,4 @@ "https://img.thrfun.com/img/080/349/spaghetti_dinner_l1.jpg" ] } -] \ No newline at end of file +] diff --git a/bot/resources/valentines/date_ideas.json b/bot/resources/valentines/date_ideas.json index 09d31067..995f14bb 100644 --- a/bot/resources/valentines/date_ideas.json +++ b/bot/resources/valentines/date_ideas.json @@ -123,5 +123,3 @@ } ] } - - diff --git a/bot/resources/valentines/love_matches.json b/bot/resources/valentines/love_matches.json index 8d50cd79..7df2dbda 100644 --- a/bot/resources/valentines/love_matches.json +++ b/bot/resources/valentines/love_matches.json @@ -55,4 +55,4 @@ ], "text": "You two will most likely have the perfect relationship. But don't think that this means you don't have to do anything for it to work. Talking to each other and spending time together is key, even in a seemingly perfect relationship." } -} \ No newline at end of file +} diff --git a/bot/resources/valentines/pickup_lines.json b/bot/resources/valentines/pickup_lines.json index a18d0840..eb01290f 100644 --- a/bot/resources/valentines/pickup_lines.json +++ b/bot/resources/valentines/pickup_lines.json @@ -94,4 +94,4 @@ "image": "https://upload.wikimedia.org/wikipedia/en/thumb/0/0c/The_Genie_Aladdin.png/220px-The_Genie_Aladdin.png" } ] -} \ No newline at end of file +} diff --git a/bot/resources/valentines/valenstates.json b/bot/resources/valentines/valenstates.json index 06cbb2e5..c58a5b7c 100644 --- a/bot/resources/valentines/valenstates.json +++ b/bot/resources/valentines/valenstates.json @@ -119,4 +119,4 @@ "text": "", "flag": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Flag_of_Wales_%281959%E2%80%93present%29.svg/1920px-Flag_of_Wales_%281959%E2%80%93present%29.svg.png" } -} \ No newline at end of file +} diff --git a/bot/resources/valentines/valentine_facts.json b/bot/resources/valentines/valentine_facts.json index d2ffa980..e6f826c3 100644 --- a/bot/resources/valentines/valentine_facts.json +++ b/bot/resources/valentines/valentine_facts.json @@ -21,4 +21,4 @@ "There's a form of cryptological communication called 'Floriography', in which you communicate through flowers. Meaning has been attributed to flowers for thousands of years, and some form of floriography has been practiced in traditional cultures throughout Europe, Asia, and Africa. Here are some meanings for roses you might want to take a look at, if you plan on gifting your loved one a bouquet of roses on Valentine's Day:\n\u200b\nRed: eternal love\nPink: young, developing love\nWhite: innocence, fervor, loyalty\nOrange: happiness, security\nViolet: love at first sight\nBlue: unfulfilled longing, quiet desire\nYellow: friendship, jealousy, envy, infidelity\nBlack: unfulfilled longing, quiet desire, grief, hatred, misfortune, death", "Traditionally, young girls in the U.S. and the U.K. believed they could tell what type of man they would marry depending on the type of bird they saw first on Valentine's Day. If they saw a blackbird, they would marry a clergyman, a robin redbreast indicated a sailor, and a goldfinch indicated a rich man. A sparrow meant they would marry a farmer, a bluebird indicated a happy man, and a crossbill meant an argumentative man. If they saw a dove, they would marry a good man, but seeing a woodpecker meant they would not marry at all." ] -} \ No newline at end of file +} diff --git a/bot/resources/valentines/zodiac_compatibility.json b/bot/resources/valentines/zodiac_compatibility.json index 4e337714..3971d40d 100644 --- a/bot/resources/valentines/zodiac_compatibility.json +++ b/bot/resources/valentines/zodiac_compatibility.json @@ -259,4 +259,4 @@ } ] -} \ No newline at end of file +} diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index f2ec83df..8caf43bd 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -364,7 +364,7 @@ class AdventOfCode(commands.Cog): aoc_embed.set_footer(text="Last Updated") await ctx.send( - content=f"Here's the current global Top {number_of_people_to_display}! {Emojis.christmas_tree*3}\n\n{table}", # noqa + f"Here's the current global Top {number_of_people_to_display}! {Emojis.christmas_tree*3}\n\n{table}", embed=aoc_embed, ) diff --git a/bot/seasons/evergreen/bookmark.py b/bot/seasons/evergreen/bookmark.py index 7bdd362c..bd7d5c11 100644 --- a/bot/seasons/evergreen/bookmark.py +++ b/bot/seasons/evergreen/bookmark.py @@ -1,65 +1,65 @@ -import logging -import random - -import discord -from discord.ext import commands - -from bot.constants import Colours, ERROR_REPLIES, Emojis, bookmark_icon_url - -log = logging.getLogger(__name__) - - -class Bookmark(commands.Cog): - """Creates personal bookmarks by relaying a message link to the user's DMs.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.command(name="bookmark", aliases=("bm", "pin")) - async def bookmark( - self, - ctx: commands.Context, - target_message: discord.Message, - *, - title: str = "Bookmark" - ) -> None: - """Send the author a link to `target_message` via DMs.""" - # Prevent users from bookmarking a message in a channel they don't have access to - permissions = ctx.author.permissions_in(target_message.channel) - if not permissions.read_messages: - log.info(f"{ctx.author} tried to bookmark a message in #{target_message.channel} but has no permissions") - embed = discord.Embed( - title=random.choice(ERROR_REPLIES), - color=Colours.soft_red, - description="You don't have permission to view this channel." - ) - await ctx.send(embed=embed) - return - - embed = discord.Embed( - title=title, - colour=Colours.soft_green, - description=target_message.content - ) - embed.add_field(name="Wanna give it a visit?", value=f"[Visit original message]({target_message.jump_url})") - embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url) - embed.set_thumbnail(url=bookmark_icon_url) - - try: - await ctx.author.send(embed=embed) - except discord.Forbidden: - error_embed = discord.Embed( - title=random.choice(ERROR_REPLIES), - description=f"{ctx.author.mention}, please enable your DMs to receive the bookmark", - colour=Colours.soft_red - ) - await ctx.send(embed=error_embed) - else: - log.info(f"{ctx.author} bookmarked {target_message.jump_url} with title '{title}'") - await ctx.message.add_reaction(Emojis.envelope) - - -def setup(bot: commands.Bot) -> None: - """Load the Bookmark cog.""" - bot.add_cog(Bookmark(bot)) - log.info("Bookmark cog loaded") +import logging +import random + +import discord +from discord.ext import commands + +from bot.constants import Colours, ERROR_REPLIES, Emojis, bookmark_icon_url + +log = logging.getLogger(__name__) + + +class Bookmark(commands.Cog): + """Creates personal bookmarks by relaying a message link to the user's DMs.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command(name="bookmark", aliases=("bm", "pin")) + async def bookmark( + self, + ctx: commands.Context, + target_message: discord.Message, + *, + title: str = "Bookmark" + ) -> None: + """Send the author a link to `target_message` via DMs.""" + # Prevent users from bookmarking a message in a channel they don't have access to + permissions = ctx.author.permissions_in(target_message.channel) + if not permissions.read_messages: + log.info(f"{ctx.author} tried to bookmark a message in #{target_message.channel} but has no permissions") + embed = discord.Embed( + title=random.choice(ERROR_REPLIES), + color=Colours.soft_red, + description="You don't have permission to view this channel." + ) + await ctx.send(embed=embed) + return + + embed = discord.Embed( + title=title, + colour=Colours.soft_green, + description=target_message.content + ) + embed.add_field(name="Wanna give it a visit?", value=f"[Visit original message]({target_message.jump_url})") + embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url) + embed.set_thumbnail(url=bookmark_icon_url) + + try: + await ctx.author.send(embed=embed) + except discord.Forbidden: + error_embed = discord.Embed( + title=random.choice(ERROR_REPLIES), + description=f"{ctx.author.mention}, please enable your DMs to receive the bookmark", + colour=Colours.soft_red + ) + await ctx.send(embed=error_embed) + else: + log.info(f"{ctx.author} bookmarked {target_message.jump_url} with title '{title}'") + await ctx.message.add_reaction(Emojis.envelope) + + +def setup(bot: commands.Bot) -> None: + """Load the Bookmark cog.""" + bot.add_cog(Bookmark(bot)) + log.info("Bookmark cog loaded") diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index fbadb7cc..d61e048b 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -334,7 +334,7 @@ class HacktoberStats(commands.Cog): return author_id, author_mention -def setup(bot): # Noqa +def setup(bot: commands.Bot) -> None: """Hacktoberstats Cog load.""" bot.add_cog(HacktoberStats(bot)) log.info("HacktoberStats cog loaded") -- cgit v1.2.3 From 6ec6c81233f3942c9ba6e8f09ec175535c270afa Mon Sep 17 00:00:00 2001 From: ks123 Date: Sat, 7 Mar 2020 09:50:18 +0200 Subject: Copy-pasted help command content from Python bot --- bot/help.py | 554 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 bot/help.py (limited to 'bot') diff --git a/bot/help.py b/bot/help.py new file mode 100644 index 00000000..7e2064c8 --- /dev/null +++ b/bot/help.py @@ -0,0 +1,554 @@ +import asyncio +import itertools +from collections import namedtuple +from contextlib import suppress +from typing import Union + +from discord import Colour, Embed, HTTPException, Message, Reaction, User +from discord.ext import commands +from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context +from fuzzywuzzy import fuzz, process + +from bot import constants +from bot.bot import Bot +from bot.constants import Channels, Emojis, STAFF_ROLES +from bot.decorators import redirect_output +from bot.pagination import ( + FIRST_EMOJI, LAST_EMOJI, + LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, +) + +DELETE_EMOJI = Emojis.trashcan + +REACTIONS = { + FIRST_EMOJI: 'first', + LEFT_EMOJI: 'back', + RIGHT_EMOJI: 'next', + LAST_EMOJI: 'end', + DELETE_EMOJI: 'stop', +} + +Cog = namedtuple('Cog', ['name', 'description', 'commands']) + + +class HelpQueryNotFound(ValueError): + """ + Raised when a HelpSession Query doesn't match a command or cog. + Contains the custom attribute of ``possible_matches``. + Instances of this object contain a dictionary of any command(s) that were close to matching the + query, where keys are the possible matched command names and values are the likeness match scores. + """ + + def __init__(self, arg: str, possible_matches: dict = None): + super().__init__(arg) + self.possible_matches = possible_matches + + +class HelpSession: + """ + An interactive session for bot and command help output. + Expected attributes include: + * title: str + The title of the help message. + * query: Union[discord.ext.commands.Bot, discord.ext.commands.Command] + * description: str + The description of the query. + * pages: list[str] + A list of the help content split into manageable pages. + * message: `discord.Message` + The message object that's showing the help contents. + * destination: `discord.abc.Messageable` + Where the help message is to be sent to. + Cogs can be grouped into custom categories. All cogs with the same category will be displayed + under a single category name in the help output. Custom categories are defined inside the cogs + as a class attribute named `category`. A description can also be specified with the attribute + `category_description`. If a description is not found in at least one cog, the default will be + the regular description (class docstring) of the first cog found in the category. + """ + + def __init__( + self, + ctx: Context, + *command, + cleanup: bool = False, + only_can_run: bool = True, + show_hidden: bool = False, + max_lines: int = 15 + ): + """Creates an instance of the HelpSession class.""" + self._ctx = ctx + self._bot = ctx.bot + self.title = "Command Help" + + # set the query details for the session + if command: + query_str = ' '.join(command) + self.query = self._get_query(query_str) + self.description = self.query.description or self.query.help + else: + self.query = ctx.bot + self.description = self.query.description + self.author = ctx.author + self.destination = ctx.channel + + # set the config for the session + self._cleanup = cleanup + self._only_can_run = only_can_run + self._show_hidden = show_hidden + self._max_lines = max_lines + + # init session states + self._pages = None + self._current_page = 0 + self.message = None + self._timeout_task = None + self.reset_timeout() + + def _get_query(self, query: str) -> Union[Command, Cog]: + """Attempts to match the provided query with a valid command or cog.""" + command = self._bot.get_command(query) + if command: + return command + + # Find all cog categories that match. + cog_matches = [] + description = None + for cog in self._bot.cogs.values(): + if hasattr(cog, "category") and cog.category == query: + cog_matches.append(cog) + if hasattr(cog, "category_description"): + description = cog.category_description + + # Try to search by cog name if no categories match. + if not cog_matches: + cog = self._bot.cogs.get(query) + + # Don't consider it a match if the cog has a category. + if cog and not hasattr(cog, "category"): + cog_matches = [cog] + + if cog_matches: + cog = cog_matches[0] + cmds = (cog.get_commands() for cog in cog_matches) # Commands of all cogs + + return Cog( + name=cog.category if hasattr(cog, "category") else cog.qualified_name, + description=description or cog.description, + commands=tuple(itertools.chain.from_iterable(cmds)) # Flatten the list + ) + + self._handle_not_found(query) + + def _handle_not_found(self, query: str) -> None: + """ + Handles when a query does not match a valid command or cog. + Will pass on possible close matches along with the `HelpQueryNotFound` exception. + """ + # Combine command and cog names + choices = list(self._bot.all_commands) + list(self._bot.cogs) + + result = process.extractBests(query, choices, scorer=fuzz.ratio, score_cutoff=90) + + raise HelpQueryNotFound(f'Query "{query}" not found.', dict(result)) + + async def timeout(self, seconds: int = 30) -> None: + """Waits for a set number of seconds, then stops the help session.""" + await asyncio.sleep(seconds) + await self.stop() + + def reset_timeout(self) -> None: + """Cancels the original timeout task and sets it again from the start.""" + # cancel original if it exists + if self._timeout_task: + if not self._timeout_task.cancelled(): + self._timeout_task.cancel() + + # recreate the timeout task + self._timeout_task = self._bot.loop.create_task(self.timeout()) + + async def on_reaction_add(self, reaction: Reaction, user: User) -> None: + """Event handler for when reactions are added on the help message.""" + # ensure it was the relevant session message + if reaction.message.id != self.message.id: + return + + # ensure it was the session author who reacted + if user.id != self.author.id: + return + + emoji = str(reaction.emoji) + + # check if valid action + if emoji not in REACTIONS: + return + + self.reset_timeout() + + # Run relevant action method + action = getattr(self, f'do_{REACTIONS[emoji]}', None) + if action: + await action() + + # remove the added reaction to prep for re-use + with suppress(HTTPException): + await self.message.remove_reaction(reaction, user) + + async def on_message_delete(self, message: Message) -> None: + """Closes the help session when the help message is deleted.""" + if message.id == self.message.id: + await self.stop() + + async def prepare(self) -> None: + """Sets up the help session pages, events, message and reactions.""" + # create paginated content + await self.build_pages() + + # setup listeners + self._bot.add_listener(self.on_reaction_add) + self._bot.add_listener(self.on_message_delete) + + # Send the help message + await self.update_page() + self.add_reactions() + + def add_reactions(self) -> None: + """Adds the relevant reactions to the help message based on if pagination is required.""" + # if paginating + if len(self._pages) > 1: + for reaction in REACTIONS: + self._bot.loop.create_task(self.message.add_reaction(reaction)) + + # if single-page + else: + self._bot.loop.create_task(self.message.add_reaction(DELETE_EMOJI)) + + def _category_key(self, cmd: Command) -> str: + """ + Returns a cog name of a given command for use as a key for `sorted` and `groupby`. + A zero width space is used as a prefix for results with no cogs to force them last in ordering. + """ + if cmd.cog: + try: + if cmd.cog.category: + return f'**{cmd.cog.category}**' + except AttributeError: + pass + + return f'**{cmd.cog_name}**' + else: + return "**\u200bNo Category:**" + + def _get_command_params(self, cmd: Command) -> str: + """ + Returns the command usage signature. + This is a custom implementation of `command.signature` in order to format the command + signature without aliases. + """ + results = [] + for name, param in cmd.clean_params.items(): + + # if argument has a default value + if param.default is not param.empty: + + if isinstance(param.default, str): + show_default = param.default + else: + show_default = param.default is not None + + # if default is not an empty string or None + if show_default: + results.append(f'[{name}={param.default}]') + else: + results.append(f'[{name}]') + + # if variable length argument + elif param.kind == param.VAR_POSITIONAL: + results.append(f'[{name}...]') + + # if required + else: + results.append(f'<{name}>') + + return f"{cmd.name} {' '.join(results)}" + + async def build_pages(self) -> None: + """Builds the list of content pages to be paginated through in the help message, as a list of str.""" + # Use LinePaginator to restrict embed line height + paginator = LinePaginator(prefix='', suffix='', max_lines=self._max_lines) + + prefix = constants.Bot.prefix + + # show signature if query is a command + if isinstance(self.query, commands.Command): + signature = self._get_command_params(self.query) + parent = self.query.full_parent_name + ' ' if self.query.parent else '' + paginator.add_line(f'**```{prefix}{parent}{signature}```**') + + # show command aliases + aliases = ', '.join(f'`{a}`' for a in self.query.aliases) + if aliases: + paginator.add_line(f'**Can also use:** {aliases}\n') + + if not await self.query.can_run(self._ctx): + paginator.add_line('***You cannot run this command.***\n') + + # show name if query is a cog + if isinstance(self.query, Cog): + paginator.add_line(f'**{self.query.name}**') + + if self.description: + paginator.add_line(f'*{self.description}*') + + # list all children commands of the queried object + if isinstance(self.query, (commands.GroupMixin, Cog)): + + # remove hidden commands if session is not wanting hiddens + if not self._show_hidden: + filtered = [c for c in self.query.commands if not c.hidden] + else: + filtered = self.query.commands + + # if after filter there are no commands, finish up + if not filtered: + self._pages = paginator.pages + return + + # set category to Commands if cog + if isinstance(self.query, Cog): + grouped = (('**Commands:**', self.query.commands),) + + # set category to Subcommands if command + elif isinstance(self.query, commands.Command): + grouped = (('**Subcommands:**', self.query.commands),) + + # don't show prefix for subcommands + prefix = '' + + # otherwise sort and organise all commands into categories + else: + cat_sort = sorted(filtered, key=self._category_key) + grouped = itertools.groupby(cat_sort, key=self._category_key) + + # process each category + for category, cmds in grouped: + cmds = sorted(cmds, key=lambda c: c.name) + + # if there are no commands, skip category + if len(cmds) == 0: + continue + + cat_cmds = [] + + # format details for each child command + for command in cmds: + + # skip if hidden and hide if session is set to + if command.hidden and not self._show_hidden: + continue + + # see if the user can run the command + strikeout = '' + + # Patch to make the !help command work outside of #bot-commands again + # This probably needs a proper rewrite, but this will make it work in + # the mean time. + try: + can_run = await command.can_run(self._ctx) + except CheckFailure: + can_run = False + + if not can_run: + # skip if we don't show commands they can't run + if self._only_can_run: + continue + strikeout = '~~' + + signature = self._get_command_params(command) + info = f"{strikeout}**`{prefix}{signature}`**{strikeout}" + + # handle if the command has no docstring + if command.short_doc: + cat_cmds.append(f'{info}\n*{command.short_doc}*') + else: + cat_cmds.append(f'{info}\n*No details provided.*') + + # state var for if the category should be added next + print_cat = 1 + new_page = True + + for details in cat_cmds: + + # keep details together, paginating early if it won't fit + lines_adding = len(details.split('\n')) + print_cat + if paginator._linecount + lines_adding > self._max_lines: + paginator._linecount = 0 + new_page = True + paginator.close_page() + + # new page so print category title again + print_cat = 1 + + if print_cat: + if new_page: + paginator.add_line('') + paginator.add_line(category) + print_cat = 0 + + paginator.add_line(details) + + # save organised pages to session + self._pages = paginator.pages + + def embed_page(self, page_number: int = 0) -> Embed: + """Returns an Embed with the requested page formatted within.""" + embed = Embed() + + # if command or cog, add query to title for pages other than first + if isinstance(self.query, (commands.Command, Cog)) and page_number > 0: + title = f'Command Help | "{self.query.name}"' + else: + title = self.title + + embed.set_author(name=title, icon_url=constants.Icons.questionmark) + embed.description = self._pages[page_number] + + # add page counter to footer if paginating + page_count = len(self._pages) + if page_count > 1: + embed.set_footer(text=f'Page {self._current_page+1} / {page_count}') + + return embed + + async def update_page(self, page_number: int = 0) -> None: + """Sends the intial message, or changes the existing one to the given page number.""" + self._current_page = page_number + embed_page = self.embed_page(page_number) + + if not self.message: + self.message = await self.destination.send(embed=embed_page) + else: + await self.message.edit(embed=embed_page) + + @classmethod + async def start(cls, ctx: Context, *command, **options) -> "HelpSession": + """ + Create and begin a help session based on the given command context. + Available options kwargs: + * cleanup: Optional[bool] + Set to `True` to have the message deleted on session end. Defaults to `False`. + * only_can_run: Optional[bool] + Set to `True` to hide commands the user can't run. Defaults to `False`. + * show_hidden: Optional[bool] + Set to `True` to include hidden commands. Defaults to `False`. + * max_lines: Optional[int] + Sets the max number of lines the paginator will add to a single page. Defaults to 20. + """ + session = cls(ctx, *command, **options) + await session.prepare() + + return session + + async def stop(self) -> None: + """Stops the help session, removes event listeners and attempts to delete the help message.""" + self._bot.remove_listener(self.on_reaction_add) + self._bot.remove_listener(self.on_message_delete) + + # ignore if permission issue, or the message doesn't exist + with suppress(HTTPException, AttributeError): + if self._cleanup: + await self.message.delete() + else: + await self.message.clear_reactions() + + @property + def is_first_page(self) -> bool: + """Check if session is currently showing the first page.""" + return self._current_page == 0 + + @property + def is_last_page(self) -> bool: + """Check if the session is currently showing the last page.""" + return self._current_page == (len(self._pages)-1) + + async def do_first(self) -> None: + """Event that is called when the user requests the first page.""" + if not self.is_first_page: + await self.update_page(0) + + async def do_back(self) -> None: + """Event that is called when the user requests the previous page.""" + if not self.is_first_page: + await self.update_page(self._current_page-1) + + async def do_next(self) -> None: + """Event that is called when the user requests the next page.""" + if not self.is_last_page: + await self.update_page(self._current_page+1) + + async def do_end(self) -> None: + """Event that is called when the user requests the last page.""" + if not self.is_last_page: + await self.update_page(len(self._pages)-1) + + async def do_stop(self) -> None: + """Event that is called when the user requests to stop the help session.""" + await self.message.delete() + + +class Help(DiscordCog): + """Custom Embed Pagination Help feature.""" + + @commands.command('help') + @redirect_output(destination_channel=Channels.bot_commands, bypass_roles=STAFF_ROLES) + async def new_help(self, ctx: Context, *commands) -> None: + """Shows Command Help.""" + try: + await HelpSession.start(ctx, *commands) + except HelpQueryNotFound as error: + embed = Embed() + embed.colour = Colour.red() + embed.title = str(error) + + if error.possible_matches: + matches = '\n'.join(error.possible_matches.keys()) + embed.description = f'**Did you mean:**\n`{matches}`' + + await ctx.send(embed=embed) + + +def unload(bot: Bot) -> None: + """ + Reinstates the original help command. + This is run if the cog raises an exception on load, or if the extension is unloaded. + """ + bot.remove_command('help') + bot.add_command(bot._old_help) + + +def setup(bot: Bot) -> None: + """ + The setup for the help extension. + This is called automatically on `bot.load_extension` being run. + Stores the original help command instance on the `bot._old_help` attribute for later + reinstatement, before removing it from the command registry so the new help command can be + loaded successfully. + If an exception is raised during the loading of the cog, `unload` will be called in order to + reinstate the original help command. + """ + bot._old_help = bot.get_command('help') + bot.remove_command('help') + + try: + bot.add_cog(Help()) + except Exception: + unload(bot) + raise + + +def teardown(bot: Bot) -> None: + """ + The teardown for the help extension. + This is called automatically on `bot.unload_extension` being run. + Calls `unload` in order to reinstate the original help command. + """ + unload(bot) \ No newline at end of file -- cgit v1.2.3 From 9fe768418ea364845e5dfcfe3d5ab9d1661b407d Mon Sep 17 00:00:00 2001 From: ks123 Date: Sat, 7 Mar 2020 09:51:18 +0200 Subject: (Help): Added information comment to top of file --- bot/help.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/help.py b/bot/help.py index 7e2064c8..34eb023e 100644 --- a/bot/help.py +++ b/bot/help.py @@ -1,3 +1,4 @@ +# Help command from Python bot. All commands that will be added to there in futures should be added to here too. import asyncio import itertools from collections import namedtuple -- cgit v1.2.3 From dad85ff79e1716b45d21f514c8347e83f295eaea Mon Sep 17 00:00:00 2001 From: ks123 Date: Sat, 7 Mar 2020 09:59:11 +0200 Subject: (Help): Made modifications to original code to make this working in SeasonalBot, marked them down with comments and fixed docstrings formatting for linting. --- bot/help.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) (limited to 'bot') diff --git a/bot/help.py b/bot/help.py index 34eb023e..3c0a460a 100644 --- a/bot/help.py +++ b/bot/help.py @@ -11,9 +11,11 @@ from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Conte from fuzzywuzzy import fuzz, process from bot import constants -from bot.bot import Bot -from bot.constants import Channels, Emojis, STAFF_ROLES -from bot.decorators import redirect_output +# from bot.bot import Bot (Changed to): +from bot.bot import SeasonalBot +# Original: from bot.constants import Channels, Emojis, STAFF_ROLES +from bot.constants import Emojis +# from bot.decorators import redirect_output (Removed) from bot.pagination import ( FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, @@ -35,6 +37,7 @@ Cog = namedtuple('Cog', ['name', 'description', 'commands']) class HelpQueryNotFound(ValueError): """ Raised when a HelpSession Query doesn't match a command or cog. + Contains the custom attribute of ``possible_matches``. Instances of this object contain a dictionary of any command(s) that were close to matching the query, where keys are the possible matched command names and values are the likeness match scores. @@ -48,6 +51,7 @@ class HelpQueryNotFound(ValueError): class HelpSession: """ An interactive session for bot and command help output. + Expected attributes include: * title: str The title of the help message. @@ -143,6 +147,7 @@ class HelpSession: def _handle_not_found(self, query: str) -> None: """ Handles when a query does not match a valid command or cog. + Will pass on possible close matches along with the `HelpQueryNotFound` exception. """ # Combine command and cog names @@ -226,6 +231,7 @@ class HelpSession: def _category_key(self, cmd: Command) -> str: """ Returns a cog name of a given command for use as a key for `sorted` and `groupby`. + A zero width space is used as a prefix for results with no cogs to force them last in ordering. """ if cmd.cog: @@ -242,6 +248,7 @@ class HelpSession: def _get_command_params(self, cmd: Command) -> str: """ Returns the command usage signature. + This is a custom implementation of `command.signature` in order to format the command signature without aliases. """ @@ -277,7 +284,8 @@ class HelpSession: # Use LinePaginator to restrict embed line height paginator = LinePaginator(prefix='', suffix='', max_lines=self._max_lines) - prefix = constants.Bot.prefix + # Original: prefix = constants.Bot.prefix + prefix = constants.Client.prefix # show signature if query is a command if isinstance(self.query, commands.Command): @@ -410,7 +418,8 @@ class HelpSession: else: title = self.title - embed.set_author(name=title, icon_url=constants.Icons.questionmark) + # Original: embed.set_author(name=title, icon_url=constants.Icons.questionmark) + embed.set_author(name=title) embed.description = self._pages[page_number] # add page counter to footer if paginating @@ -434,6 +443,7 @@ class HelpSession: async def start(cls, ctx: Context, *command, **options) -> "HelpSession": """ Create and begin a help session based on the given command context. + Available options kwargs: * cleanup: Optional[bool] Set to `True` to have the message deleted on session end. Defaults to `False`. @@ -499,8 +509,8 @@ class HelpSession: class Help(DiscordCog): """Custom Embed Pagination Help feature.""" + # @redirect_output(destination_channel=Channels.bot_commands, bypass_roles=STAFF_ROLES) (Removed) @commands.command('help') - @redirect_output(destination_channel=Channels.bot_commands, bypass_roles=STAFF_ROLES) async def new_help(self, ctx: Context, *commands) -> None: """Shows Command Help.""" try: @@ -517,18 +527,22 @@ class Help(DiscordCog): await ctx.send(embed=embed) -def unload(bot: Bot) -> None: +# Original: def unload(bot: Bot) -> None: +def unload(bot: SeasonalBot) -> None: """ Reinstates the original help command. + This is run if the cog raises an exception on load, or if the extension is unloaded. """ bot.remove_command('help') bot.add_command(bot._old_help) -def setup(bot: Bot) -> None: +# Original: def setup(bot: Bot) -> None: +def setup(bot: SeasonalBot) -> None: """ The setup for the help extension. + This is called automatically on `bot.load_extension` being run. Stores the original help command instance on the `bot._old_help` attribute for later reinstatement, before removing it from the command registry so the new help command can be @@ -546,10 +560,12 @@ def setup(bot: Bot) -> None: raise -def teardown(bot: Bot) -> None: +# Original: def teardown(bot: Bot) -> None: +def teardown(bot: SeasonalBot) -> None: """ The teardown for the help extension. + This is called automatically on `bot.unload_extension` being run. Calls `unload` in order to reinstate the original help command. """ - unload(bot) \ No newline at end of file + unload(bot) -- cgit v1.2.3 From 62cf4a69eec0e5702b268611635d574d2693f986 Mon Sep 17 00:00:00 2001 From: ks123 Date: Sat, 7 Mar 2020 10:00:47 +0200 Subject: (Help): Added unloading block in cogs loading --- bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/bot.py b/bot/bot.py index 2a723021..8b389b6a 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -28,7 +28,7 @@ class SeasonalBot(commands.Bot): # Unload all cogs extensions = list(self.extensions.keys()) for extension in extensions: - if extension != "bot.seasons": # We shouldn't unload the manager. + if extension not in ["bot.seasons", "bot.help"]: # We shouldn't unload the manager and help. self.unload_extension(extension) # Load in the list of cogs that was passed in here -- cgit v1.2.3 From 7ce17e3f5af61ef33231eae99c942f060b96278b Mon Sep 17 00:00:00 2001 From: ks123 Date: Sat, 7 Mar 2020 10:01:27 +0200 Subject: (Help): Added cog loading to __main__.py --- bot/__main__.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/__main__.py b/bot/__main__.py index 9dc0b173..a169257f 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -7,5 +7,6 @@ from bot.decorators import in_channel_check log = logging.getLogger(__name__) bot.add_check(in_channel_check(*WHITELISTED_CHANNELS, bypass_roles=STAFF_ROLES)) +bot.load_extension("bot.help") bot.load_extension("bot.seasons") bot.run(Client.token) -- cgit v1.2.3 From 89c5996af883c99255620002cd5160dfb45f4e85 Mon Sep 17 00:00:00 2001 From: ks123 Date: Tue, 10 Mar 2020 19:07:44 +0200 Subject: (Help): Added `Icons` class to constant, added `questionmark` to there, reverted icon removal in `.help` command. --- bot/constants.py | 4 ++++ bot/help.py | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index f0656926..256dc40c 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -104,6 +104,10 @@ class Emojis: merge = "<:PRMerged:629695470570176522>" +class Icons: + questionmark = "https://cdn.discordapp.com/emojis/512367613339369475.png" + + class Lovefest: role_id = int(environ.get("LOVEFEST_ROLE_ID", 542431903886606399)) diff --git a/bot/help.py b/bot/help.py index 3c0a460a..ccb7db7d 100644 --- a/bot/help.py +++ b/bot/help.py @@ -418,8 +418,7 @@ class HelpSession: else: title = self.title - # Original: embed.set_author(name=title, icon_url=constants.Icons.questionmark) - embed.set_author(name=title) + embed.set_author(name=title, icon_url=constants.Icons.questionmark) embed.description = self._pages[page_number] # add page counter to footer if paginating -- cgit v1.2.3 From df45e5c41630c38ebf8b1ae235d45cdecdefa9eb Mon Sep 17 00:00:00 2001 From: ks123 Date: Tue, 10 Mar 2020 19:10:50 +0200 Subject: (Help): Removed unnecessary comments about changes --- bot/help.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'bot') diff --git a/bot/help.py b/bot/help.py index ccb7db7d..cded62c7 100644 --- a/bot/help.py +++ b/bot/help.py @@ -11,11 +11,8 @@ from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Conte from fuzzywuzzy import fuzz, process from bot import constants -# from bot.bot import Bot (Changed to): from bot.bot import SeasonalBot -# Original: from bot.constants import Channels, Emojis, STAFF_ROLES from bot.constants import Emojis -# from bot.decorators import redirect_output (Removed) from bot.pagination import ( FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, @@ -284,7 +281,6 @@ class HelpSession: # Use LinePaginator to restrict embed line height paginator = LinePaginator(prefix='', suffix='', max_lines=self._max_lines) - # Original: prefix = constants.Bot.prefix prefix = constants.Client.prefix # show signature if query is a command @@ -508,7 +504,6 @@ class HelpSession: class Help(DiscordCog): """Custom Embed Pagination Help feature.""" - # @redirect_output(destination_channel=Channels.bot_commands, bypass_roles=STAFF_ROLES) (Removed) @commands.command('help') async def new_help(self, ctx: Context, *commands) -> None: """Shows Command Help.""" @@ -526,7 +521,6 @@ class Help(DiscordCog): await ctx.send(embed=embed) -# Original: def unload(bot: Bot) -> None: def unload(bot: SeasonalBot) -> None: """ Reinstates the original help command. @@ -537,7 +531,6 @@ def unload(bot: SeasonalBot) -> None: bot.add_command(bot._old_help) -# Original: def setup(bot: Bot) -> None: def setup(bot: SeasonalBot) -> None: """ The setup for the help extension. @@ -559,7 +552,6 @@ def setup(bot: SeasonalBot) -> None: raise -# Original: def teardown(bot: Bot) -> None: def teardown(bot: SeasonalBot) -> None: """ The teardown for the help extension. -- cgit v1.2.3 From 5f40f8a6b1f0af707d46f96a8bb1ab78211c8819 Mon Sep 17 00:00:00 2001 From: ks123 Date: Fri, 13 Mar 2020 11:33:06 +0200 Subject: (Help): Comments Cleanup --- bot/help.py | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'bot') diff --git a/bot/help.py b/bot/help.py index cded62c7..7209b71e 100644 --- a/bot/help.py +++ b/bot/help.py @@ -203,14 +203,11 @@ class HelpSession: async def prepare(self) -> None: """Sets up the help session pages, events, message and reactions.""" - # create paginated content await self.build_pages() - # setup listeners self._bot.add_listener(self.on_reaction_add) self._bot.add_listener(self.on_message_delete) - # Send the help message await self.update_page() self.add_reactions() @@ -289,7 +286,6 @@ class HelpSession: parent = self.query.full_parent_name + ' ' if self.query.parent else '' paginator.add_line(f'**```{prefix}{parent}{signature}```**') - # show command aliases aliases = ', '.join(f'`{a}`' for a in self.query.aliases) if aliases: paginator.add_line(f'**Can also use:** {aliases}\n') @@ -297,7 +293,6 @@ class HelpSession: if not await self.query.can_run(self._ctx): paginator.add_line('***You cannot run this command.***\n') - # show name if query is a cog if isinstance(self.query, Cog): paginator.add_line(f'**{self.query.name}**') @@ -318,11 +313,9 @@ class HelpSession: self._pages = paginator.pages return - # set category to Commands if cog if isinstance(self.query, Cog): grouped = (('**Commands:**', self.query.commands),) - # set category to Subcommands if command elif isinstance(self.query, commands.Command): grouped = (('**Subcommands:**', self.query.commands),) @@ -334,17 +327,14 @@ class HelpSession: cat_sort = sorted(filtered, key=self._category_key) grouped = itertools.groupby(cat_sort, key=self._category_key) - # process each category for category, cmds in grouped: cmds = sorted(cmds, key=lambda c: c.name) - # if there are no commands, skip category if len(cmds) == 0: continue cat_cmds = [] - # format details for each child command for command in cmds: # skip if hidden and hide if session is set to @@ -401,14 +391,12 @@ class HelpSession: paginator.add_line(details) - # save organised pages to session self._pages = paginator.pages def embed_page(self, page_number: int = 0) -> Embed: """Returns an Embed with the requested page formatted within.""" embed = Embed() - # if command or cog, add query to title for pages other than first if isinstance(self.query, (commands.Command, Cog)) and page_number > 0: title = f'Command Help | "{self.query.name}"' else: @@ -417,7 +405,6 @@ class HelpSession: embed.set_author(name=title, icon_url=constants.Icons.questionmark) embed.description = self._pages[page_number] - # add page counter to footer if paginating page_count = len(self._pages) if page_count > 1: embed.set_footer(text=f'Page {self._current_page+1} / {page_count}') -- cgit v1.2.3