Skip to content
This repository was archived by the owner on Mar 14, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
cd2958a
testfile for testing commiting inside my text editor. can be used for…
eivl Mar 23, 2018
ed78916
Naive input handling
TildeBeta Mar 23, 2018
0a8ecf2
get_snake info from wikipedia, get_snek missing exception handeling
eivl Mar 25, 2018
194b420
Squashed commit of the following:
TildeBeta Mar 25, 2018
b41ebe6
Merge pull request #1 from discord-python/master
eivl Mar 25, 2018
e10dfd9
Quick fixes
TildeBeta Mar 25, 2018
5f24356
Merge branch 'master' of github.com:eivl/code-jam-1
TildeBeta Mar 25, 2018
33df4d7
Fix flake8 import order issue
TildeBeta Mar 25, 2018
38dadc2
Make import PEP8 after previous change
TildeBeta Mar 25, 2018
9dd9c2f
exception handeling, pageid->wiki_error & dict -> add error key
eivl Mar 25, 2018
8e49271
Merge branch 'master' of https://github.com/eivl/code-jam-1
eivl Mar 25, 2018
6be2ba0
changed to bot.http_session
eivl Mar 25, 2018
a88e00e
map_list and image_list -> full url support
eivl Mar 25, 2018
83a1019
Various fixes
TildeBeta Mar 25, 2018
128efbf
edited code to pure json return instead of text
eivl Mar 25, 2018
01529d4
Turns out this broke the custom invoking
TildeBeta Mar 25, 2018
280bfa9
Oops
TildeBeta Mar 25, 2018
316b55c
replaced spacees with %20 in snake image URL
eivl Mar 25, 2018
20c555a
added thumbnail list to snake dict with 100px width
eivl Mar 25, 2018
f9cf749
Embed thumbnail
TildeBeta Mar 25, 2018
525adc0
Quick fixes
TildeBeta Mar 25, 2018
f844893
New secret command
TildeBeta Mar 25, 2018
a143ae3
Remove test file
TildeBeta Mar 25, 2018
5e7ed88
Better safe than sorry
TildeBeta Mar 25, 2018
676f1ec
Reuse ClientSession
TildeBeta Mar 25, 2018
f2e5d69
Add clarification
TildeBeta Mar 25, 2018
92bc40a
New command and disambiguate changes
TildeBeta Mar 25, 2018
9373d79
Docstring for new command
TildeBeta Mar 25, 2018
69ab790
banned images from guess
eivl Mar 25, 2018
e579d2a
Merge branch 'master' of https://github.com/eivl/code-jam-1
eivl Mar 25, 2018
bfdc89c
more banned images
eivl Mar 25, 2018
98b8ba7
flake8 error in import statement, commented out
eivl Mar 25, 2018
f741a2b
forcing travis rebuild
eivl Mar 25, 2018
a52208a
newline error flake8 fixed
eivl Mar 25, 2018
3e7f499
commands changed to bot.snakes.COMMAND (from bot.COMMAND)
eivl Mar 25, 2018
cc8624a
README info
eivl Mar 25, 2018
11be7f1
README update
eivl Mar 25, 2018
53973e4
README fix
eivl Mar 25, 2018
ce51e6c
Allow user to only run one instance of command at a time
TildeBeta Mar 25, 2018
812e4c2
Fix import order
TildeBeta Mar 25, 2018
cf32fdb
Document locked decorator
TildeBeta Mar 25, 2018
7e5a33c
Clean up get_snek a bit
TildeBeta Mar 25, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ name = "pypi"
aiodns = "*"
aiohttp = "<2.3.0,>=2.0.0"
websockets = ">=4.0,<5.0"
fuzzywuzzy = "*"
python-levenshtein = "*"
"discord.py" = {git = "https://github.com/Rapptz/discord.py", ref = "rewrite", extras = ["voice"]}

[dev-packages]
"flake8" = "*"
Expand Down
30 changes: 26 additions & 4 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

198 changes: 189 additions & 9 deletions bot/cogs/snakes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# 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.utils import disambiguate

log = logging.getLogger(__name__)

Expand All @@ -12,10 +22,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]:
Copy link
Copy Markdown

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!

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

Expand All @@ -25,23 +46,182 @@ 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?"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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"

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of one-letter variable names.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

j is not acceptable as a variable name for this.

# wikipedia does have a error page
try:
PAGEID = j["query"]["search"][0]["pageid"]
except KeyError:
PAGEID = 41118
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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}"

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'
]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are hilariously arbitrary. What's up? :P

Copy link
Copy Markdown

@TildeBeta TildeBeta Mar 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wikipedia has arbitrary images on their snake pages ¯\_(ツ)_/¯

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I don't love these one-letter variable names

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line is far too dense, and i is another awful variable name.

if not i.startswith('Map'):
for b in banned:
image_banned = False
if b in i:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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:
log.info("the image is clean: ", i)
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()
async def get(self, ctx: Context, name: str = None):
@bot_has_permissions(manage_messages=True)
async def get(self, ctx: Context, name: Snake = None):
"""
Go online and fetch information about a snake

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.
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What colour is this? Is it not included in the discord.Colour class as a staticmethod?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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(aliases=['identify'])
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):
Expand Down
49 changes: 49 additions & 0 deletions bot/converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import json
import random

import discord
from discord.ext.commands import Converter
from fuzzywuzzy import fuzz

from bot.utils import disambiguate


class Snake(Converter):
with open('snakes.json', 'r') as f:
snakes = json.load(f)

async def convert(self, ctx, name):
name = name.lower()

if name == 'python':
return 'Python (programming language)'

def get_potential(iterable, *, threshold=80):
nonlocal name
potential = []

for item in iterable:
original, item = item, item.lower()

if name == item:
return [original]

a, b = fuzz.ratio(name, item), fuzz.partial_ratio(name, item)
if a >= threshold or b >= threshold:
potential.append(original)

return potential

all_names = self.snakes.keys() | self.snakes.values()
timeout = len(all_names) * (3 / 4)

embed = discord.Embed(title='Found multiple choices. Please choose the correct one.', colour=0x59982F)
embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar_url)

name = await disambiguate(ctx, get_potential(all_names), timeout=timeout, embed=embed)
return self.snakes.get(name, name)

@classmethod
def random(cls):
# list cast necessary because choice() uses indexing internally
return random.choice(list(cls.snakes.values()))
Loading