-
-
Notifications
You must be signed in to change notification settings - Fork 16
Team 21 #12
base: master
Are you sure you want to change the base?
Team 21 #12
Changes from 40 commits
cd2958a
ed78916
0a8ecf2
194b420
b41ebe6
e10dfd9
5f24356
33df4d7
38dadc2
9dd9c2f
8e49271
6be2ba0
a88e00e
83a1019
128efbf
01529d4
280bfa9
316b55c
20c555a
f9cf749
525adc0
f844893
a143ae3
5e7ed88
676f1ec
f2e5d69
92bc40a
9373d79
69ab790
e579d2a
bfdc89c
98b8ba7
f741a2b
a52208a
3e7f499
cc8624a
11be7f1
53973e4
ce51e6c
812e4c2
cf32fdb
7e5a33c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,19 @@ | ||
| # coding=utf-8 | ||
| import asyncio | ||
| import logging | ||
| import random | ||
| import re | ||
| import textwrap | ||
| from typing import Any, Dict | ||
|
|
||
| from discord.ext.commands import AutoShardedBot, Context, command | ||
| import aiohttp | ||
| import async_timeout | ||
| import discord | ||
| from discord.ext.commands import AutoShardedBot, Context, command, bot_has_permissions | ||
|
|
||
| from bot.converters import Snake | ||
| from bot.decorators import locked | ||
| from bot.utils import disambiguate | ||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
@@ -12,10 +23,21 @@ class Snakes: | |
| Snake-related commands | ||
| """ | ||
|
|
||
| # I really hope this works | ||
| wiki_sects = re.compile(r'(?:=+ (.*?) =+)(.*?\n\n)', flags=re.DOTALL) | ||
| wiki_brief = re.compile(r'(.*?)(=+ (.*?) =+)', flags=re.DOTALL) | ||
|
|
||
| valid = ('gif', 'png', 'jpeg', 'jpg', 'webp') | ||
|
|
||
| def __init__(self, bot: AutoShardedBot): | ||
| self.bot = bot | ||
|
|
||
| async def get_snek(self, name: str = None) -> Dict[str, Any]: | ||
| async def fetch(self, session, url): | ||
| async with async_timeout.timeout(10): | ||
| async with session.get(url) as response: | ||
| return await response.json() | ||
|
|
||
| async def get_snek(self, name: str) -> Dict[str, Any]: | ||
| """ | ||
| Go online and fetch information about a snake | ||
|
|
||
|
|
@@ -25,23 +47,183 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: | |
| If "python" is given as the snake name, you should return information about the programming language, but with | ||
| all the information you'd provide for a real snake. Try to have some fun with this! | ||
|
|
||
| :param name: Optional, the name of the snake to get information for - omit for a random snake | ||
| :param name: The name of the snake to get information for - omit for a random snake | ||
| :return: A dict containing information on a snake | ||
| """ | ||
| snake_info = {} | ||
| # python (programming language) pageid = 23862 | ||
| URL = "https://en.wikipedia.org/w/api.php?" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Constants should probably be at module-level? |
||
| ACTION = "action=query" | ||
| LIST = "list=search" | ||
| SRSEARCH = "srsearch=" | ||
| UTF8 = "utf8=" | ||
| SRLIMIT = "srlimit=1" | ||
| FORMAT = "format=json" | ||
| PROP = "prop=extracts|images|info" | ||
| EXLIMIT = "exlimit=max" | ||
| EXPLAINTEXT = "explaintext" | ||
| INPROP = "inprop=url" | ||
|
|
||
| @command() | ||
| async def get(self, ctx: Context, name: str = None): | ||
| """ | ||
| Go online and fetch information about a snake | ||
| PAGE_ID_URL = f"{URL}{FORMAT}&{ACTION}&{LIST}&{SRSEARCH}{name}&{UTF8}&{SRLIMIT}" | ||
|
|
||
| async with aiohttp.ClientSession() as session: | ||
| j = await self.fetch(session, PAGE_ID_URL) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a fan of one-letter variable names. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| # wikipedia does have a error page | ||
| try: | ||
| PAGEID = j["query"]["search"][0]["pageid"] | ||
| except KeyError: | ||
| PAGEID = 41118 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could use a comment, what the hell is it |
||
| PAGEIDS = f"pageids={PAGEID}" | ||
|
|
||
| snake_page = f"{URL}{FORMAT}&{ACTION}&{PROP}&{EXLIMIT}&{EXPLAINTEXT}&{INPROP}&{PAGEIDS}" | ||
|
|
||
| This should make use of your `get_snek` method, using it to get information about a snake. This information | ||
| should be sent back to Discord in an embed. | ||
| j = await self.fetch(session, snake_page) | ||
| # constructing dict - handle exceptions later | ||
| try: | ||
| snake_info["title"] = j["query"]["pages"][f"{PAGEID}"]["title"] | ||
| snake_info["extract"] = j["query"]["pages"][f"{PAGEID}"]["extract"] | ||
| snake_info["images"] = j["query"]["pages"][f"{PAGEID}"]["images"] | ||
| snake_info["fullurl"] = j["query"]["pages"][f"{PAGEID}"]["fullurl"] | ||
| snake_info["pageid"] = j["query"]["pages"][f"{PAGEID}"]["pageid"] | ||
| except KeyError: | ||
| snake_info["error"] = True | ||
| if snake_info["images"]: | ||
| i_url = 'https://commons.wikimedia.org/wiki/Special:FilePath/' | ||
| image_list = [] | ||
| map_list = [] | ||
| thumb_list = [] | ||
| banned = ['Commons-logo.svg', | ||
| 'Red%20Pencil%20Icon.png', | ||
| 'distribution', | ||
| 'The%20Death%20of%20Cleopatra%20arthur.jpg', | ||
| 'Head%20of%20holotype', | ||
| 'locator', | ||
| 'Woma.png', | ||
| '-map.', | ||
| '.svg', | ||
| 'ange.', | ||
| 'Adder%20(PSF).png' | ||
| ] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are hilariously arbitrary. What's up? :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wikipedia has arbitrary images on their snake pages ¯\_(ツ)_/¯ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please be consistent about this style choice. Later on you're using the convention that looks like this: var = (
"contents"
)This is the style used in all of our open source projects, so we would prefer if you used this style here as well. |
||
| for image in snake_info["images"]: | ||
| i = image["title"].split(':')[1].replace(" ", "%20") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I don't love these one-letter variable names There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this line is far too dense, and |
||
| if not i.startswith('Map'): | ||
| for b in banned: | ||
| image_banned = False | ||
| if b in i: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and this line pretty much perfectly illustrates why single letter variables suck. |
||
| image_banned = True | ||
| break | ||
| if image_banned: | ||
| log.info("the image is banned") | ||
| else: | ||
| image_list.append(f"{i_url}{i}") | ||
| thumb_list.append(f"{i_url}{i}?width=100") | ||
| else: | ||
| map_list.append(f"{i_url}{i}") | ||
| snake_info["image_list"] = image_list | ||
| snake_info["map_list"] = map_list | ||
| snake_info["thumb_list"] = thumb_list | ||
| return snake_info | ||
|
|
||
| @command(name="snakes.get()", aliases=["snakes.get"]) | ||
| @bot_has_permissions(manage_messages=True) | ||
| @locked() | ||
| async def get(self, ctx: Context, name: Snake = None): | ||
| """ | ||
| Fetches information about a snake from Wikipedia. | ||
|
|
||
| :param ctx: Context object passed from discord.py | ||
| :param name: Optional, the name of the snake to get information for - omit for a random snake | ||
| """ | ||
| if name is None: | ||
| name = Snake.random() | ||
|
|
||
| data = await self.get_snek(name) | ||
|
|
||
| if data.get('error'): | ||
| return await ctx.send('Could not fetch data from Wikipedia.') | ||
|
|
||
| match = self.wiki_brief.match(data['extract']) | ||
| embed = discord.Embed( | ||
| title=data['title'], | ||
| description=match.group(1) if match else None, | ||
| url=data['fullurl'], | ||
| colour=0x59982F | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What colour is this? Is it not included in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it's just a random green I found online |
||
| ) | ||
|
|
||
| fields = self.wiki_sects.findall(data['extract']) | ||
| excluded = ('see also', 'further reading', 'subspecies') | ||
|
|
||
| for title, body in fields: | ||
| if title.lower() in excluded: | ||
| continue | ||
| if not body.strip(): | ||
| continue | ||
| # Only takes the first sentence | ||
| title, dot, _ = title.partition('.') | ||
| # There's probably a better way to do this | ||
| value = textwrap.shorten(body.strip(), width=200) | ||
| embed.add_field(name=title + dot, value=value + '\n\u200b', inline=False) | ||
|
|
||
| embed.set_footer(text='Powered by Wikipedia') | ||
|
|
||
| emoji = 'https://emojipedia-us.s3.amazonaws.com/thumbs/60/google/3/snake_1f40d.png' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yak! Hard coded value inside the function again. Same issue as the list and dicts. |
||
| image = next((url for url in data['image_list'] if url.endswith(self.valid)), emoji) | ||
| embed.set_thumbnail(url=image) | ||
|
|
||
| await ctx.send(embed=embed) | ||
|
|
||
| @command(hidden=True) | ||
| async def zen(self, ctx): | ||
| """ | ||
| >>> import this | ||
|
|
||
| Long time Pythoneer Tim Peters succinctly channels the BDFL's guiding principles | ||
| for Python's design into 20 aphorisms, only 19 of which have been written down. | ||
|
|
||
| You must be connected to a voice channel in order to use this command. | ||
| """ | ||
| channel = ctx.author.voice.channel | ||
| if channel is None: | ||
| return | ||
|
|
||
| state = ctx.guild.voice_client | ||
| if state is not None: | ||
| # Already playing | ||
| return | ||
|
|
||
| voice = await channel.connect() | ||
| source = discord.FFmpegPCMAudio('zen.mp3') | ||
| voice.play(source, after=lambda *args: asyncio.run_coroutine_threadsafe( | ||
| voice.disconnect(), loop=ctx.bot.loop | ||
| )) | ||
|
|
||
| @command(name="snakes.guess()", aliases=["snakes.guess", "identify"]) | ||
| @locked() | ||
| async def guess(self, ctx): | ||
| """ | ||
| Snake identifying game! | ||
| """ | ||
| image = None | ||
|
|
||
| while image is None: | ||
| snakes = [Snake.random() for _ in range(5)] | ||
| answer = random.choice(snakes) | ||
|
|
||
| data = await self.get_snek(answer) | ||
|
|
||
| image = next((url for url in data['image_list'] if url.endswith(self.valid)), None) | ||
|
|
||
| embed = discord.Embed( | ||
| title='Which of the following is the snake in the image?', | ||
| colour=random.randint(1, 0xFFFFFF) | ||
| ) | ||
| embed.set_image(url=image) | ||
|
|
||
| guess = await disambiguate(ctx, snakes, timeout=60, embed=embed) | ||
|
|
||
| # Any additional commands can be placed here. Be creative, but keep it to a reasonable amount! | ||
| if guess == answer: | ||
| return await ctx.send('You guessed correctly!') | ||
| await ctx.send(f'You guessed wrong. The correct answer was {answer}.') | ||
|
|
||
|
|
||
| def setup(bot): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove what it returns? That's great documenting!