From b31278643dbca23a2fd89d12eea89a3e5a908a0a Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Thu, 22 Mar 2018 21:06:40 -0400 Subject: [PATCH 01/27] just add some pass stuff glhf --- bot/cogs/snakes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index c9ed8042..43685005 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -28,6 +28,7 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: :param name: Optional, the name of the snake to get information for - omit for a random snake :return: A dict containing information on a snake """ + pass @command() async def get(self, ctx: Context, name: str = None): @@ -40,6 +41,7 @@ async def get(self, ctx: Context, name: str = None): :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 """ + pass # Any additional commands can be placed here. Be creative, but keep it to a reasonable amount! From f92cf5edb6b4c4b31feb209f9172226e49de68f9 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Thu, 22 Mar 2018 22:01:04 -0400 Subject: [PATCH 02/27] some startup stuff --- Procfile | 1 + bot/cogs/logging.py | 7 +++++++ bot/cogs/snakes.py | 3 ++- requirements.txt | 2 ++ 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Procfile create mode 100644 requirements.txt diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..c747d057 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: python run.py diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index 5f5cd85c..aed5a82f 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -15,6 +15,13 @@ def __init__(self, bot: AutoShardedBot): self.bot = bot async def on_ready(self): + log.info('Signed in as:') + log.info('--------------') + log.info(f'Username: {self.bot.user.name}') + log.info(f'User ID: {self.bot.user.id}') + log.info('--------------') + log.info('Serving Team 17 in Code Jam 1!') + log.info('--------------') log.info("Bot connected!") diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 43685005..d43ce2db 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -1,7 +1,7 @@ # coding=utf-8 import logging from typing import Any, Dict - +from bs4 import BeautifulSoup from discord.ext.commands import AutoShardedBot, Context, command log = logging.getLogger(__name__) @@ -14,6 +14,7 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.bot = bot + self.base_url = 'http://www.softschools.com/facts/animals/' async def get_snek(self, name: str = None) -> Dict[str, Any]: """ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e89298ac --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py +BeautifulSoup4 From 91cfb2d3ef11362763c619f59c00a01c5dc459ef Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Thu, 22 Mar 2018 22:05:07 -0400 Subject: [PATCH 03/27] fix bs4 req --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e89298ac..e161b8bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py -BeautifulSoup4 +beautifulsoup4 From 6dd7aefd0d9ff84a9b97f2dc439e893b5e40c22a Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Thu, 22 Mar 2018 22:20:10 -0400 Subject: [PATCH 04/27] remove that --- .gitignore | 3 +++ Procfile | 1 - requirements.txt | 2 -- run.py | 10 +++++++++- 4 files changed, 12 insertions(+), 4 deletions(-) delete mode 100644 Procfile delete mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 2895fff3..fa248c51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# config +config.json + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/Procfile b/Procfile deleted file mode 100644 index c747d057..00000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -worker: python run.py diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e161b8bb..00000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py -beautifulsoup4 diff --git a/run.py b/run.py index 2ec711fd..db2d0492 100644 --- a/run.py +++ b/run.py @@ -1,5 +1,6 @@ # coding=utf-8 import os +import json from aiohttp import AsyncResolver, ClientSession, TCPConnector @@ -35,6 +36,13 @@ # Commands, etc bot.load_extension("bot.cogs.snakes") -bot.run(os.environ.get("BOT_TOKEN")) +try: + with open('config.json') as f: + config = json.load(f) + token = config.get('bot_token') +except FileNotFoundError: + print('No token found.') +else: + bot.run(os.environ.get("BOT_TOKEN")) bot.http_session.close() # Close the aiohttp session when the bot finishes running From c8650449a1efe69f74dfd9ec0dc485e506b57175 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Thu, 22 Mar 2018 22:23:09 -0400 Subject: [PATCH 05/27] right thing to run --- run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.py b/run.py index db2d0492..a85847ca 100644 --- a/run.py +++ b/run.py @@ -43,6 +43,6 @@ except FileNotFoundError: print('No token found.') else: - bot.run(os.environ.get("BOT_TOKEN")) + bot.run(token) bot.http_session.close() # Close the aiohttp session when the bot finishes running From e830c959499f9b60b84a1027b5bbbfba150d8f93 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Fri, 23 Mar 2018 18:43:08 -0400 Subject: [PATCH 06/27] create the soup from the info source - Fix the way the bot is run to match with the docs (including removing `config.json` from the `.gitignore` file - Create the bot's session and response from the info source - update the info source and functions to easily get info - add dependencies (bs4 and lxml) --- .gitignore | 5 +---- Pipfile | 2 ++ Pipfile.lock | 45 ++++++++++++++++++++++++++++++++++++++++++++- bot/cogs/logging.py | 11 +++++++++++ bot/cogs/snakes.py | 11 +++++++---- run.py | 10 +--------- 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index fa248c51..1b259f0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ -# config -config.json - -# Byte-compiled / optimized / DLL files + Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class diff --git a/Pipfile b/Pipfile index 096fb9b3..3cc318a5 100644 --- a/Pipfile +++ b/Pipfile @@ -8,6 +8,8 @@ name = "pypi" aiodns = "*" aiohttp = "<2.3.0,>=2.0.0" websockets = ">=4.0,<5.0" +"beautifulsoup4" = "*" +lxml = "*" [dev-packages] "flake8" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 4e5214bb..40727166 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d797e580ddcddc99bf058109ab0306ad584c2902752a3d4076ba713fdc580fb7" + "sha256": "b9b192123ee24bf056723e79ecaaf98633d36ec32fd0175f2823d20e13dc882b" }, "pipfile-spec": 6, "requires": { @@ -53,6 +53,15 @@ ], "version": "==2.0.1" }, + "beautifulsoup4": { + "hashes": [ + "sha256:11a9a27b7d3bddc6d86f59fb76afb70e921a25ac2d6cc55b40d072bd68435a76", + "sha256:7015e76bf32f1f574636c4288399a6de66ce08fb7b2457f628a8d70c0fbabb11", + "sha256:808b6ac932dccb0a4126558f7dfdcf41710dd44a4ef497a0bb59a77f9f078e89" + ], + "index": "pypi", + "version": "==4.6.0" + }, "chardet": { "hashes": [ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", @@ -67,6 +76,40 @@ ], "version": "==2.6" }, + "lxml": { + "hashes": [ + "sha256:01c45df6d90497c20aa2a07789a41941f9a1029faa30bf725fc7f6d515b1afe9", + "sha256:0c9fef4f8d444e337df96c54544aeb85b7215b2ed7483bb6c35de97ac99f1bcd", + "sha256:0e3cd94c95d30ba9ca3cff40e9b2a14e1a10a4fd8131105b86c6b61648f57e4b", + "sha256:0e7996e9b46b4d8b4ac1c329a00e2d10edcd8380b95d2a676fccabf4c1dd0512", + "sha256:1858b1933d483ec5727549d3fe166eeb54229fbd6a9d3d7ea26d2c8a28048058", + "sha256:1b164bba1320b14905dcff77da10d5ce9c411ac4acc4fb4ed9a2a4d10fae38c9", + "sha256:1b46f37927fa6cd1f3fe34b54f1a23bd5bea1d905657289e08e1297069a1a597", + "sha256:231047b05907315ae9a9b6925751f9fd2c479cf7b100fff62485a25e382ca0d4", + "sha256:28f0c6652c1b130f1e576b60532f84b19379485eb8da6185c29bd8c9c9bc97bf", + "sha256:34d49d0f72dd82b9530322c48b70ac78cca0911275da741c3b1d2f3603c5f295", + "sha256:3682a17fbf72d56d7e46db2e80ca23850b79c28cfe75dcd9b82f58808f730909", + "sha256:3cf2830b9a6ad7f6e965fa53a768d4d2372a7856f20ffa6ce43d2fe9c0d34b19", + "sha256:5b653c9379ce29ce271fbe1010c5396670f018e78b643e21beefbb3dc6d291de", + "sha256:65a272821d5d8194358d6b46f3ca727fa56a6b63981606eac737c86d27309cdd", + "sha256:691f2cd97cf026c611df1ea5055755eec7f878f2d4f4330dc8686583de6fc5fd", + "sha256:6b6379495d3baacf7ed755ac68547c8dff6ce5d37bf370f0b7678888dc1283f9", + "sha256:75322a531504d4f383264391d89993a42e286da8821ddc5ac315e57305cb84f0", + "sha256:7f457cbda964257f443bac861d3a36732dcba8183149e7818ee2fb7c86901b94", + "sha256:7ff1fc76d8804e0f870c343a72007ff587090c218b0f92d8ee784ac2b6eaf5b9", + "sha256:8523fbde9c2216f3f2b950cb01ebe52e785eaa8a07ffeb456dd3576ca1b4fb9b", + "sha256:8f37627f16e026523fca326f1b5c9a43534862fede6c3e99c2ba6a776d75c1ab", + "sha256:a7182ea298cc3555ea56ffbb0748fe0d5e0d81451e2bc16d7f4645cd01b1ca70", + "sha256:abbd2fb4a5a04c11b5e04eb146659a0cf67bb237dd3d7ca3b9994d3a9f826e55", + "sha256:accc9f6b77bed0a6f267b4fae120f6008a951193d548cdbe9b61fc98a08b1cf8", + "sha256:bd88c8ce0d1504fdfd96a35911dd4f3edfb2e560d7cfdb5a3d09aa571ae5fbae", + "sha256:c557ad647facb3c0027a9d0af58853f905e85a0a2f04dcb73f8e665272fcdc3a", + "sha256:defabb7fbb99f9f7b3e0b24b286a46855caef4776495211b066e9e6592d12b04", + "sha256:e2629cdbcad82b83922a3488937632a4983ecc0fed3e5cfbf430d069382eeb9b" + ], + "index": "pypi", + "version": "==4.2.1" + }, "multidict": { "hashes": [ "sha256:0462372fc74e4c061335118a4a5992b9a618d6c584b028ef03cf3e9b88a960e2", diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index aed5a82f..f6ad4784 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -1,6 +1,10 @@ # coding=utf-8 import logging +from aiohttp import ClientSession + +from bs4 import BeautifulSoup + from discord.ext.commands import AutoShardedBot log = logging.getLogger(__name__) @@ -23,6 +27,13 @@ async def on_ready(self): log.info('Serving Team 17 in Code Jam 1!') log.info('--------------') log.info("Bot connected!") + async with ClientSession(loop=self.bot.loop) as session: + self.bot.session = session + self.bot.info_url = 'https://snake-facts.weebly.com/snake_names.html' + log.info('Session created!') + async with self.bot.session.get(self.bot.info_url).text as resp: + self.bot.soup = BeautifulSoup(resp, 'lxml') + log.info('Response successful!') def setup(bot): diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index d43ce2db..d6df16da 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -1,7 +1,7 @@ # coding=utf-8 import logging from typing import Any, Dict -from bs4 import BeautifulSoup + from discord.ext.commands import AutoShardedBot, Context, command log = logging.getLogger(__name__) @@ -14,7 +14,12 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.bot = bot - self.base_url = 'http://www.softschools.com/facts/animals/' + + def get_attr(self, type: str, attr: str): + return self.bot.soup.find(type, class_=attr).text + + def get_all_attrs(self, type: str, attr: str): + return self.bot.soup.find_all(type, class_=attr) async def get_snek(self, name: str = None) -> Dict[str, Any]: """ @@ -29,7 +34,6 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: :param name: Optional, the name of the snake to get information for - omit for a random snake :return: A dict containing information on a snake """ - pass @command() async def get(self, ctx: Context, name: str = None): @@ -42,7 +46,6 @@ async def get(self, ctx: Context, name: str = None): :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 """ - pass # Any additional commands can be placed here. Be creative, but keep it to a reasonable amount! diff --git a/run.py b/run.py index a85847ca..7664341c 100644 --- a/run.py +++ b/run.py @@ -1,6 +1,5 @@ # coding=utf-8 import os -import json from aiohttp import AsyncResolver, ClientSession, TCPConnector @@ -36,13 +35,6 @@ # Commands, etc bot.load_extension("bot.cogs.snakes") -try: - with open('config.json') as f: - config = json.load(f) - token = config.get('bot_token') -except FileNotFoundError: - print('No token found.') -else: - bot.run(token) +bot.run(os.environ.get('BOT_TOKEN')) bot.http_session.close() # Close the aiohttp session when the bot finishes running From 80273f6e8f7e5db4b36b6fd9666c4bf5f5b3b9f1 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Fri, 23 Mar 2018 22:00:11 -0400 Subject: [PATCH 07/27] rough estimate of what it should look like - still have to remove html tags - fix image url to actually work - move image to the embed's image function - region picture to thumbnail - more info --- bot/cogs/logging.py | 19 +++++---- bot/cogs/snakes.py | 70 +++++++++++++++++++++++++++++--- snakes.txt | 97 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 snakes.txt diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index f6ad4784..197491c4 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -3,8 +3,6 @@ from aiohttp import ClientSession -from bs4 import BeautifulSoup - from discord.ext.commands import AutoShardedBot log = logging.getLogger(__name__) @@ -27,13 +25,18 @@ async def on_ready(self): log.info('Serving Team 17 in Code Jam 1!') log.info('--------------') log.info("Bot connected!") - async with ClientSession(loop=self.bot.loop) as session: - self.bot.session = session - self.bot.info_url = 'https://snake-facts.weebly.com/snake_names.html' + + self.bot.session = ClientSession(loop=self.bot.loop) + self.bot.info_url = 'https://snake-facts.weebly.com/' log.info('Session created!') - async with self.bot.session.get(self.bot.info_url).text as resp: - self.bot.soup = BeautifulSoup(resp, 'lxml') - log.info('Response successful!') + + with open('./snakes.txt') as f: + self.bot.sneks = f.read().split('\n') + for i, snek in enumerate(self.bot.sneks): + self.bot.sneks[i] = snek.replace('​', '').replace('', '') + print(self.bot.sneks) + + log.info('Snakes loaded.') def setup(bot): diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index d6df16da..ebc22184 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -1,7 +1,12 @@ # coding=utf-8 import logging +from difflib import get_close_matches +from random import choice from typing import Any, Dict +from bs4 import BeautifulSoup + +import discord from discord.ext.commands import AutoShardedBot, Context, command log = logging.getLogger(__name__) @@ -15,11 +20,38 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.bot = bot - def get_attr(self, type: str, attr: str): - return self.bot.soup.find(type, class_=attr).text - - def get_all_attrs(self, type: str, attr: str): - return self.bot.soup.find_all(type, class_=attr) + def get_attr(self, soup, type, attr): + return soup.find(type, class_=attr) + + def get_all_attrs(self, soup, type, attr): + return soup.find_all(type, class_=attr) + + def no_sneks_found(self, name): + '''Helper function if the snake was not found in the directory.''' + em = discord.Embed( + title='No snake found.', + color=discord.Color.green() + ) + snakes = get_close_matches(name, self.bot.sneks) + if snakes: + em.description = 'Did you mean...\n' + em.description += '\n'.join(f'`{x}`' for x in snakes) + else: + snakes = 'https://github.com/SharpBit/code-jam-1/blob/master/snakes.txt' + em.description = f'Click [here]({snakes}) for the list of available snakes.' + return em + + def format_info(self, data): + '''Formats the info with the given data''' + em = discord.Embed( + title=f"{data['name']} ({data['scientific-name']})", + description='Nothing yet.', + color=discord.Color.green() + ) + em.set_thumbnail(url=data['image-url']) + em.set_footer(text='Bot by SharpBit and Pikuhana') + + return em async def get_snek(self, name: str = None) -> Dict[str, Any]: """ @@ -34,9 +66,28 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: :param name: Optional, the name of the snake to get information for - omit for a random snake :return: A dict containing information on a snake """ + if name: + if name not in self.bot.sneks: + return self.no_sneks_found(name) + else: + name = choice(self.bot.sneks) + snake = name.lower().replace(' ', '-') + url = f'{self.bot.info_url}{snake}.html' + async with self.bot.session.get(url) as resp: + info = await resp.read() + soup = BeautifulSoup(info, 'lxml') + img = self.get_attr(soup, 'div', 'wsite-image wsite-image-border-none ').a.img.src + names = self.get_attr(soup, 'td', 'wsite-multicol-col') + info = { + 'name': names.h1, + 'scientific-name': names.h2.i, + 'image-url': f"{self.bot.info_url}{img}" + } + + return info @command() - async def get(self, ctx: Context, name: str = None): + async def get(self, ctx: Context, *, name: str = None): """ Go online and fetch information about a snake @@ -46,6 +97,13 @@ async def get(self, ctx: Context, name: str = None): :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 """ + data = await self.get_snek(name) + # if the snake is not found + if isinstance(data, discord.Embed): + return await ctx.send(embed=data) + # format the given data + em = self.format_info(data) + await ctx.send(embed=em) # Any additional commands can be placed here. Be creative, but keep it to a reasonable amount! diff --git a/snakes.txt b/snakes.txt new file mode 100644 index 00000000..7df42454 --- /dev/null +++ b/snakes.txt @@ -0,0 +1,97 @@ +african rock python +​amazon tree boa +ball python +​banded water snake +​brown water snake +​burmese python +blue racer +brown snake +​black rat snake +boa constrictor +black-headed python +​black mamba +​boomslang +​bullsnake +​bushmaster +​blood python +brazilian rainbow boa +blue krait +carpet python +​children's python +corn snake +coachwhip +​​california kingsnake +coastal taipan +​central ranges taipan +copperhead +​cottonmouth +​cape cobra +california red-sided gartersnake +checkered gartersnake +​common krait +death adder +diamondback water snake +​dragon snake +​​​dumeril's boa +eastern diamondback rattlesnake +​eastern indigo snake +eastern garter snake +​eastern hognose +​eastern milksnake +​eastern green mamba +emerald tree boa +​eastern brown snake +​egyptian cobra +​eyelash viper +​european cat snake +fer-de-lance +​fox Snake +green anaconda +​golden lancehead +gaboon viper +​gigantophis +green tree python +​gopher snake +​grass snake +​horned viper +​inland taipan +​indian cobra +​king cobra +kenyan sand boa +king brown snake +​milk snake +​massasauga rattlesnake +​mojave rattlesnake +mozambique spitting cobra +monocled cobra +​mangrove snake +​mexican black kingsnake +​mamushi +​northern water snake +​olive python +pine snake +prairie rattlesnake +pigmy rattlesnake +​puff adder​ +queen Snake +rubber boa +​red-bellied snake +rosy boa +​reticulated python +​russel's viper +rinkhals +rough green snake +​ribbon snake +​ringneck snake +red-bellied black snake +​southern black racer +​sidewinder​ +spotted python +​titanoboa +​tiger snake +​timber rattlesnake +white-lipped python +​western diamondback rattlesnake +woma python +​western hognose snake +​​yellow anaconda \ No newline at end of file From 36ace300c4b77f8eca5ed333fdcfd5a0dd597dfd Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sat, 24 Mar 2018 08:10:47 -0400 Subject: [PATCH 08/27] @pikuhana can u do something --- bot/cogs/logging.py | 1 - bot/cogs/snakes.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index 197491c4..354d02be 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -34,7 +34,6 @@ async def on_ready(self): self.bot.sneks = f.read().split('\n') for i, snek in enumerate(self.bot.sneks): self.bot.sneks[i] = snek.replace('​', '').replace('', '') - print(self.bot.sneks) log.info('Snakes loaded.') diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index ebc22184..f9ee53d7 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -76,11 +76,11 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: async with self.bot.session.get(url) as resp: info = await resp.read() soup = BeautifulSoup(info, 'lxml') - img = self.get_attr(soup, 'div', 'wsite-image wsite-image-border-none ').a.img.src + img = soup.find(itemprop='image') names = self.get_attr(soup, 'td', 'wsite-multicol-col') info = { - 'name': names.h1, - 'scientific-name': names.h2.i, + 'name': names.h1.string, + 'scientific-name': names.h2.string, 'image-url': f"{self.bot.info_url}{img}" } From 2d743c87746a1bfa4e47d04e7095dd408a0057cb Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 24 Mar 2018 21:15:58 +0100 Subject: [PATCH 09/27] Use image from OGP tag, remove unused methods. --- bot/cogs/logging.py | 1 + bot/cogs/snakes.py | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index 354d02be..e9878ccc 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -36,6 +36,7 @@ async def on_ready(self): self.bot.sneks[i] = snek.replace('​', '').replace('', '') log.info('Snakes loaded.') + await self.bot.user.edit(username='SharpVolc') def setup(bot): diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index f9ee53d7..74c890fa 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -20,12 +20,6 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.bot = bot - def get_attr(self, soup, type, attr): - return soup.find(type, class_=attr) - - def get_all_attrs(self, soup, type, attr): - return soup.find_all(type, class_=attr) - def no_sneks_found(self, name): '''Helper function if the snake was not found in the directory.''' em = discord.Embed( @@ -49,7 +43,7 @@ def format_info(self, data): color=discord.Color.green() ) em.set_thumbnail(url=data['image-url']) - em.set_footer(text='Bot by SharpBit and Pikuhana') + em.set_footer(text='Bot by SharpBit and Volcyy') return em @@ -76,12 +70,12 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: async with self.bot.session.get(url) as resp: info = await resp.read() soup = BeautifulSoup(info, 'lxml') - img = soup.find(itemprop='image') - names = self.get_attr(soup, 'td', 'wsite-multicol-col') + img = soup.find(attrs={'property': {'og:image'}})['content'] + names = soup.find('td', class_='wsite-multicol-col') info = { 'name': names.h1.string, 'scientific-name': names.h2.string, - 'image-url': f"{self.bot.info_url}{img}" + 'image-url': img } return info From b77b7fe47df68dc9c46fa9efc4b3c5937b65a94b Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sat, 24 Mar 2018 16:17:08 -0400 Subject: [PATCH 10/27] test --- bot/cogs/snakes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index f9ee53d7..23252618 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -76,11 +76,11 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: async with self.bot.session.get(url) as resp: info = await resp.read() soup = BeautifulSoup(info, 'lxml') - img = soup.find(itemprop='image') + img = self.get_attr(soup, 'img alt', name.title() + ' ') names = self.get_attr(soup, 'td', 'wsite-multicol-col') info = { 'name': names.h1.string, - 'scientific-name': names.h2.string, + 'scientific-name': names.h2.i.string, 'image-url': f"{self.bot.info_url}{img}" } From f915d90c9a7f8707194163f43b6d1cddc31edca8 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sat, 24 Mar 2018 18:03:53 -0400 Subject: [PATCH 11/27] image (most work) and description So far these don't return an image: - bullsnake - ringneck snake - puff adder --- Pipfile | 1 + Pipfile.lock | 30 ++++++++++++++++++++++++++---- bot/cogs/logging.py | 4 ++-- bot/cogs/snakes.py | 7 ++++--- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/Pipfile b/Pipfile index 3cc318a5..03272d3a 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ aiohttp = "<2.3.0,>=2.0.0" websockets = ">=4.0,<5.0" "beautifulsoup4" = "*" lxml = "*" +"html5lib" = "*" [dev-packages] "flake8" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 40727166..5f9889d8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b9b192123ee24bf056723e79ecaaf98633d36ec32fd0175f2823d20e13dc882b" + "sha256": "440eb412bed48bab9fb3d5add459fa75d23c67a510e46b12dc4597be03dc179d" }, "pipfile-spec": 6, "requires": { @@ -69,6 +69,14 @@ ], "version": "==3.0.4" }, + "html5lib": { + "hashes": [ + "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", + "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" + ], + "index": "pypi", + "version": "==1.0.1" + }, "idna": { "hashes": [ "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", @@ -163,6 +171,20 @@ ], "version": "==2.3.0" }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, "websockets": { "hashes": [ "sha256:0c31bc832d529dc7583d324eb6c836a4f362032a1902723c112cf57883488d8c", @@ -325,10 +347,10 @@ }, "gitpython": { "hashes": [ - "sha256:ad61bc25deadb535b047684d06f3654c001d9415e1971e51c9c20f5b510076e9", - "sha256:b8367c432de995dc330b5b146c5bfdc0926b8496e100fda6692134e00c0dcdc5" + "sha256:05069e26177c650b3cb945dd543a7ef7ca449f8db5b73038b465105673c1ef61", + "sha256:c47cc31af6e88979c57a33962cbc30a7c25508d74a1b3a19ec5aa7ed64b03129" ], - "version": "==2.1.8" + "version": "==2.1.9" }, "idna": { "hashes": [ diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index 354d02be..13de6ddc 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -30,10 +30,10 @@ async def on_ready(self): self.bot.info_url = 'https://snake-facts.weebly.com/' log.info('Session created!') - with open('./snakes.txt') as f: + with open('./snakes.txt', encoding='utf-8') as f: self.bot.sneks = f.read().split('\n') for i, snek in enumerate(self.bot.sneks): - self.bot.sneks[i] = snek.replace('​', '').replace('', '') + self.bot.sneks[i] = snek.replace('\u200b', '').replace('\ufeff', '') log.info('Snakes loaded.') diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 74c890fa..8087d5a6 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -39,10 +39,10 @@ def format_info(self, data): '''Formats the info with the given data''' em = discord.Embed( title=f"{data['name']} ({data['scientific-name']})", - description='Nothing yet.', + description=data['description'], color=discord.Color.green() ) - em.set_thumbnail(url=data['image-url']) + em.set_image(url=data['image-url']) em.set_footer(text='Bot by SharpBit and Volcyy') return em @@ -75,7 +75,8 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: info = { 'name': names.h1.string, 'scientific-name': names.h2.string, - 'image-url': img + 'image-url': img, + 'description': soup.find(attrs={'property': {'og:description'}})['content'] } return info From a5525435151d3ae088a11792c41c2e6d9f1ef03f Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 24 Mar 2018 23:40:32 +0100 Subject: [PATCH 12/27] Attach URL and location map. --- bot/cogs/snakes.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 8087d5a6..188a7069 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -10,6 +10,14 @@ from discord.ext.commands import AutoShardedBot, Context, command log = logging.getLogger(__name__) +SNEK_MAP_SELECTOR = ( + "#wsite-content > div:nth-of-type(2) > div > div > table > " + "tbody > tr > td:nth-of-type(2) > div:nth-of-type(3) > div > a > img" +) +SCIENTIFIC_NAME_SELECTOR = ( + "#wsite-content > div:nth-of-type(1) > div > div > " + "table > tbody > tr > td:nth-of-type(1) > div:nth-of-type(2)" +) class Snakes: @@ -40,10 +48,11 @@ def format_info(self, data): em = discord.Embed( title=f"{data['name']} ({data['scientific-name']})", description=data['description'], - color=discord.Color.green() + color=discord.Color.green(), + url=data['url'] ) em.set_image(url=data['image-url']) - em.set_footer(text='Bot by SharpBit and Volcyy') + em.set_thumbnail(url=data['map-url']) return em @@ -72,11 +81,16 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: soup = BeautifulSoup(info, 'lxml') img = soup.find(attrs={'property': {'og:image'}})['content'] names = soup.find('td', class_='wsite-multicol-col') + sci_name = soup.select(SNEK_MAP_SELECTOR)[0].text.strip() + location_map = soup.select(SCIENTIFIC_NAME_SELECTOR)[0]['src'] + description_tag = soup.find(attrs={'property': {'og:description'}}) info = { 'name': names.h1.string, - 'scientific-name': names.h2.string, + 'scientific-name': sci_name, 'image-url': img, - 'description': soup.find(attrs={'property': {'og:description'}})['content'] + 'map-url': f'{self.bot.info_url}{location_map}', + 'description': description_tag['content'], + 'url': url } return info From 3234d57843b2bf40bf71ce76b95106a7fce59607 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sat, 24 Mar 2018 21:46:23 -0400 Subject: [PATCH 13/27] did you know command - still ussues with the snek map image url - see the comments in L14-16 in bot/cogs/snakes.py - add command aliases --- bot/cogs/snakes.py | 54 ++++++++++++++++++++++++++++++++++++++++------ run.py | 2 +- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 188a7069..d3c478f9 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -10,15 +10,25 @@ from discord.ext.commands import AutoShardedBot, Context, command log = logging.getLogger(__name__) + +# sometimes it's the 2nd div after 2nd td, sometimes it's the 3rd div. +# either way, it's returning `None`, so then info url + None = info url +# which is what the thumbnail url is currently. SNEK_MAP_SELECTOR = ( "#wsite-content > div:nth-of-type(2) > div > div > table > " "tbody > tr > td:nth-of-type(2) > div:nth-of-type(3) > div > a > img" ) + SCIENTIFIC_NAME_SELECTOR = ( "#wsite-content > div:nth-of-type(1) > div > div > " "table > tbody > tr > td:nth-of-type(1) > div:nth-of-type(2)" ) +DID_YOU_KNOW_SELECTOR = ( + '#wsite-content > div:nth-of-type(2) > div > div > table > ' + 'tbody > tr > td:nth-of-type(2) > div:nth-of-type(1)' +) + class Snakes: """ @@ -34,23 +44,27 @@ def no_sneks_found(self, name): title='No snake found.', color=discord.Color.green() ) + snakes = get_close_matches(name, self.bot.sneks) + if snakes: em.description = 'Did you mean...\n' em.description += '\n'.join(f'`{x}`' for x in snakes) else: snakes = 'https://github.com/SharpBit/code-jam-1/blob/master/snakes.txt' - em.description = f'Click [here]({snakes}) for the list of available snakes.' + em.description = f'No close matches found. Click [here]({snakes}) for the list of available snakes.' + return em def format_info(self, data): - '''Formats the info with the given data''' + '''Formats the info with the given data.''' em = discord.Embed( title=f"{data['name']} ({data['scientific-name']})", description=data['description'], color=discord.Color.green(), url=data['url'] ) + em.set_image(url=data['image-url']) em.set_thumbnail(url=data['map-url']) @@ -74,16 +88,20 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: return self.no_sneks_found(name) else: name = choice(self.bot.sneks) + snake = name.lower().replace(' ', '-') url = f'{self.bot.info_url}{snake}.html' + async with self.bot.session.get(url) as resp: info = await resp.read() soup = BeautifulSoup(info, 'lxml') + img = soup.find(attrs={'property': {'og:image'}})['content'] names = soup.find('td', class_='wsite-multicol-col') - sci_name = soup.select(SNEK_MAP_SELECTOR)[0].text.strip() - location_map = soup.select(SCIENTIFIC_NAME_SELECTOR)[0]['src'] + sci_name = soup.select(SCIENTIFIC_NAME_SELECTOR)[0].text.strip() + location_map = soup.select(SNEK_MAP_SELECTOR)[0]['src'] description_tag = soup.find(attrs={'property': {'og:description'}}) + info = { 'name': names.h1.string, 'scientific-name': sci_name, @@ -95,7 +113,25 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: return info - @command() + async def get_snek_fact(self): + '''Helper function to get a snake fact.''' + page = choice(self.bot.sneks).replace(' ', '-') + url = f'{self.bot.info_url}{page}.html' + + async with self.bot.session.get(url) as resp: + response = await resp.read() + soup = BeautifulSoup(response, 'lxml') + fact = soup.select(DID_YOU_KNOW_SELECTOR)[0].text + + em = discord.Embed( + title='Did you know?', + description=fact[13:], + color=discord.Color.green() + ) + + return em + + @command(aliases=['snakes.get', 'snakes.get()', 'get()']) async def get(self, ctx: Context, *, name: str = None): """ Go online and fetch information about a snake @@ -114,7 +150,13 @@ async def get(self, ctx: Context, *, name: str = None): em = self.format_info(data) await ctx.send(embed=em) - # Any additional commands can be placed here. Be creative, but keep it to a reasonable amount! + @command(aliases=['getsnekfact', 'snekfact()', 'get_snek_fact()']) + async def snekfact(self, ctx: Context): + ''' + Gets a randomsnek fact from the "Did you know?" cards + that the website has on the right hand side. + ''' + await ctx.send(embed=await self.get_snek_fact()) def setup(bot): diff --git a/run.py b/run.py index 7664341c..3afaea56 100644 --- a/run.py +++ b/run.py @@ -16,7 +16,7 @@ ">>> ", ">> ", "> ", ">>>", ">>", ">" ), # Order matters (and so do commas) - activity=Game(name="Help: bot.help()"), + activity=Game(name="with snekky sneks"), help_attrs={"aliases": ["help()"]}, formatter=Formatter() ) From 3f56b6c4d38b20b0fca0f1cc429197fd13b8030f Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sat, 24 Mar 2018 21:55:32 -0400 Subject: [PATCH 14/27] remove map image for now - until we get a fix - proper css selector - correct link --- bot/cogs/snakes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index d3c478f9..24b862c7 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -66,7 +66,7 @@ def format_info(self, data): ) em.set_image(url=data['image-url']) - em.set_thumbnail(url=data['map-url']) + # em.set_thumbnail(url=data['map-url']) return em @@ -99,14 +99,14 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: img = soup.find(attrs={'property': {'og:image'}})['content'] names = soup.find('td', class_='wsite-multicol-col') sci_name = soup.select(SCIENTIFIC_NAME_SELECTOR)[0].text.strip() - location_map = soup.select(SNEK_MAP_SELECTOR)[0]['src'] + # location_map = soup.select(SNEK_MAP_SELECTOR)[0]['src'] description_tag = soup.find(attrs={'property': {'og:description'}}) info = { 'name': names.h1.string, 'scientific-name': sci_name, 'image-url': img, - 'map-url': f'{self.bot.info_url}{location_map}', + # 'map-url': f'{self.bot.info_url}{location_map}', 'description': description_tag['content'], 'url': url } From dfd3175d0491006501be7b469a66dc15bef7d9d3 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sat, 24 Mar 2018 22:19:32 -0400 Subject: [PATCH 15/27] if python: send language - if apostrophe: remove it # used in `children's python` --- bot/cogs/snakes.py | 19 +++++++++++++++++-- bot/constants.py | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 24b862c7..5523f2b6 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -89,7 +89,7 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: else: name = choice(self.bot.sneks) - snake = name.lower().replace(' ', '-') + snake = name.lower().replace(' ', '-').replace("'", '') url = f'{self.bot.info_url}{snake}.html' async with self.bot.session.get(url) as resp: @@ -115,7 +115,7 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: async def get_snek_fact(self): '''Helper function to get a snake fact.''' - page = choice(self.bot.sneks).replace(' ', '-') + page = choice(self.bot.sneks).replace(' ', '-').replace("'", '') url = f'{self.bot.info_url}{page}.html' async with self.bot.session.get(url) as resp: @@ -142,6 +142,21 @@ async def get(self, ctx: Context, *, name: str = None): :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 """ + # Sends info about the programming language + if name.lower() == 'python': + # Python language info. + em = discord.Embed( + title='Python', + description='Python is an interpreted high-level programming language for general-purpose programming. ' + 'Created by Guido van Rossum and first released in 1991, ' + 'Python has a design philosophy that emphasizes code readability, ' + 'notably using significant whitespace.', + color=discord.Color.blurple() + ) + + em.set_thumbnail(url='https://ih0.redbubble.net/image.80621508.8934/flat,800x800,075,t.u1.jpg') + em.set_image(url='https://www.python.org/static/community_logos/python-logo-master-v3-TM.png') + return await ctx.send(embed=em) data = await self.get_snek(name) # if the snake is not found if isinstance(data, discord.Embed): diff --git a/bot/constants.py b/bot/constants.py index e272ff8f..c2c0e187 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -1,4 +1,5 @@ # coding=utf-8 +from discord import Embed # Channels, servers and roles PYTHON_GUILD = 267624335836053506 From 0ff0f4221cac732365a5898d8677f024f49fe476 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 25 Mar 2018 13:20:42 +0200 Subject: [PATCH 16/27] Fix location map image URL. --- bot/cogs/snakes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 5523f2b6..79fc34f6 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -66,7 +66,7 @@ def format_info(self, data): ) em.set_image(url=data['image-url']) - # em.set_thumbnail(url=data['map-url']) + em.set_thumbnail(url=data['map-url']) return em @@ -99,14 +99,14 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: img = soup.find(attrs={'property': {'og:image'}})['content'] names = soup.find('td', class_='wsite-multicol-col') sci_name = soup.select(SCIENTIFIC_NAME_SELECTOR)[0].text.strip() - # location_map = soup.select(SNEK_MAP_SELECTOR)[0]['src'] + location_map = soup.select(SNEK_MAP_SELECTOR)[0]['src'] description_tag = soup.find(attrs={'property': {'og:description'}}) info = { 'name': names.h1.string, 'scientific-name': sci_name, 'image-url': img, - # 'map-url': f'{self.bot.info_url}{location_map}', + 'map-url': f'{self.bot.info_url}{location_map[1:]}', 'description': description_tag['content'], 'url': url } From ddc6e4e240b4fb58f3c5567a6a2100128691bd9c Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 07:55:18 -0400 Subject: [PATCH 17/27] Still more bugs... - the stupid image that changes divs - that causes some commands for snakes to be broken (IndexError) - some snakes still don't show with an image (cut-off html?) --- bot/cogs/snakes.py | 51 ++++++++++++++++++---------------------------- bot/selectors.py | 15 ++++++++++++++ 2 files changed, 35 insertions(+), 31 deletions(-) create mode 100644 bot/selectors.py diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 79fc34f6..e485c285 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -9,25 +9,13 @@ import discord from discord.ext.commands import AutoShardedBot, Context, command -log = logging.getLogger(__name__) - -# sometimes it's the 2nd div after 2nd td, sometimes it's the 3rd div. -# either way, it's returning `None`, so then info url + None = info url -# which is what the thumbnail url is currently. -SNEK_MAP_SELECTOR = ( - "#wsite-content > div:nth-of-type(2) > div > div > table > " - "tbody > tr > td:nth-of-type(2) > div:nth-of-type(3) > div > a > img" -) - -SCIENTIFIC_NAME_SELECTOR = ( - "#wsite-content > div:nth-of-type(1) > div > div > " - "table > tbody > tr > td:nth-of-type(1) > div:nth-of-type(2)" +from bot.selectors import ( + SNEK_MAP_SELECTOR, + SCIENTIFIC_NAME_SELECTOR, + DID_YOU_KNOW_SELECTOR ) -DID_YOU_KNOW_SELECTOR = ( - '#wsite-content > div:nth-of-type(2) > div > div > table > ' - 'tbody > tr > td:nth-of-type(2) > div:nth-of-type(1)' -) +log = logging.getLogger(__name__) class Snakes: @@ -143,20 +131,21 @@ async def get(self, ctx: Context, *, name: str = None): :param name: Optional, the name of the snake to get information for - omit for a random snake """ # Sends info about the programming language - if name.lower() == 'python': - # Python language info. - em = discord.Embed( - title='Python', - description='Python is an interpreted high-level programming language for general-purpose programming. ' - 'Created by Guido van Rossum and first released in 1991, ' - 'Python has a design philosophy that emphasizes code readability, ' - 'notably using significant whitespace.', - color=discord.Color.blurple() - ) - - em.set_thumbnail(url='https://ih0.redbubble.net/image.80621508.8934/flat,800x800,075,t.u1.jpg') - em.set_image(url='https://www.python.org/static/community_logos/python-logo-master-v3-TM.png') - return await ctx.send(embed=em) + if name: + if name.lower() == 'python': + # Python language info. + em = discord.Embed( + title='Python (Pseudo anguis)', + description='Python is an interpreted high-level programming language for general-purpose programming. ' + 'Created by Guido van Rossum and first released in 1991, ' + 'Python has a design philosophy that emphasizes code readability, ' + 'notably using significant whitespace.', + color=discord.Color.blurple() + ) + + em.set_thumbnail(url='https://ih0.redbubble.net/image.80621508.8934/flat,800x800,075,t.u1.jpg') + em.set_image(url='https://www.python.org/static/community_logos/python-logo-master-v3-TM.png') + return await ctx.send(embed=em) data = await self.get_snek(name) # if the snake is not found if isinstance(data, discord.Embed): diff --git a/bot/selectors.py b/bot/selectors.py new file mode 100644 index 00000000..08c8830b --- /dev/null +++ b/bot/selectors.py @@ -0,0 +1,15 @@ +# still different divs for each page... +SNEK_MAP_SELECTOR = ( + "#wsite-content > div:nth-of-type(2) > div > div > table > " + "tbody > tr > td:nth-of-type(2) > div:nth-of-type(3) > div > a > img" +) + +SCIENTIFIC_NAME_SELECTOR = ( + "#wsite-content > div:nth-of-type(1) > div > div > " + "table > tbody > tr > td:nth-of-type(1) > div:nth-of-type(2)" +) + +DID_YOU_KNOW_SELECTOR = ( + '#wsite-content > div:nth-of-type(2) > div > div > table > ' + 'tbody > tr > td:nth-of-type(2) > div:nth-of-type(1)' +) From 9edf8c955a98d9866b352852790890fd14f1a184 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 25 Mar 2018 16:42:54 +0200 Subject: [PATCH 18/27] Added a TicTacToe game stub. --- bot/cogs/snakes.py | 143 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 3 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index e485c285..853e3d34 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -1,8 +1,9 @@ # coding=utf-8 import logging from difflib import get_close_matches +from enum import Enum from random import choice -from typing import Any, Dict +from typing import Any, Dict, Tuple from bs4 import BeautifulSoup @@ -10,14 +11,123 @@ from discord.ext.commands import AutoShardedBot, Context, command from bot.selectors import ( - SNEK_MAP_SELECTOR, + DID_YOU_KNOW_SELECTOR, SCIENTIFIC_NAME_SELECTOR, - DID_YOU_KNOW_SELECTOR + SNEK_MAP_SELECTOR ) log = logging.getLogger(__name__) +TICTACTOE_REACTIONS = { + (0, 0): '↖', + (0, 1): '⬆', + (0, 2): '↗', + (1, 0): '⬅', + (1, 1): '☀', + (1, 2): '➡', + (2, 0): '↙', + (2, 1): '⬇', + (2, 2): '↘' +} + + +class TicTacToeSymbol(Enum): + NOT_SET = ' ' + USER_FIELD = '🐍' + BOT_FIELD = '🤖' + + +class TicTacToePlayer(Enum): + BOT = 1 + USER = 2 + + +class TicTacToeBoard: + def __init__(self, ctx): + self.board = [ + [' ', ' ', ' '], + [' ', ' ', ' '], + [' ', ' ', ' '] + ] + self.ctx = ctx + self.msg = None + self.player = ctx.author + self.turn_user = choice(tuple(TicTacToePlayer)) + self.winner = None + + def __str__(self): + return '\n---+---+---\n'.join( + '|'.join( + f' {field} ' for field in line + ) for line in self.board + ) + + def advance_turn(self): + """Switches the active player.""" + + if self.turn_user == TicTacToePlayer.BOT: + self.turn_user = TicTacToePlayer.USER + else: + self.turn_user = TicTacToePlayer.BOT + + def can_set(self, coordinates: Tuple[int, int]): + """Checks if the given coordinate pair can be set.""" + + field = self.board[coordinates[0]][coordinates[1]] + return field == TicTacToeSymbol.NOT_SET + + def get_random_open_field(self) -> Tuple[int, int]: + """Returns a random open field for the bot to mark.""" + + return choice( + choice( + field for field in line if field == TicTacToeSymbol.NOT_SET + ) for line in self.board if any( + field == TicTacToeSymbol.NOT_SET for field in line + ) + ) + + def is_valid_reaction(self, user, msg_reaction): + """ + A check for `ctx.wait_for` to ensure + that an entered reaction was valid. + """ + + message_valid = msg_reaction.message == self.ctx.message + user_valid = user == self.player + for coordinates, reaction in TICTACTOE_REACTIONS.items(): + if msg_reaction == reaction and self.can_set(coordinates): + return message_valid and user_valid + return False + + def mark_field(self, coordinates: Tuple[int, int]): + """Marks the given coordinates with the current user's symbol.""" + + if self.turn_user == TicTacToePlayer.BOT: + symbol = TicTacToeSymbol.BOT_FIELD + else: + symbol = TicTacToeSymbol.USER_FIELD + + self.board[coordinates[0]][coordinates[1]] = symbol + + async def send(self): + """Sends the board to the context passed to the constructor.""" + + self.msg = await self.ctx.send(str(self)) + for reaction in TICTACTOE_REACTIONS.values(): + await self.msg.add_reaction(reaction) + + async def update_message(self): + """ + Edits the original board sent through + `send` to display an updated board. + Blindly assumes that `send` was called previously. + """ + + await self.msg.edit(content=str(self)) + + class Snakes: """ Snake-related commands @@ -162,6 +272,33 @@ async def snekfact(self, ctx: Context): ''' await ctx.send(embed=await self.get_snek_fact()) + @command() + async def tictactoe(self, ctx: Context): + """ + Starts a game of Tic Tac Toe with the author. + Only one instance of the game per player is allowed at a time. + """ + + game = TicTacToeBoard(ctx) + await game.send() + while game.winner is None: + if game.turn_user == TicTacToePlayer.BOT: + field = game.get_random_open_field() + game.mark_field(field) + else: + _, direction = await self.bot.wait_for( + 'reaction', check=game.is_valid_reaction + ) + game.mark_field( + next( + coords for coords in TICTACTOE_REACTIONS + if TICTACTOE_REACTIONS[coords] == direction + ) + ) + + game.advance_turn() + await game.update_message() + def setup(bot): bot.add_cog(Snakes(bot)) From 97e0e9d1735fac71bbc014acec283950b0869d75 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 12:04:58 -0400 Subject: [PATCH 19/27] fix some requests and travis stuff - remove unnecessary import - added it to on_ready in seperate cog - cog attribute, not bot attribute - fix accidental `.gitignore` change - tic tac toe uses python emoji - update tic tac toe board --- .gitignore | 2 +- bot/cogs/logging.py | 13 --------- bot/cogs/snakes.py | 66 ++++++++++++++++++++++++++++----------------- bot/constants.py | 2 +- 4 files changed, 43 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 1b259f0c..2895fff3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - Byte-compiled / optimized / DLL files +# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index 13de6ddc..aed5a82f 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -1,8 +1,6 @@ # coding=utf-8 import logging -from aiohttp import ClientSession - from discord.ext.commands import AutoShardedBot log = logging.getLogger(__name__) @@ -26,17 +24,6 @@ async def on_ready(self): log.info('--------------') log.info("Bot connected!") - self.bot.session = ClientSession(loop=self.bot.loop) - self.bot.info_url = 'https://snake-facts.weebly.com/' - log.info('Session created!') - - with open('./snakes.txt', encoding='utf-8') as f: - self.bot.sneks = f.read().split('\n') - for i, snek in enumerate(self.bot.sneks): - self.bot.sneks[i] = snek.replace('\u200b', '').replace('\ufeff', '') - - log.info('Snakes loaded.') - def setup(bot): bot.add_cog(Logging(bot)) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 853e3d34..d90c0947 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -5,11 +5,13 @@ from random import choice from typing import Any, Dict, Tuple +from aiohttp import ClientSession from bs4 import BeautifulSoup import discord from discord.ext.commands import AutoShardedBot, Context, command +from bot.constants import EMOJI_SERVER from bot.selectors import ( DID_YOU_KNOW_SELECTOR, SCIENTIFIC_NAME_SELECTOR, @@ -33,9 +35,16 @@ class TicTacToeSymbol(Enum): - NOT_SET = ' ' - USER_FIELD = '🐍' - BOT_FIELD = '🤖' + + def __init__(self, bot: AutoShardedBot): + self.bot = bot + self.game_emojis = {} + for e in bot.get_guild(EMOJI_SERVER): + self.game_emojis[e.name] = e.id + + self.NOT_SET = '⬜' + self.USER_FIELD = '🐍' + self.BOT_FIELD = str(bot.get_emoji(self.game_emojis.get('Python'))) class TicTacToePlayer(Enum): @@ -44,11 +53,11 @@ class TicTacToePlayer(Enum): class TicTacToeBoard: - def __init__(self, ctx): + def __init__(self, ctx: Context): self.board = [ - [' ', ' ', ' '], - [' ', ' ', ' '], - [' ', ' ', ' '] + '⬜⬜⬜', + '⬜⬜⬜', + '⬜⬜⬜' ] self.ctx = ctx self.msg = None @@ -57,11 +66,7 @@ def __init__(self, ctx): self.winner = None def __str__(self): - return '\n---+---+---\n'.join( - '|'.join( - f' {field} ' for field in line - ) for line in self.board - ) + return '\n'.join(self.board) def advance_turn(self): """Switches the active player.""" @@ -80,13 +85,13 @@ def can_set(self, coordinates: Tuple[int, int]): def get_random_open_field(self) -> Tuple[int, int]: """Returns a random open field for the bot to mark.""" - return choice( - choice( + return choice([ + choice([ field for field in line if field == TicTacToeSymbol.NOT_SET - ) for line in self.board if any( + ]) for line in self.board if any( field == TicTacToeSymbol.NOT_SET for field in line ) - ) + ]) def is_valid_reaction(self, user, msg_reaction): """ @@ -136,6 +141,17 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.bot = bot + async def on_ready(self): + self.session = ClientSession(loop=self.bot.loop) + self.info_url = 'https://snake-facts.weebly.com/' + log.info('Session created.') + + with open('./snakes.txt', encoding='utf-8') as f: + self.sneks = f.read().split('\n') + for i, snek in enumerate(self.sneks): + self.sneks[i] = snek.replace('\u200b', '').replace('\ufeff', '') + log.info('Snakes loaded.') + def no_sneks_found(self, name): '''Helper function if the snake was not found in the directory.''' em = discord.Embed( @@ -143,7 +159,7 @@ def no_sneks_found(self, name): color=discord.Color.green() ) - snakes = get_close_matches(name, self.bot.sneks) + snakes = get_close_matches(name, self.sneks) if snakes: em.description = 'Did you mean...\n' @@ -182,15 +198,15 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: :return: A dict containing information on a snake """ if name: - if name not in self.bot.sneks: + if name not in self.sneks: return self.no_sneks_found(name) else: - name = choice(self.bot.sneks) + name = choice(self.sneks) snake = name.lower().replace(' ', '-').replace("'", '') - url = f'{self.bot.info_url}{snake}.html' + url = f'{self.info_url}{snake}.html' - async with self.bot.session.get(url) as resp: + async with self.session.get(url) as resp: info = await resp.read() soup = BeautifulSoup(info, 'lxml') @@ -204,7 +220,7 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: 'name': names.h1.string, 'scientific-name': sci_name, 'image-url': img, - 'map-url': f'{self.bot.info_url}{location_map[1:]}', + 'map-url': f'{self.info_url}{location_map[1:]}', 'description': description_tag['content'], 'url': url } @@ -213,10 +229,10 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: async def get_snek_fact(self): '''Helper function to get a snake fact.''' - page = choice(self.bot.sneks).replace(' ', '-').replace("'", '') - url = f'{self.bot.info_url}{page}.html' + page = choice(self.sneks).replace(' ', '-').replace("'", '') + url = f'{self.info_url}{page}.html' - async with self.bot.session.get(url) as resp: + async with self.session.get(url) as resp: response = await resp.read() soup = BeautifulSoup(response, 'lxml') fact = soup.select(DID_YOU_KNOW_SELECTOR)[0].text diff --git a/bot/constants.py b/bot/constants.py index c2c0e187..5e43f391 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -1,8 +1,8 @@ # coding=utf-8 -from discord import Embed # Channels, servers and roles PYTHON_GUILD = 267624335836053506 +EMOJI_SERVER = 426864222862049289 BOT_CHANNEL = 267659945086812160 HELP1_CHANNEL = 303906576991780866 From adafa483466d41b646d11de7f8a7d9e3484ceeee Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 12:32:27 -0400 Subject: [PATCH 20/27] redo the bot.get python less clutter in the actual command --- bot/cogs/snakes.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index d90c0947..0b620d3a 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -141,6 +141,19 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.bot = bot + PYTHON_INFO = { + 'name': 'Python', + 'scientific-name': 'Pseudo anguis', + 'image-url': 'https://www.python.org/static/community_logos/python-logo-master-v3-TM.png', + 'url': 'https://en.wikipedia.org/wiki/Python_(programming_language)', + 'map-url': 'https://ih0.redbubble.net/image.80621508.8934/flat,800x800,075,t.u1.jpg', + 'description': 'Python is an interpreted high-level programming language ' + 'for general-purpose programming. ' + 'Created by Guido van Rossum and first released in 1991, ' + 'Python has a design philosophy that emphasizes code readability, ' + 'notably using significant whitespace.' + } + async def on_ready(self): self.session = ClientSession(loop=self.bot.loop) self.info_url = 'https://snake-facts.weebly.com/' @@ -170,12 +183,12 @@ def no_sneks_found(self, name): return em - def format_info(self, data): + def format_info(self, data, color=discord.Color.green()): '''Formats the info with the given data.''' em = discord.Embed( title=f"{data['name']} ({data['scientific-name']})", description=data['description'], - color=discord.Color.green(), + color=color, url=data['url'] ) @@ -259,18 +272,7 @@ async def get(self, ctx: Context, *, name: str = None): # Sends info about the programming language if name: if name.lower() == 'python': - # Python language info. - em = discord.Embed( - title='Python (Pseudo anguis)', - description='Python is an interpreted high-level programming language for general-purpose programming. ' - 'Created by Guido van Rossum and first released in 1991, ' - 'Python has a design philosophy that emphasizes code readability, ' - 'notably using significant whitespace.', - color=discord.Color.blurple() - ) - - em.set_thumbnail(url='https://ih0.redbubble.net/image.80621508.8934/flat,800x800,075,t.u1.jpg') - em.set_image(url='https://www.python.org/static/community_logos/python-logo-master-v3-TM.png') + em = self.format_info(self.PYTHON_INFO, discord.Color.blurple()) return await ctx.send(embed=em) data = await self.get_snek(name) # if the snake is not found From 70334634f892b6c1d05c350a265f51c5af921775 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 25 Mar 2018 18:35:14 +0200 Subject: [PATCH 21/27] Revert "Added a TicTacToe game stub." This reverts commit 9edf8c955a98d9866b352852790890fd14f1a184. --- bot/cogs/snakes.py | 146 +-------------------------------------------- 1 file changed, 3 insertions(+), 143 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 0b620d3a..284dde4f 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -1,9 +1,8 @@ # coding=utf-8 import logging from difflib import get_close_matches -from enum import Enum from random import choice -from typing import Any, Dict, Tuple +from typing import Any, Dict from aiohttp import ClientSession from bs4 import BeautifulSoup @@ -13,126 +12,14 @@ from bot.constants import EMOJI_SERVER from bot.selectors import ( - DID_YOU_KNOW_SELECTOR, + SNEK_MAP_SELECTOR, SCIENTIFIC_NAME_SELECTOR, - SNEK_MAP_SELECTOR + DID_YOU_KNOW_SELECTOR ) log = logging.getLogger(__name__) -TICTACTOE_REACTIONS = { - (0, 0): '↖', - (0, 1): '⬆', - (0, 2): '↗', - (1, 0): '⬅', - (1, 1): '☀', - (1, 2): '➡', - (2, 0): '↙', - (2, 1): '⬇', - (2, 2): '↘' -} - - -class TicTacToeSymbol(Enum): - - def __init__(self, bot: AutoShardedBot): - self.bot = bot - self.game_emojis = {} - for e in bot.get_guild(EMOJI_SERVER): - self.game_emojis[e.name] = e.id - - self.NOT_SET = '⬜' - self.USER_FIELD = '🐍' - self.BOT_FIELD = str(bot.get_emoji(self.game_emojis.get('Python'))) - - -class TicTacToePlayer(Enum): - BOT = 1 - USER = 2 - - -class TicTacToeBoard: - def __init__(self, ctx: Context): - self.board = [ - '⬜⬜⬜', - '⬜⬜⬜', - '⬜⬜⬜' - ] - self.ctx = ctx - self.msg = None - self.player = ctx.author - self.turn_user = choice(tuple(TicTacToePlayer)) - self.winner = None - - def __str__(self): - return '\n'.join(self.board) - - def advance_turn(self): - """Switches the active player.""" - - if self.turn_user == TicTacToePlayer.BOT: - self.turn_user = TicTacToePlayer.USER - else: - self.turn_user = TicTacToePlayer.BOT - - def can_set(self, coordinates: Tuple[int, int]): - """Checks if the given coordinate pair can be set.""" - - field = self.board[coordinates[0]][coordinates[1]] - return field == TicTacToeSymbol.NOT_SET - - def get_random_open_field(self) -> Tuple[int, int]: - """Returns a random open field for the bot to mark.""" - - return choice([ - choice([ - field for field in line if field == TicTacToeSymbol.NOT_SET - ]) for line in self.board if any( - field == TicTacToeSymbol.NOT_SET for field in line - ) - ]) - - def is_valid_reaction(self, user, msg_reaction): - """ - A check for `ctx.wait_for` to ensure - that an entered reaction was valid. - """ - - message_valid = msg_reaction.message == self.ctx.message - user_valid = user == self.player - for coordinates, reaction in TICTACTOE_REACTIONS.items(): - if msg_reaction == reaction and self.can_set(coordinates): - return message_valid and user_valid - return False - - def mark_field(self, coordinates: Tuple[int, int]): - """Marks the given coordinates with the current user's symbol.""" - - if self.turn_user == TicTacToePlayer.BOT: - symbol = TicTacToeSymbol.BOT_FIELD - else: - symbol = TicTacToeSymbol.USER_FIELD - - self.board[coordinates[0]][coordinates[1]] = symbol - - async def send(self): - """Sends the board to the context passed to the constructor.""" - - self.msg = await self.ctx.send(str(self)) - for reaction in TICTACTOE_REACTIONS.values(): - await self.msg.add_reaction(reaction) - - async def update_message(self): - """ - Edits the original board sent through - `send` to display an updated board. - Blindly assumes that `send` was called previously. - """ - - await self.msg.edit(content=str(self)) - - class Snakes: """ Snake-related commands @@ -290,33 +177,6 @@ async def snekfact(self, ctx: Context): ''' await ctx.send(embed=await self.get_snek_fact()) - @command() - async def tictactoe(self, ctx: Context): - """ - Starts a game of Tic Tac Toe with the author. - Only one instance of the game per player is allowed at a time. - """ - - game = TicTacToeBoard(ctx) - await game.send() - while game.winner is None: - if game.turn_user == TicTacToePlayer.BOT: - field = game.get_random_open_field() - game.mark_field(field) - else: - _, direction = await self.bot.wait_for( - 'reaction', check=game.is_valid_reaction - ) - game.mark_field( - next( - coords for coords in TICTACTOE_REACTIONS - if TICTACTOE_REACTIONS[coords] == direction - ) - ) - - game.advance_turn() - await game.update_message() - def setup(bot): bot.add_cog(Snakes(bot)) From cca57c450f13250b82d2dda6807ab5b5af92581f Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 12:38:55 -0400 Subject: [PATCH 22/27] completely remove --- bot/cogs/snakes.py | 2 +- bot/constants.py | 1 - run.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 284dde4f..7741b92d 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -5,12 +5,12 @@ from typing import Any, Dict from aiohttp import ClientSession + from bs4 import BeautifulSoup import discord from discord.ext.commands import AutoShardedBot, Context, command -from bot.constants import EMOJI_SERVER from bot.selectors import ( SNEK_MAP_SELECTOR, SCIENTIFIC_NAME_SELECTOR, diff --git a/bot/constants.py b/bot/constants.py index 5e43f391..e272ff8f 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -2,7 +2,6 @@ # Channels, servers and roles PYTHON_GUILD = 267624335836053506 -EMOJI_SERVER = 426864222862049289 BOT_CHANNEL = 267659945086812160 HELP1_CHANNEL = 303906576991780866 diff --git a/run.py b/run.py index 3afaea56..dfd980e2 100644 --- a/run.py +++ b/run.py @@ -35,6 +35,6 @@ # Commands, etc bot.load_extension("bot.cogs.snakes") -bot.run(os.environ.get('BOT_TOKEN')) +bot.run(os.environ.get("BOT_TOKEN")) bot.http_session.close() # Close the aiohttp session when the bot finishes running From 3f9177519154298622da19dfbfd5a60910a47c36 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 12:42:37 -0400 Subject: [PATCH 23/27] fix flake8 linting import order --- bot/cogs/snakes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 7741b92d..072c19e9 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -12,9 +12,9 @@ from discord.ext.commands import AutoShardedBot, Context, command from bot.selectors import ( - SNEK_MAP_SELECTOR, + DID_YOU_KNOW_SELECTOR, SCIENTIFIC_NAME_SELECTOR, - DID_YOU_KNOW_SELECTOR + SNEK_MAP_SELECTOR ) log = logging.getLogger(__name__) From b712a24aa6030ad46e60d73585119cfdced717e3 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 13:35:33 -0400 Subject: [PATCH 24/27] map region works all the time! loops through the possible divs --- bot/cogs/snakes.py | 8 +++++++- bot/selectors.py | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 072c19e9..ca660593 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -113,9 +113,15 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: img = soup.find(attrs={'property': {'og:image'}})['content'] names = soup.find('td', class_='wsite-multicol-col') sci_name = soup.select(SCIENTIFIC_NAME_SELECTOR)[0].text.strip() - location_map = soup.select(SNEK_MAP_SELECTOR)[0]['src'] description_tag = soup.find(attrs={'property': {'og:description'}}) + for x in range(1, 7): + try: + location_map = soup.select(SNEK_MAP_SELECTOR.format(x))[0]['src'] + break + except IndexError: + continue + info = { 'name': names.h1.string, 'scientific-name': sci_name, diff --git a/bot/selectors.py b/bot/selectors.py index 08c8830b..665cb161 100644 --- a/bot/selectors.py +++ b/bot/selectors.py @@ -1,7 +1,6 @@ -# still different divs for each page... SNEK_MAP_SELECTOR = ( "#wsite-content > div:nth-of-type(2) > div > div > table > " - "tbody > tr > td:nth-of-type(2) > div:nth-of-type(3) > div > a > img" + "tbody > tr > td:nth-of-type(2) > div:nth-of-type({}) > div > a > img" ) SCIENTIFIC_NAME_SELECTOR = ( From 5850ddb737a3846cb9d8e90bb5abd6955fe9da6e Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 15:48:59 -0400 Subject: [PATCH 25/27] success rate of images has gone up! - this site's html is like anti-webscrape - no one cares about the eastern brown snake - i put `for x in range(1, 7)`, but for some reason it's not doing the 1? - only 1 issue so far but as i already said, no one cares about the eastern brown snake so screw that --- Pipfile | 1 - Pipfile.lock | 24 +----------------------- bot/cogs/snakes.py | 17 +++++++++++++++-- bot/selectors.py | 23 +++++++++++++++++------ snakes.txt | 4 +--- 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/Pipfile b/Pipfile index 03272d3a..3cc318a5 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,6 @@ aiohttp = "<2.3.0,>=2.0.0" websockets = ">=4.0,<5.0" "beautifulsoup4" = "*" lxml = "*" -"html5lib" = "*" [dev-packages] "flake8" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 5f9889d8..5a010121 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "440eb412bed48bab9fb3d5add459fa75d23c67a510e46b12dc4597be03dc179d" + "sha256": "b9b192123ee24bf056723e79ecaaf98633d36ec32fd0175f2823d20e13dc882b" }, "pipfile-spec": 6, "requires": { @@ -69,14 +69,6 @@ ], "version": "==3.0.4" }, - "html5lib": { - "hashes": [ - "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", - "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" - ], - "index": "pypi", - "version": "==1.0.1" - }, "idna": { "hashes": [ "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", @@ -171,20 +163,6 @@ ], "version": "==2.3.0" }, - "six": { - "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" - ], - "version": "==1.11.0" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" - }, "websockets": { "hashes": [ "sha256:0c31bc832d529dc7583d324eb6c836a4f362032a1902723c112cf57883488d8c", diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index ca660593..82cc4abc 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -12,7 +12,9 @@ from discord.ext.commands import AutoShardedBot, Context, command from bot.selectors import ( + ALT_IMG_SELECTOR, DID_YOU_KNOW_SELECTOR, + SNAKE_IMG_SELECTOR, SCIENTIFIC_NAME_SELECTOR, SNEK_MAP_SELECTOR ) @@ -110,7 +112,18 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: info = await resp.read() soup = BeautifulSoup(info, 'lxml') - img = soup.find(attrs={'property': {'og:image'}})['content'] + for x in range(1, 7): + try: + img = soup.select(SNAKE_IMG_SELECTOR.format(x))[0]['src'] + break + except IndexError: + continue + try: + img = img[1:] + except UnboundLocalError: + print(name) + img = soup.select(ALT_IMG_SELECTOR)[0]['src'][1:] + names = soup.find('td', class_='wsite-multicol-col') sci_name = soup.select(SCIENTIFIC_NAME_SELECTOR)[0].text.strip() description_tag = soup.find(attrs={'property': {'og:description'}}) @@ -125,7 +138,7 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: info = { 'name': names.h1.string, 'scientific-name': sci_name, - 'image-url': img, + 'image-url': f'{self.info_url}{img}', 'map-url': f'{self.info_url}{location_map[1:]}', 'description': description_tag['content'], 'url': url diff --git a/bot/selectors.py b/bot/selectors.py index 665cb161..86e89fea 100644 --- a/bot/selectors.py +++ b/bot/selectors.py @@ -1,6 +1,17 @@ -SNEK_MAP_SELECTOR = ( - "#wsite-content > div:nth-of-type(2) > div > div > table > " - "tbody > tr > td:nth-of-type(2) > div:nth-of-type({}) > div > a > img" +ALT_IMG_SELECTOR = ( + '#wsite-content > div:nth-of-type(2) > div > div > table > ' + 'tbody > tr > td:nth-of-type(1) > div:nth-of-type(5) > div > a > img' +) + +DID_YOU_KNOW_SELECTOR = ( + '#wsite-content > div:nth-of-type(2) > div > div > table > ' + 'tbody > tr > td:nth-of-type(2) > div:nth-of-type(1)' +) + +SNAKE_IMG_SELECTOR = ( + '#wsite-content > div:nth-of-type(2) > div > div > table > ' + 'tbody > tr > td:nth-of-type(1) > div:nth-of-type(4) > div > div > ' + 'table > tbody > tr > td:nth-of-type({0}) > div > div > a > img' ) SCIENTIFIC_NAME_SELECTOR = ( @@ -8,7 +19,7 @@ "table > tbody > tr > td:nth-of-type(1) > div:nth-of-type(2)" ) -DID_YOU_KNOW_SELECTOR = ( - '#wsite-content > div:nth-of-type(2) > div > div > table > ' - 'tbody > tr > td:nth-of-type(2) > div:nth-of-type(1)' +SNEK_MAP_SELECTOR = ( + "#wsite-content > div:nth-of-type(2) > div > div > table > " + "tbody > tr > td:nth-of-type(2) > div:nth-of-type({0}) > div > a > img" ) diff --git a/snakes.txt b/snakes.txt index 7df42454..286bce33 100644 --- a/snakes.txt +++ b/snakes.txt @@ -49,7 +49,6 @@ fer-de-lance green anaconda ​golden lancehead gaboon viper -​gigantophis green tree python ​gopher snake ​grass snake @@ -87,11 +86,10 @@ red-bellied black snake ​southern black racer ​sidewinder​ spotted python -​titanoboa ​tiger snake ​timber rattlesnake white-lipped python ​western diamondback rattlesnake woma python ​western hognose snake -​​yellow anaconda \ No newline at end of file +​​yellow anaconda From 256d4e1664496c5c0355b853f2654aa002ff15c4 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 16:02:08 -0400 Subject: [PATCH 26/27] fix flake8 --- bot/cogs/snakes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 82cc4abc..8103f1f5 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -14,8 +14,8 @@ from bot.selectors import ( ALT_IMG_SELECTOR, DID_YOU_KNOW_SELECTOR, - SNAKE_IMG_SELECTOR, SCIENTIFIC_NAME_SELECTOR, + SNAKE_IMG_SELECTOR, SNEK_MAP_SELECTOR ) @@ -121,7 +121,6 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: try: img = img[1:] except UnboundLocalError: - print(name) img = soup.select(ALT_IMG_SELECTOR)[0]['src'][1:] names = soup.find('td', class_='wsite-multicol-col') From 0854cb1e604b6483f8285d351f21f9bd538ef99c Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Sun, 25 Mar 2018 19:04:23 -0400 Subject: [PATCH 27/27] fix requested changes --- bot/cogs/snakes.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index 8103f1f5..525be8fb 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -21,6 +21,19 @@ log = logging.getLogger(__name__) +PYTHON_INFO = { + 'name': 'Python', + 'scientific-name': 'Pseudo anguis', + 'image-url': 'https://www.python.org/static/community_lopython-logo-master-v3-TM.png', + 'url': 'https://en.wikipedia.org/wiki/Python_(programming_language)', + 'map-url': 'https://ih0.redbubble.net/image.80621508.8flat,800x800,075,t.u1.jpg', + 'description': 'Python is an interpreted high-level programmlanguage ' + 'for general-purpose programming. ' + 'Created by Guido van Rossum and first released1991, ' + 'Python has a design philosophy that emphasizes creadability, ' + 'notably using significant whitespace.' +} + class Snakes: """ @@ -30,19 +43,6 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.bot = bot - PYTHON_INFO = { - 'name': 'Python', - 'scientific-name': 'Pseudo anguis', - 'image-url': 'https://www.python.org/static/community_logos/python-logo-master-v3-TM.png', - 'url': 'https://en.wikipedia.org/wiki/Python_(programming_language)', - 'map-url': 'https://ih0.redbubble.net/image.80621508.8934/flat,800x800,075,t.u1.jpg', - 'description': 'Python is an interpreted high-level programming language ' - 'for general-purpose programming. ' - 'Created by Guido van Rossum and first released in 1991, ' - 'Python has a design philosophy that emphasizes code readability, ' - 'notably using significant whitespace.' - } - async def on_ready(self): self.session = ClientSession(loop=self.bot.loop) self.info_url = 'https://snake-facts.weebly.com/' @@ -54,8 +54,8 @@ async def on_ready(self): self.sneks[i] = snek.replace('\u200b', '').replace('\ufeff', '') log.info('Snakes loaded.') - def no_sneks_found(self, name): - '''Helper function if the snake was not found in the directory.''' + def no_sneks_found(self, name: str) -> discord.Embed: + """Helper function if the snake was not found in the directory.""" em = discord.Embed( title='No snake found.', color=discord.Color.green() @@ -72,8 +72,8 @@ def no_sneks_found(self, name): return em - def format_info(self, data, color=discord.Color.green()): - '''Formats the info with the given data.''' + def format_info(self, data: dict, color=discord.Color.green()) -> discord.Embed: + """Formats the info with the given data.""" em = discord.Embed( title=f"{data['name']} ({data['scientific-name']})", description=data['description'], @@ -145,8 +145,8 @@ async def get_snek(self, name: str = None) -> Dict[str, Any]: return info - async def get_snek_fact(self): - '''Helper function to get a snake fact.''' + async def get_snek_fact(self) -> discord.Embed: + """Helper function to get a snake fact.""" page = choice(self.sneks).replace(' ', '-').replace("'", '') url = f'{self.info_url}{page}.html' @@ -177,7 +177,7 @@ async def get(self, ctx: Context, *, name: str = None): # Sends info about the programming language if name: if name.lower() == 'python': - em = self.format_info(self.PYTHON_INFO, discord.Color.blurple()) + em = self.format_info(PYTHON_INFO, discord.Color.blurple()) return await ctx.send(embed=em) data = await self.get_snek(name) # if the snake is not found @@ -189,10 +189,10 @@ async def get(self, ctx: Context, *, name: str = None): @command(aliases=['getsnekfact', 'snekfact()', 'get_snek_fact()']) async def snekfact(self, ctx: Context): - ''' + """ Gets a randomsnek fact from the "Did you know?" cards that the website has on the right hand side. - ''' + """ await ctx.send(embed=await self.get_snek_fact())