diff --git a/Pipfile b/Pipfile index 096fb9b3..1465c88f 100644 --- a/Pipfile +++ b/Pipfile @@ -6,8 +6,10 @@ name = "pypi" [packages] "72eb2aa" = {file = "https://github.com/Rapptz/discord.py/archive/rewrite.zip"} aiodns = "*" -aiohttp = "<2.3.0,>=2.0.0" +aiohttp = "==2.0.0" websockets = ">=4.0,<5.0" +google-api-python-client = "*" +pillow = "*" [dev-packages] "flake8" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 4e5214bb..659049ac 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d797e580ddcddc99bf058109ab0306ad584c2902752a3d4076ba713fdc580fb7" + "sha256": "67e9d9bf3667a67671c41272b8c0d4d915535fc3158259e18f9e369977340f21" }, "pipfile-spec": 6, "requires": { @@ -29,22 +29,22 @@ }, "aiohttp": { "hashes": [ - "sha256:129d83dd067760cec3cfd4456b5c6d7ac29f2c639d856884568fd539bed5a51f", - "sha256:33c62afd115c456b0cf1e890fe6753055effe0f31a28321efd4f787378d6f4ab", - "sha256:666756e1d4cf161ed1486b82f65fdd386ac07dd20fb10f025abf4be54be12746", - "sha256:9705ded5a0faa25c8f14c6afb7044002d66c9120ed7eadb4aa9ca4aad32bd00c", - "sha256:af5bfdd164256118a0a306b3f7046e63207d1f8cba73a67dcc0bd858dcfcd3bc", - "sha256:b80f44b99fa3c9b4530fcfa324a99b84843043c35b084e0b653566049974435d", - "sha256:c67e105ec74b85c8cb666b6877569dee6f55b9548f982983b9bee80b3d47e6f3", - "sha256:d15c6658de5b7783c2538407278fa062b079a46d5f814a133ae0f09bbb2cfbc4", - "sha256:d611ebd1ef48498210b65486306e065fde031040a1f3c455ca1b6baa7bf32ad3", - "sha256:dcc7e4dcec6b0012537b9f8a0726f8b111188894ab0f924b680d40b13d3298a0", - "sha256:de8ef106e130b94ca143fdfc6f27cda1d8ba439462542377738af4d99d9f5dd2", - "sha256:eb6f1405b607fff7e44168e3ceb5d3c8a8c5a2d3effe0a27f843b16ec047a6d7", - "sha256:f0e2ac69cb709367400008cebccd5d48161dd146096a009a632a132babe5714c" + "sha256:41e77a9853a44cee04a748e573ade7806903b867dd7c5ef99981d8bb777b2c9f", + "sha256:4bff5fe18c198044873e57c80cb3528b1284e05fc9f89c0bdf920907d9e880c7", + "sha256:53d05ff50e8681d93babf8d134f22aca162e667474df5fb20da5e00e63cab5e0", + "sha256:7047ce8553fe390196a115c63d3897464a42fca93270660a0dbf44913ad97b9b", + "sha256:875cb0ec2b7c2fd103d4efaf4d08058876008aacb60394e8d03b9a4e09ae453e", + "sha256:9d73c4b530ff11ca58f96184618180fbc1f93d08fe8da600dfe9c858e471bdfa", + "sha256:a24cef553055453aee2347aeac5bc02a946223e88d3079712ba0a54a96e79f7b", + "sha256:a769a1af5c7645bc744c4904ab4662c544b1315a7c0b81e5e6ca5e0dd22a4a58", + "sha256:b7c61dfccdcd69822596066a3f92ccb7412de8f12fc9be30427284f0505cacfd", + "sha256:bc6510935f73fb2c8b5a674da1f385ce679f8a44deb82c05a7837e9906cb8f7f", + "sha256:bda5f69197cd9a7c9cc9839ef4c9c12e53f5a9d52d55c1f775cae534d14fa23a", + "sha256:eaa474f2212a9dcea53a2581a371cba07849dc1799800289bd6045c9f159003b", + "sha256:fee37e748b886cb430644fe97249ae986bb6e935f251c446a9e2110038ab1386" ], "index": "pypi", - "version": "==2.2.5" + "version": "==2.0.0" }, "async-timeout": { "hashes": [ @@ -60,12 +60,19 @@ ], "version": "==3.0.4" }, - "idna": { + "google-api-python-client": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:2cf9ab83fa62e06717363e8855fb027864caeb35a3197cadb7f0de38356881c4", + "sha256:95ce394028754ec537e5791e811511fdd5fabe6f1f8879407a8daed71ecb0b4c" ], - "version": "==2.6" + "index": "pypi", + "version": "==1.6.5" + }, + "httplib2": { + "hashes": [ + "sha256:5c0138b53a2f13b1dc29707b75c04236e35247d6b5f2a14c930966e8f45ce3cc" + ], + "version": "==0.11.0" }, "multidict": { "hashes": [ @@ -94,6 +101,94 @@ ], "version": "==4.1.0" }, + "oauth2client": { + "hashes": [ + "sha256:bd3062c06f8b10c6ef7a890b22c2740e5f87d61b6e1f4b1c90d069cdfc9dadb5", + "sha256:cf061f52f75e91d489bf5c276498f8af2655fe331b454f10022441513cf445a6" + ], + "version": "==4.1.2" + }, + "pillow": { + "hashes": [ + "sha256:0013f590a8f260df60bcfd65db19d18efc04e7f046c3c82a40e2e2b3292a937c", + "sha256:0b899ee80920bb533f26581af9b4660bc12aff4562555afe74e429101ebf3c94", + "sha256:12f29d6c23424f704c66b5b68c02fe0b571504459605cfe36ab8158359b0e1bb", + "sha256:135e9aa65150c53f7db85bf2bebb8a0e1a48ea850e80cf66e16dd04fa09d309c", + "sha256:153ec6f18f7b61641e0e6e502acfaf4a06c9aba2ea11c0b4b3578ea9f13a4a4a", + "sha256:17fe25efc785194d48c38fad85dce470013ba19d2fb66639e149f14bccf1327f", + "sha256:1912b7230459fd53682dae32b83cbd8e5d642ba36d4be18566f00a9c063aa13d", + "sha256:1a5b93084e01328a1cb1ecdad99d11d75e881e89a95f88d85b523646553b36c2", + "sha256:25193f934d37d836a6b1f4c062ce574a96cbca7c6d9dc8ddfbbac7f9c54deaa4", + "sha256:2c042352b430d678db50c78c5214e19638eff8b688941271da2de21fd298dfe5", + "sha256:2e818dbe445e86fc6c266973fe540c35125c42eb2cf13a6095e9adaa89c0deb5", + "sha256:2fcde9954c8882d1c7f93bb828caa34a4c5e3ee69dbc7895dc8652ad972b455a", + "sha256:35f7d998b8e82fb3fb51ff88b30485eb81cd7dd56ec7e1a8deba23eb88532d44", + "sha256:37cc0339abfa9e295c75d9a7f227d35cb44716feb95057f9449c4a9e9a17daf7", + "sha256:43334f9581cd067945b8898cef9eb5714ee4883f8de0304c011f1dbdb1d4e2aa", + "sha256:4bd4a71501b6d51db4abc07e1f43f5a6fed0a1a9583cca0b401d6af50284b0db", + "sha256:57aa6198ba8acba1313c3b743e267d821a60cac77e6026caf0b55ca58d3d23be", + "sha256:5b0d657460d9f3615876fec6306e97ca15a471f6169b622d76a47e270998acf1", + "sha256:5cd36804f9f06a914a883fe682df5711d16d7b4f44d43189c5f013e7cd91e149", + "sha256:6977cf073d83358b34f93abf5c1f1193b88675fe0e4441e0e28318bc3dcba7a0", + "sha256:718ec7a122b28d64afc5fbc3a9b99bb0545ef511373cac06fe7624520e82cb20", + "sha256:7dfbefdb3fb911ca9faed307bf309861e9995e36cca6b761c7ba6d9b77a9744a", + "sha256:801cca8923508311bf5d6d0f7da5362552e8208ebd8ec0d7b9f2cd2ff5705734", + "sha256:82b172e3264e62372c01b5b009b5b1a02fbb9276cbe5cc57ab00a6d6e5ed9a18", + "sha256:82d1ff571489765df2816785d532e243bde213752156c227fca595723ec5ff42", + "sha256:8580fc58074a16b749905b26cf8363f7b628dd167ba0130f5382cdc91c86b509", + "sha256:931030d1d6282b7900e6b0a7ff9ecdb503b5e1e6781800dab2b71a9f39405bff", + "sha256:9525cd680a6f9e80c6c0af03cf973e6505c59f60b4745f682cd1a449e54b31bb", + "sha256:a224651a81e45ef4f1d0164e256c5f6b4abb49f2ae8f22ba2f3a9d0ff338e608", + "sha256:a370d1c570f1d72e877099651e752332444b1c5009381f043c9da5fd47f3ebae", + "sha256:b1d33c63a55d0d85df0ad02b2c16158fb4d8153afa7b908f1a67330fac694cd6", + "sha256:b2240f298482f823576f397bb9f32ea913ad9456c526e141bc6f0a022b37a3e8", + "sha256:b85f703c2ffe539313e39ce0676bed0f355cec45a16e58c9ab7417445843047c", + "sha256:b9f63451084a718eccdeb1e382768c94647915653af4d6019f64560d9e98642b", + "sha256:c793dfaa130847ccff958492b76ae8b9304e60b8a79a92962cb19e368276a22b", + "sha256:d60c1625b108432ace8b1fa1a584017e5efa73f107d0f493c7f39c79bebf1d41", + "sha256:dc4b018d5c9b636f7546583c5591b9ea00c328c3e5871992ef5b95bac353f097", + "sha256:ddd16ab250b4fc97db1c47407e78c25216a75c29d29d10ad37e51b7a2ec7b2c3", + "sha256:e126ff4fed71e78333840c07279e1617f63cfca76d63ad5b27d65a7277206a3d", + "sha256:f8d49be8c282df8d2e1ab6ab53ab8abd859b1fa6fed384457ee85c9eff64ef97", + "sha256:fcf64c91fd44485100a2965d23bb0e227d093e91f7e776c5ca3b32574766eb56" + ], + "index": "pypi", + "version": "==5.0.0" + }, + "pyasn1": { + "hashes": [ + "sha256:0d7f6e959fe53f3960a23d73f35e1fce61348b30915b6664309ca756de7c1f89", + "sha256:5a0db897b311d265cde49615cf783f1c78613138605cdd0f907ecfa5b2aba3ee", + "sha256:758cb50abddc03e4563fd9e7f03db56e3e87b58c0bd01247360326e5c0c7ffa5", + "sha256:7d626683e3d792cccc608da02498aff37ab4f3dafd8905d6bf755d11f9b26b43", + "sha256:a7efe807c4b83a859e2735c692b92ed7b567cfddc4163763412920041d876c2b", + "sha256:b5a9ca48055b9a20f6d1b3d68e38692e5431c86a0f99ea602e61294e891fee5b", + "sha256:c07d6e587b2f928366b1f67c09bda026a3e6fcc99e80a744dc67f8fca3895626", + "sha256:d258b0a71994f7770599835249cece1caef3c70def868c4915e6e5ca49b67d15", + "sha256:d5cd6ed995dba16fad0c521cfe31cd2d68400b53fcc2bce93326829be73ab6d1", + "sha256:d84c2aea3cf43780e9e6a19f4e4dddee9f6976519020e64e47c57e5c7a8c3dd2", + "sha256:e85895087905c65b5b594eb91f7522664c85545b147d5f4d4e7b1b07da8dcbdc", + "sha256:f81c96761fca60d64b1c9b79ec2e40cf9495a745cf570613079ef324aeb9672b" + ], + "version": "==0.4.2" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:041e9fbafac548d095f5b6c3b328b80792f006196e15a232b731a83c93d59493", + "sha256:0cdca76a68dcb701fff58c397de0ef9922b472b1cb3ea9695ca19d03f1869787", + "sha256:0cea139045c38f84abaa803bcb4b5e8775ea12a42af10019d942f227acc426c3", + "sha256:0f2e50d20bc670be170966638fa0ae603f0bc9ed6ebe8e97a6d1d4cef30cc889", + "sha256:47fb6757ab78fe966e7c58b2030b546854f78416d653163f0ce9290cf2278e8b", + "sha256:598a6004ec26a8ab40a39ea955068cf2a3949ad9c0030da970f2e1ca4c9f1cc9", + "sha256:72fd8b0c11191da088147c6e4678ec53e573923ecf60b57eeac9e97433e09fc2", + "sha256:854700bbdd01394e2ada9c1bfbd0ed9f5d0c551350dbbd023e88b11d2771ae06", + "sha256:af00ea8f2022b6287dc375b2c70f31ab5af83989fc6fe9eacd4976ce26cd7ccc", + "sha256:b1f395cae2d669e0830cb023aa86f9f283b7a9aa32317d7f80d8e78aa2745812", + "sha256:c6747146e95d2b14cc2a8399b2b0bde3f93778f8f9ec704690d2b589c376c137", + "sha256:f53fe5bcebdf318f51399b250fe8325ef3a26d927f012cc0c8e0f9e9af7f9deb" + ], + "version": "==0.2.1" + }, "pycares": { "hashes": [ "sha256:0e81c971236bb0767354f1456e67ab6ae305f248565ce77cd413a311f9572bf5", @@ -120,6 +215,28 @@ ], "version": "==2.3.0" }, + "rsa": { + "hashes": [ + "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5", + "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd" + ], + "version": "==3.4.2" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "uritemplate": { + "hashes": [ + "sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd", + "sha256:1b9c467a940ce9fb9f50df819e8ddd14696f89b9a8cc87ac77952ba416e0a8fd", + "sha256:c02643cebe23fc8adb5e6becffe201185bf06c40bda5c0b4028a93f1527d011d" + ], + "version": "==3.0.0" + }, "websockets": { "hashes": [ "sha256:0c31bc832d529dc7583d324eb6c836a4f362032a1902723c112cf57883488d8c", @@ -144,21 +261,21 @@ }, "yarl": { "hashes": [ - "sha256:045dbba18c9142278113d5dc62622978a6f718ba662392d406141c59b540c514", - "sha256:17e57a495efea42bcfca08b49e16c6d89e003acd54c99c903ea1cb3de0ba1248", - "sha256:213e8f54b4a942532d6ac32314c69a147d3b82fa1725ca05061b7c1a19a1d9b1", - "sha256:3353fae45d93cc3e7e41bfcb1b633acc37db821d368e660b03068dbfcf68f8c8", - "sha256:51a084ff8756811101f8b5031a14d1c2dd26c666976e1b18579c6b1c8761a102", - "sha256:5580f22ac1298261cd24e8e584180d83e2cca9a6167113466d2d16cb2aa1f7b1", - "sha256:64727a2593fdba5d6ef69e94eba793a196deeda7152c7bd3a64edda6b1f95f6e", - "sha256:6e75753065c310befab71c5077a59b7cb638d2146b1cfbb1c3b8f08b51362714", - "sha256:7236eba4911a5556b497235828e7a4bc5d90957efa63b7c4b3e744d2d2cf1b94", - "sha256:a69dd7e262cdb265ac7d5e929d55f2f3d07baaadd158c8f19caebf8dde08dfe8", - "sha256:d9ca55a5a297408f08e5401c23ad22bd9f580dab899212f0d5dc1830f0909404", - "sha256:e072edbd1c5628c0b8f97d00cf6c9fcd6a4ee2b5ded10d463fcb6eaa066cf40c", - "sha256:e9a6a319c4bbfb57618f207e86a7c519ab0f637be3d2366e4cdac271577834b8" - ], - "version": "==1.1.1" + "sha256:1ce778f7001c84735f4847f655e60a58b2104783b3478d6a0867c6e3f887c950", + "sha256:27b24ba3ef3cb8475aea1a655a1750bb11918ba139278af21db5846ee9643138", + "sha256:3163e9bcb8e86baac9aba73bc68b6f208d83aa5385b64ca1aa0ac271ee514d5c", + "sha256:3a1d5d97fb2cbc93936857b734d849240a4f7a0cc5746298b5321b06936949f7", + "sha256:493660233ec60caf52a18c0f1114b24d7d2a34320dd4977d48b019e8e89a3757", + "sha256:49d210792e797f34abdd8f28f412552ac6780b883c0098871bba4ce275200075", + "sha256:56f5bd0906dbf50dd24e879e168742629562c56565aa36149b57cfbff55d3348", + "sha256:6fc3dddc0b8d2bda65492ca42dabfa92bf65d7ef093b1ca37ebf91486b4499fc", + "sha256:7e01e5a67aae4c548da9e902fdd4b151a0a85f707e89d215fcb693a7043c8817", + "sha256:83f4ff0318137d212368ee75d68c9541de293d3a54a033f96bf5c17ddea07bad", + "sha256:c25ff7942ec5157c3c4e8d5fd203c28d44c698802bddd832b092a93fc0901b83", + "sha256:c37fe44e909f7de30499b562d3566e2cd5f8f4343a0e5e0b54b536de963660ff", + "sha256:c6e459ad6bc8241e675dc15a1784707d82c3e12854c8d4718162ced48a6824e4" + ], + "version": "==0.10.3" } }, "develop": { @@ -282,10 +399,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/cards/backs/card_back1.jpg b/bot/cards/backs/card_back1.jpg new file mode 100644 index 00000000..22959fa7 Binary files /dev/null and b/bot/cards/backs/card_back1.jpg differ diff --git a/bot/cards/backs/card_back2.jpg b/bot/cards/backs/card_back2.jpg new file mode 100644 index 00000000..d56edc32 Binary files /dev/null and b/bot/cards/backs/card_back2.jpg differ diff --git a/bot/cards/card_bottom.png b/bot/cards/card_bottom.png new file mode 100644 index 00000000..8b2b91c5 Binary files /dev/null and b/bot/cards/card_bottom.png differ diff --git a/bot/cards/card_frame.png b/bot/cards/card_frame.png new file mode 100644 index 00000000..149a0a5f Binary files /dev/null and b/bot/cards/card_frame.png differ diff --git a/bot/cards/card_top.png b/bot/cards/card_top.png new file mode 100644 index 00000000..e329c873 Binary files /dev/null and b/bot/cards/card_top.png differ diff --git a/bot/cards/expressway.ttf b/bot/cards/expressway.ttf new file mode 100644 index 00000000..39e15794 Binary files /dev/null and b/bot/cards/expressway.ttf differ diff --git a/bot/cogs/snakes.py b/bot/cogs/snakes.py index c9ed8042..bbca71d9 100644 --- a/bot/cogs/snakes.py +++ b/bot/cogs/snakes.py @@ -1,11 +1,53 @@ # coding=utf-8 + +# This shit imports more than the USA D: +import ast +import difflib +import json import logging -from typing import Any, Dict +import os +import random +import textwrap +import urllib.parse +from functools import partial +from io import BytesIO +from typing import Dict + +from PIL import Image, ImageDraw, ImageFont + +import aiohttp + +import async_timeout +from discord import Embed, File from discord.ext.commands import AutoShardedBot, Context, command log = logging.getLogger(__name__) +# Probably should move these somewhere + +WIKI = "https://en.wikipedia.org/w/api.php?" +BASEURL = WIKI + "format=json&action=query&prop=extracts|pageimages&exintro=&explaintext=&titles={title}&redirects=1" +FAILIMAGE = "http://i.imgur.com/HtIPyLy.png/beep" + +# Yes, we're naming snakes. Shush. +with open("bot/specials.json") as specials: + SPECIALS = json.load(specials) + +with open("bot/snakes.txt") as f: + SNAKES = [line.strip() for line in f.readlines()] + +# I don't think this is the best way of doing it +CARD = { + "top": Image.open("bot/cards/card_top.png"), + "frame": Image.open("bot/cards/card_frame.png"), + "bottom": Image.open("bot/cards/card_bottom.png"), + "backs": [ + Image.open(f"bot/cards/backs/{file}") for file in os.listdir("bot/cards/backs") + ], + "font": ImageFont.truetype("bot/cards/expressway.ttf", 20) +} + class Snakes: """ @@ -14,35 +56,241 @@ class Snakes: def __init__(self, bot: AutoShardedBot): self.bot = bot + self.session = aiohttp.ClientSession(loop=bot.loop) + + @staticmethod + def kwargs(args, positional_args=list()): + """for given command parameters *args **kwargs turns them into a dictionary with given positional_args list""" + # This is Someone's fancy kwarg stuff - juan + final = {} + if all('=' in str(arg) for arg in args): + for arg in args: + + try: + k, v = arg.split('=') + except ValueError: + return {k: v for k, v in positional_args} + + try: + v = v.strip('()[]') + final[k] = ast.literal_eval("[" + '","'.join(v) + "\"]") + except(ValueError, SyntaxError): + final[k] = v + + elif all('=' not in str(arg) for arg in args): + for index, arg in enumerate(args): + + try: + arg = arg.strip('()[]') + final[[k for k, v in positional_args][index]] = ast.literal_eval("[\"" + '","'.join(arg) + "\"]") + except(ValueError, SyntaxError): + final[[k for k, v in positional_args][index]] = arg + else: + return {k: v for k, v in positional_args} - async def get_snek(self, name: str = None) -> Dict[str, Any]: - """ - Go online and fetch information about a snake + return final - The information includes the name of the snake, a picture of the snake, and various other pieces of info. - What information you get for the snake is up to you. Be creative! + @staticmethod + def snake_url(name: str): + """Get the URL of a snake""" + name = name.lower() - 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! + def format_url(text: str): + """Get the full URL with that snake :D""" + return BASEURL.format(title=urllib.parse.quote_plus(text)) - :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 - """ + # Check if the snake name is valid + if name in SNAKES: + return format_url(name) + + # Get the most similar name if a match wasn't found + return difflib.get_close_matches(name, SNAKES, n=5, cutoff=0) + + @staticmethod + async def fetch_image(self, url: str): + """Fetch an image, return a BytesIO object""" + stream = BytesIO() + async with async_timeout.timeout(10): + async with self.session.get(url) as response: + stream.write(await response.read()) + + stream.seek(0) + return stream + + async def get_snek(self, name: str = None, kwargs: dict = None) -> Dict[str, str]: + """If a name is provided, this gets a specific snake. Otherwise, it gets a random snake.""" + + if name is None: + name = random.choice(SNAKES) + + name = name.lower().replace("-", " ") + if name in SPECIALS: + return SPECIALS[name] + + # Get snake information + url = self.snake_url(name) + + # Does some magic if the url is actually a list ^^ + if isinstance(url, list): + if kwargs.get("autocorrect", False): + return await self.get_snek(url[0]) + + return { + "name": "Oops!", + "info": "We can't find that snake, but here are some similar names:\n\n" + "\n".join(url).title() + } + + # Get the content + async with async_timeout.timeout(10): + async with self.session.get(url) as response: + response = await response.json() + + page = response["query"]["pages"] + content = next(iter(page.values())) + + # Parse the full-res image from the thumbnail + thumb = content.get("thumbnail", {}).get("source", FAILIMAGE) + image = "/".join(thumb.replace("thumb/", "").split("/")[:-1]) + + return { + "name": content["title"], + "info": content.get("extract", "I don't know about that snake!"), + "image": image + } @command() - async def get(self, ctx: Context, name: str = None): - """ - Go online and fetch information about a snake + async def get(self, ctx: Context, *args): + """Get information about a snake :D""" + content = await self.get_snek( + args[0] if args else None, + self.kwargs(args[1:], positional_args=["autocorrect"]) + ) + + embed = Embed( + title=content["name"], + description=content["info"][:2000], # May consider cutting it shorter. 'Rattler' is a long one. + color=0x7289da + ) - 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. + if "image" in content: + embed.set_image(url=content.get("image")) - :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 - """ + await ctx.send(embed=embed) # Any additional commands can be placed here. Be creative, but keep it to a reasonable amount! + # Cards are here :D + + @staticmethod + def generate_card(buffer: BytesIO, content: dict) -> BytesIO: + """Generate a card from snake information""" + snake = Image.open(buffer) + + # Get the size of the snake icon, configure the height of the image box (yes, it changes) + icon_width = 347 # Hardcoded, not much i can do about that + icon_height = int((icon_width / snake.width) * snake.height) + frame_copies = icon_height // CARD['frame'].height + 1 + snake.thumbnail((icon_width, icon_height)) + + # Get the dimensions of the final image + main_height = icon_height + CARD['top'].height + CARD['bottom'].height + main_width = CARD['frame'].width + + # Start creating the foreground + foreground = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) + foreground.paste(CARD['top'], (0, 0)) + + # Generate the frame borders to the correct height + for offset in range(frame_copies): + position = (0, CARD['top'].height + offset * CARD['frame'].height) + foreground.paste(CARD['frame'], position) + + # Add the image and bottom part of the image + foreground.paste(snake, (36, CARD['top'].height)) # Also hardcoded :( + foreground.paste(CARD['bottom'], (0, CARD['top'].height + icon_height)) + + # Setup the background + back = random.choice(CARD['backs']) + back_copies = main_height // back.height + 1 + full_image = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) + + # Generate the tiled background + for offset in range(back_copies): + full_image.paste(back, (16, 16 + offset * back.height)) + + # Place the foreground onto the final image + full_image.paste(foreground, (0, 0), foreground) + + # Get the first two sentences of the info + description = '.'.join(content['info'].split(".")[:2]) + '.' + + # Setup positioning variables + margin = 36 + offset = CARD['top'].height + icon_height + margin + + # Create blank rectangle image which will be behind the text + rectangle = Image.new( + "RGBA", + (main_width, main_height), + (0, 0, 0, 0) + ) + + # Draw a semi-transparent rectangle on it + rect = ImageDraw.Draw(rectangle) + rect.rectangle( + (margin, offset, main_width - margin, main_height - margin), + fill=(63, 63, 63, 128) + ) + + del rect + + # Paste it onto the final image + full_image.paste(rectangle, (0, 0), mask=rectangle) + + # Draw the text onto the final image + draw = ImageDraw.Draw(full_image) + for line in textwrap.wrap(description, 36): + draw.text([margin + 4, offset], line, font=CARD['font']) + offset += CARD['font'].getsize(line)[1] + + del draw + + # Get the image contents as a BufferIO object + buffer = BytesIO() + full_image.save(buffer, 'PNG') + buffer.seek(0) + + return buffer + + @command() + async def snake_card(self, ctx: Context, *args): + """Create an interesting little card from a snake!""" + # Someone's funky kwarg stuff + content = await self.get_snek( + args[0] if args else None, + self.kwargs( + args[1:], + positional_args=["autocorrect"] + ) + ) + + async with ctx.typing(): + + stream = BytesIO() + async with async_timeout.timeout(10): + async with self.session.get(content['image']) as response: + stream.write(await response.read()) + + stream.seek(0) + + func = partial(self.generate_card, stream, content) + final_buffer = await self.bot.loop.run_in_executor(None, func) + + await ctx.send( + f"A wild {content['name'].title()} appears!", + file=File(final_buffer, filename=content['name'].replace(" ", "") + ".png") + ) + def setup(bot): bot.add_cog(Snakes(bot)) diff --git a/bot/cogs/snakes_juan.py b/bot/cogs/snakes_juan.py new file mode 100644 index 00000000..ca4c493c --- /dev/null +++ b/bot/cogs/snakes_juan.py @@ -0,0 +1,270 @@ +# coding=utf-8 + +# This shit imports more than the USA D: +import ast +import difflib +import json +import logging +import os +import random +import textwrap +import urllib.parse +from functools import partial +from io import BytesIO +from typing import Dict + +from PIL import Image, ImageDraw, ImageFont + +import aiohttp + +import async_timeout + +from discord import Embed, File +from discord.ext.commands import AutoShardedBot, Context, command + +log = logging.getLogger(__name__) + +# Probably should move these somewhere + +WIKI = "https://en.wikipedia.org/w/api.php?" +BASEURL = WIKI + "format=json&action=query&prop=extracts|pageimages&exintro=&explaintext=&titles={title}&redirects=1" +FAILIMAGE = "http://i.imgur.com/HtIPyLy.png/beep" + +# Yes, we're naming snakes. Shush. +with open("bot/specials.json") as specials: + SPECIALS = json.load(specials) + +with open("bot/snakes.txt") as f: + SNAKES = [line.strip() for line in f.readlines()] + +# I don't think this is the best way of doing it +CARD = { + "top": Image.open("bot/cards/card_top.png"), + "frame": Image.open("bot/cards/card_frame.png"), + "bottom": Image.open("bot/cards/card_bottom.png"), + "backs": [Image.open(f"bot/cards/backs/{file}") for file in os.listdir("bot/cards/backs")], + "font": ImageFont.truetype("bot/cards/expressway.ttf", 20) +} + + +class Snakes: + """ + Snake-related commands + """ + + def __init__(self, bot: AutoShardedBot): + self.bot = bot + self.session = aiohttp.ClientSession(loop=bot.loop) + + @staticmethod + def parse_arg(arg: str): + """Return the value of a arg (needs manual assignment)""" + try: + items = arg.split("=") + value = items[1] + except (IndexError, AttributeError): + value = arg + + try: + parsed = ast.literal_eval(value) + except (NameError, ValueError): + parsed = value + + return parsed + + @staticmethod + def snake_url(name: str): + """Get the URL of a snake""" + name = name.lower() + + def format_url(text: str): + """Get the full URL with that snake :D""" + return BASEURL.format(title=urllib.parse.quote_plus(text)) + + # Check if the snake name is valid + if name in SNAKES: + return format_url(name) + + # Get the most similar name if a match wasn't found + return difflib.get_close_matches(name, SNAKES, n=5, cutoff=0) + + @staticmethod + async def fetch_image(self, url: str): + """Fetch an image, return a BytesIO object""" + stream = BytesIO() + async with async_timeout.timeout(10): + async with self.session.get(url) as response: + stream.write(await response.read()) + + stream.seek(0) + return stream + + async def get_snek(self, name: str = None, autocorrect: bool = True) -> Dict[str, str]: + """If a name is provided, this gets a specific snake. Otherwise, it gets a random snake.""" + + if name is None: + name = random.choice(SNAKES) + + name = name.lower().replace("-", " ") + if name in SPECIALS: + return SPECIALS[name] + + # Get snake information + url = self.snake_url(name) + + # Does some magic if the url is actually a list ^^ + if isinstance(url, list): + if autocorrect: + return await self.get_snek(url[0]) + + return { + "name": "Oops!", + "info": "We can't find that snake, but here are some similar names:\n\n" + "\n".join(url).title() + } + + # Get the content + async with async_timeout.timeout(10): + async with self.session.get(url) as response: + response = await response.json() + + page = response["query"]["pages"] + content = next(iter(page.values())) + + # Parse the full-res image from the thumbnail + thumb = content.get("thumbnail", {}).get("source", FAILIMAGE) + image = "/".join(thumb.replace("thumb/", "").split("/")[:-1]) + + return { + "name": content["title"], + "info": content.get("extract", "I don't know about that snake!"), + "image": image + } + + @command() + async def get(self, ctx: Context, name: str, autocorrect: str = "False"): + """Get information about a snake :D""" + autocorrect = self.parse_arg(autocorrect) + content = await self.get_snek(name, autocorrect) + + embed = Embed( + title=content["name"], + description=content["info"][:2000], # May consider cutting it shorter. 'Rattler' is a long one. + color=0x7289da + ) + + if "image" in content: + embed.set_image(url=content.get("image")) + + await ctx.send(embed=embed) + + # Any additional commands can be placed here. Be creative, but keep it to a reasonable amount! + + # Cards are here :D + + @staticmethod + def generate_card(buffer: BytesIO, content: dict) -> BytesIO: + """Generate a card from snake information""" + snake = Image.open(buffer) + + # Get the size of the snake icon, configure the height of the image box (yes, it changes) + icon_width = 347 # Hardcoded, not much i can do about that + icon_height = int((icon_width / snake.width) * snake.height) + frame_copies = icon_height // CARD['frame'].height + 1 + snake.thumbnail((icon_width, icon_height)) + + # Get the dimensions of the final image + main_height = icon_height + CARD['top'].height + CARD['bottom'].height + main_width = CARD['frame'].width + + # Start creating the foreground + foreground = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) + foreground.paste(CARD['top'], (0, 0)) + + # Generate the frame borders to the correct height + for offset in range(frame_copies): + position = (0, CARD['top'].height + offset * CARD['frame'].height) + foreground.paste(CARD['frame'], position) + + # Add the image and bottom part of the image + foreground.paste(snake, (36, CARD['top'].height)) # Also hardcoded :( + foreground.paste(CARD['bottom'], (0, CARD['top'].height + icon_height)) + + # Setup the background + back = random.choice(CARD['backs']) + back_copies = main_height // back.height + 1 + full_image = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) + + # Generate the tiled background + for offset in range(back_copies): + full_image.paste(back, (16, 16 + offset * back.height)) + + # Place the foreground onto the final image + full_image.paste(foreground, (0, 0), foreground) + + # Get the first two sentences of the info + description = '.'.join(content['info'].split(".")[:2]) + '.' + + # Setup positioning variables + margin = 36 + offset = CARD['top'].height + icon_height + margin + + # Create blank rectangle image which will be behind the text + rectangle = Image.new( + "RGBA", + (main_width, main_height), + (0, 0, 0, 0) + ) + + # Draw a semi-transparent rectangle on it + rect = ImageDraw.Draw(rectangle) + rect.rectangle( + (margin, offset, main_width - margin, main_height - margin), + fill=(63, 63, 63, 128) + ) + + del rect + + # Paste it onto the final image + full_image.paste(rectangle, (0, 0), mask=rectangle) + + # Draw the text onto the final image + draw = ImageDraw.Draw(full_image) + for line in textwrap.wrap(description, 36): + draw.text([margin + 4, offset], line, font=CARD['font']) + offset += CARD['font'].getsize(line)[1] + + del draw + + # Get the image contents as a BufferIO object + buffer = BytesIO() + full_image.save(buffer, 'PNG') + buffer.seek(0) + + return buffer + + @command() + async def snake_card(self, ctx: Context, name: str): + """Create an interesting little card from a snake!""" + content = await self.get_snek(name, True) + + async with ctx.typing(): + + stream = BytesIO() + async with async_timeout.timeout(10): + async with self.session.get(content['image']) as response: + stream.write(await response.read()) + + stream.seek(0) + + func = partial(self.generate_card, stream, content) + final_buffer = await self.bot.loop.run_in_executor(None, func) + + await ctx.send( + f"A wild {content['name'].title()} appears!", + file=File(final_buffer, filename=content['name'].replace(" ", "") + ".png") + ) + + +def setup(bot): + bot.add_cog(Snakes(bot)) + log.info("Cog loaded: Snakes") diff --git a/bot/snakes.txt b/bot/snakes.txt new file mode 100644 index 00000000..2db821f5 --- /dev/null +++ b/bot/snakes.txt @@ -0,0 +1,506 @@ +viperidae +common adder +acanthophis +desert death adder +long-nosed adder +many-horned adder +mud adder +namaqua dwarf adder +peringuey's adder +african puff adder +rhombic night adder +dwarf sand adder +namib dwarf sand adder +water adder +aesculapian snake +anaconda +eunectes +de schauensee's anaconda +green anaconda +yellow anaconda +arafura file snake +asp (reptile) +european asp +egyptian asp +ball python +bird snake +black-headed snake +mexican black kingsnake +black rat snake +red-bellied black snake +blind snake +brahminy blind snake +texas blind snake +western blind snake +boidae +abaco island boa +amazon tree boa +boa constrictor +cuban boa +dumeril's boa +dwarf boa +emerald tree boa +hogg island boa +jamaican boa +madagascar ground boa +madagascar tree boa +puerto rican boa +rainbow boa +red-tailed boa +rosy boa +rubber boa +sand boa +tree boa +boiga +boomslang +eastern brown snake +bull snake +lachesis (genus) +dwarf beaked snake +rufous beaked snake +crotalus horridus +agkistrodon bilineatus +crotalus durissus +cat-eyed snake +banded cat-eyed snake +cat snake +andaman cat snake +beddome's cat snake +dog-toothed cat snake +forsten's cat snake +gold-ringed cat snake +gray cat snake +many-spotted cat snake +nicobar cat snake +sri lanka cat snake +tawny cat snake +coachwhip snake +cobra +andaman cobra +arabian cobra +banded water cobra +black-necked spitting cobra +cape cobra +caspian cobra +chinese cobra +congo water cobra +common cobra +egyptian cobra +equatorial spitting cobra +false cobra +false water cobra +forest cobra +indian cobra +indochinese spitting cobra +javan spitting cobra +king cobra +monocled cobra +mozambique spitting cobra +nubian spitting cobra +philippine cobra +red spitting cobra +rinkhals cobra +shield-nosed cobra +southern philippine cobra +snouted cobra +spectacled cobra +spitting cobra +yellow cobra +zebra spitting cobra +collett's snake +congo snake +american copperhead +australian copperhead +coral snake +arizona coral snake +beddome's coral snake +brazilian coral snake +cape coral snake +eastern coral snake +false coral snake +harlequin coral snake +malayan long-glanded coral snake +texas coral snake +corn snake +agkistrodon piscivorus +crowned snake +cuban wood snake +eastern hognose snake +dasypeltis +indian egg-eater +eyelash viper +eastern coral snake +fierce snake +flying snake +golden tree snake +ornate flying snake +paradise flying snake +banded flying snake +fox snake +garter snake +checkered garter snake +common garter snake +san francisco garter snake +texas garter snake +glossy snake +gopher snake +cape gopher snake +grass snake +opheodrys +rough green snake +smooth green snake +common ground snake +three-lined ground snake +western ground snake +himehabu +okinawan habu +sakishima habu +tokara habu +elaps harlequin snake +hognose snake +eastern hognose snake +plains hognose snake +southern hognose snake +western hognose snake +hoop snake +hundred pacer +micropechis ikaheka +indigo snake +bothrops jararacussu +keelback +andrea's keelback +asian keelback +assam keelback +buff striped keelback +checkered keelback +hill keelback +himalayan keelback +khasi hills keelback +modest keelback +nicobar island keelback +nilgiri keelback +orange-collared keelback +red-necked keelback +sikkim keelback +wall's keelback +white-lipped keelback +wynaad keelback +yunnan keelback +king brown +king cobra +king snake +california kingsnake +desert kingsnake +grey-banded kingsnake +prairie kingsnake +scarlet kingsnake +speckled kingsnake +krait +banded krait +blue krait +black krait +burmese krait +indian krait +lesser black krait +malayan krait +many-banded krait +northeastern hill krait +red-headed krait +sind krait +south andaman krait +large shield snake +lancehead +common lancehead +leptophis ahaetulla +grey lora +lyre snake +texas lyre snake +mamba +black mamba +eastern green mamba +western green mamba +mamushi +milk snake +moccasin snake +montpellier snake +mud snake +mussurana +night snake +cat-eyed night snake +texas night snake +nose-horned viper +parrot snake +mexican parrot snake +patchnose snake +perrotet's shieldtail snake +pine snake +asian pipe snake +dwarf pipe snake +red-tailed pipe snake +pythonidae +african rock python +amethystine python +angolan python +australian scrub python +ball python +bismarck ringed python +black headed python +blood python +boelen python +borneo short-tailed python +bredl's python +brown water python +burmese python +calabar python +centralian carpet python +coastal carpet python +inland carpet python +jungle carpet python +new guinea carpet python +northwestern carpet python +southwestern carpet python +children's python +dauan island water python +desert woma python +diamond python +green tree python +halmahera python +indian python +indonesian water python +macklot's python +oenpelli python +olive python +papuan python +pygmy python +red blood python +reticulated python +selayer reticulated python +rough-scaled python +royal python +savu python +spotted python +stimson's python +sumatran short-tailed python +timor python +wetar island python +brown white-lipped python +northern white-lipped python +southern white-lipped python +woma python +western woma python +queen snake +buttermilk racer +eastern racer +mexican racer +southern black racer +tan racer +southwestern blackhead snake +rat snake +baird's rat snake +beauty rat snake +great plains rat snake +green rat snake +japanese forest rat snake +japanese rat snake +king rat snake +mandarin rat snake +persian rat snake +twin-spotted rat snake +yellow-striped rat snake +manchurian black water snake +rattlesnake +arizona black rattlesnake +aruba rattlesnake +coronado island rattlesnake +dusky pigmy rattlesnake +eastern diamondback rattlesnake +grand canyon rattlesnake +hopi rattlesnake +lance-headed rattlesnake +long-tailed rattlesnake +massasauga rattlesnake +mexican green rattlesnake +mexican west coast rattlesnake +midget faded rattlesnake +mojave rattlesnake +northern black-tailed rattlesnake +oaxacan small-headed rattlesnake +rattler +red diamond rattlesnake +southern pacific rattlesnake +southwestern speckled rattlesnake +tancitaran dusky rattlesnake +tiger rattlesnake +timber rattlesnake +tropical rattlesnake +twin-spotted rattlesnake +uracoan rattlesnake +western diamondback rattlesnake +ribbon snake +rinkhals +river jack +sea snake +annulated sea snake +beaked sea snake +hook nosed sea snake +olive sea snake +pelagic sea snake +yellow-bellied sea snake +shield-tailed snake +crotalus cerastes +colorado desert sidewinder +mojave desert sidewinder +sonoran sidewinder +micropechis ikaheka +brazilian smooth snake +european smooth snake +sonoran +stiletto snake +japanese striped snake +sunbeam snake +taipan +central ranges taipan +coastal taipan +inland taipan +tentacled snake +tic polonga +tiger snake +chappell island tiger snake +common tiger snake +down's tiger snake +eastern tiger snake +king island tiger snake +krefft's tiger snake +tasmanian tiger snake +western tiger snake +tigre snake +tree snake +blunt-headed tree snake +brown tree snake +many-banded tree snake +northern tree snake +trinket snake +black-banded trinket snake +twig snake +african twig snake +titanboa +urutu +ahaetulla +oxybelis +mexican vine snake +viperidae +asp viper +bluntnose viper +burrowing viper +bush viper +great lakes bush viper +hairy bush viper +nitsche's bush viper +rough-scaled bush viper +spiny bush viper +carpet viper +crossed viper +cyclades blunt-nosed viper +eyelash viper +false horned viper +fea's viper +fifty pacer +gaboon viper +hognosed viper +horned desert viper +jumping viper +kaznakov's viper +leaf-nosed viper +leaf viper +levant viper +long-nosed viper +mcmahon's viper +mole viper +nose-horned viper +palestine viper +pallas' viper +palm viper +amazonian palm viper +black-speckled palm-pitviper +eyelash palm-pitviper +green palm viper +mexican palm-pitviper +guatemalan palm viper +honduran palm viper +siamese palm viper +side-striped palm-pitviper +yellow-lined palm viper +pit viper +banded pitviper +bamboo pitviper +barbour's pit viper +black-tailed horned pit viper +bornean pitviper +brongersma's pitviper +brown spotted pitviper +cantor's pitviper +elegant pitviper +eyelash pit viper +fan-si-pan horned pitviper +flat-nosed pitviper +godman's pit viper +green tree pit viper +hagen's pitviper +horseshoe pitviper +jerdon's pitviper +kanburian pit viper +kaulback's lance-headed pitviper +kham plateau pitviper +large-eyed pitviper +malabar rock pitviper +malayan pit viper +mangrove pit viper +mangshan pitviper +motuo bamboo pitviper +nicobar bamboo pitviper +philippine pitviper +red-tailed bamboo pitviper +schultze's pitviper +stejneger's bamboo pitviper +sri lankan pit viper +temple pit viper +tibetan bamboo pitviper +tiger pit viper +undulated pit viper +wagler's pit viper +wirot's pit viper +portuguese viper +rhinoceros viper +river jack +russell's viper +sand viper +saw-scaled viper +schlegel's viper +sedge viper +sharp-nosed viper +snorkel viper +temple viper +tree viper +chinese tree viper +guatemalan tree viper +hutton's tree viper +indian tree viper +large-scaled tree viper +malcolm's tree viper +nitsche's tree viper +pope's tree viper +rough-scaled tree viper +rungwe tree viper +sumatran tree viper +white-lipped tree viper +ursini's viper +wart snake +water moccasin +bocourt's water snake +northern water snake +long-nosed whip snake +wolf snake +common worm snake +longnosed worm snake +wutu +yarara diff --git a/bot/specials.json b/bot/specials.json new file mode 100644 index 00000000..aa06414f --- /dev/null +++ b/bot/specials.json @@ -0,0 +1,17 @@ +{ + "python": { + "name": "Python", + "info": "Python is a species of programming language, commonly used by coding beginners and experts alike. It was first discovered in 1989 by Guido van Rossum in the Netherlands, and was released to the wild two years later. Its use of dynamic typing is one of many distinct features, alongside significant whitespace, heavy emphasis on readability, and, above all, absolutely pain-free software distribution... *sigh*", + "image": "https://www.python.org/static/community_logos/python-logo-master-v3-TM-flattened.png" + }, + "hunny bunny": { + "name": "Western ground snake", + "info": "The western ground snake (Sonora semiannulata) is a species of small, harmless colubrid snake. The species is endemic to North America. It is sometimes referred to as the common ground snake or variable ground snake as its patterning and coloration can vary widely, even within the same geographic region.", + "image": "http://i.imgur.com/zZPGzz0.png" + }, + "bob ross": { + "name": "Bob Ross", + "info": "Robert Norman Ross (October 29, 1942 – July 4, 1995) was an Americanpainter, art instructor, and television host. He was the creator and host of The Joy ofPainting, an instructional television program that aired from 1983 to 1994 on PBS in the United States, and also aired in Canada, Latin America, and Europe. Ross went from being apublic-television personality in the 1980s and 1990s to being an Internet celebrity in the21st century, popular with fans on YouTube and many other websites.", + "image": "https://images05.military.com/sites/default/files/styles/full/public/media/people/2013/05/bobrosspainting.jpg?itok=A3G_e8MM" + } +} \ No newline at end of file diff --git a/run.py b/run.py index 2ec711fd..1466aace 100644 --- a/run.py +++ b/run.py @@ -33,7 +33,8 @@ # Commands, etc -bot.load_extension("bot.cogs.snakes") +# Remove the '_juan' part to test @Someone's version, of course. +bot.load_extension("bot.cogs.snakes_juan") bot.run(os.environ.get("BOT_TOKEN"))